commit 37fe8fa76cea4893ecb239043fdb2d2f06802a46 parent 237507ccf48084b712f5b6895bb8b24dc104e3a4 Author: Christian Grothoff <christian@grothoff.org> Date: Sun, 8 Feb 2026 22:32:16 +0100 break up exchange API docs into one file per endpoint Diffstat:
| M | core/api-exchange.rst | | | 7050 | ++++++++----------------------------------------------------------------------- |
| A | core/exchange/delete-purses-PURSE_PUB.rst | | | 28 | ++++++++++++++++++++++++++++ |
| A | core/exchange/delete-reserves-RESERVE_PUB.rst | | | 58 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/get-aml-OFFICER_PUB-accounts.rst | | | 137 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/get-aml-OFFICER_PUB-attributes-H_NORMALIZED_PAYTO.rst | | | 98 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/get-aml-OFFICER_PUB-decisions.rst | | | 225 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/get-aml-OFFICER_PUB-kyc-statistics-NAMES.rst | | | 59 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/get-aml-OFFICER_PUB-legitimizations.rst | | | 72 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/get-aml-OFFICER_PUB-measures.rst | | | 136 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/get-aml-OFFICER_PUB-transfers-credit.rst | | | 80 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/get-coins-COIN_PUB-history.rst | | | 1 | + |
| A | core/exchange/get-config.rst | | | 95 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/get-contracts-CONTRACT_PUB.rst | | | 28 | ++++++++++++++++++++++++++++ |
| A | core/exchange/get-deposits-H_WIRE-MERCHANT_PUB-H_CONTRACT_TERMS-COIN_PUB.rst | | | 107 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/get-keys.rst | | | 621 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/get-kyc-check-H_NORMALIZED_PAYTO.rst | | | 143 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/get-kyc-info-ACCESS_TOKEN.rst | | | 93 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/get-kyc-proof-PROVIDER_NAME.rst | | | 62 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/get-kyc-spa-ACCESS_TOKEN.rst | | | 12 | ++++++++++++ |
| A | core/exchange/get-kyc-webhook-PROVIDER_NAME-star.rst | | | 22 | ++++++++++++++++++++++ |
| A | core/exchange/get-management-keys.rst | | | 138 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/get-purses-PURSE_PUB-merge.rst | | | 70 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/get-reserves-RESERVE_PUB-history.rst | | | 308 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/get-reserves-RESERVE_PUB.rst | | | 44 | ++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/get-reserves-attest-RESERVE_PUB.rst | | | 25 | +++++++++++++++++++++++++ |
| A | core/exchange/get-seed.rst | | | 8 | ++++++++ |
| A | core/exchange/get-transfers-WTID.rst | | | 74 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/get-wads-WAD_ID.rst | | | 82 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/post-aml-OFFICER_PUB-decision.rst | | | 89 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/post-auditors-AUDITOR_PUB-H_DENOM_PUB.rst | | | 63 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/post-batch-deposit.rst | | | 338 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/post-blinding-prepare.rst | | | 95 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/post-coins-COIN_PUB-refund.rst | | | 93 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/post-kyc-start-ID.rst | | | 37 | +++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/post-kyc-upload-ID.rst | | | 29 | +++++++++++++++++++++++++++++ |
| A | core/exchange/post-kyc-wallet.rst | | | 78 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/post-management-aml-officers.rst | | | 93 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/post-management-auditors-AUDITOR_PUB-disable.rst | | | 44 | ++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/post-management-auditors.rst | | | 46 | ++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/post-management-denominations-H_DENOM_PUB-revoke.rst | | | 29 | +++++++++++++++++++++++++++++ |
| A | core/exchange/post-management-drain.rst | | | 45 | +++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/post-management-global-fees.rst | | | 16 | ++++++++++++++++ |
| A | core/exchange/post-management-keys.rst | | | 58 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/post-management-signkeys-EXCHANGE_PUB-revoke.rst | | | 29 | +++++++++++++++++++++++++++++ |
| A | core/exchange/post-management-wire-disable.rst | | | 46 | ++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/post-management-wire-fee.rst | | | 43 | +++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/post-management-wire.rst | | | 55 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/post-melt.rst | | | 185 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/post-purses-PURSE_PUB-create.rst | | | 205 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/post-purses-PURSE_PUB-deposit.rst | | | 119 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/post-purses-PURSE_PUB-merge.rst | | | 103 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/post-recoup-refresh.rst | | | 80 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/post-recoup-withdraw.rst | | | 142 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/post-reserves-RESERVE_PUB-close.rst | | | 66 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/post-reserves-RESERVE_PUB-open.rst | | | 128 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/post-reserves-RESERVE_PUB-purse.rst | | | 100 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/post-reserves-attest-RESERVE_PUB.rst | | | 62 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/post-reveal-melt.rst | | | 93 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/post-reveal-withdraw.rst | | | 98 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | core/exchange/post-withdraw.rst | | | 134 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
60 files changed, 6388 insertions(+), 6329 deletions(-)
diff --git a/core/api-exchange.rst b/core/api-exchange.rst @@ -44,6683 +44,1075 @@ hard-coded into the wallet as they are the trust anchors for Taler; (3) possibly by using HTTPS. -.. http:get:: /seed +.. include:: exchange/get-seed.rst - Return an entropy seed. The exchange will return a high-entropy - value that will differ for every call. The response is NOT in - JSON, but simply high-entropy binary data in the HTTP body. - This API should be used by wallets to guard themselves against - running on low-entropy (bad PRNG) hardware. Naturally, the entropy - returned MUST be mixed with locally generated entropy. +.. include:: exchange/get-config.rst -.. http:get:: /config - Return the protocol version and currency supported by this exchange backend, - as well as the list of possible KYC requirements. This endpoint is largely - for the SPA for AML officers. Merchants should use ``/keys`` which also - contains the protocol version and currency. - This specification corresponds to ``current`` protocol being **v31**. +.. include:: exchange/get-keys.rst - **Response:** - :http:statuscode:`200 OK`: - The body is a `VersionResponse`. - .. ts:def:: ExchangeVersionResponse - interface ExchangeVersionResponse { - // libtool-style representation of the Exchange protocol version, see - // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning - // The format is "current:revision:age". - version: string; - - // Name of the protocol. - name: "taler-exchange"; +---------------------------------------------- +Management operations authorized by master key +---------------------------------------------- - // URN of the implementation (needed to interpret 'revision' in version). - // @since protocol **v18**, may become mandatory in the future. - implementation?: string; +.. include:: exchange/get-management-keys.rst - // Currency supported by this exchange, given - // as a currency code ("USD" or "EUR"). - currency: string; - // Shopping URL where users may find shops that accept - // digital cash issued by this exchange. - // @since protocol **v21**. - shopping_url?: string; +.. include:: exchange/post-management-keys.rst - // Open banking gateway base URL where wallets can - // initiate wire transfers to withdraw - // digital cash from this exchange. - // @since protocol **v30**. - open_banking_gateway?: string; - // How wallets should render this currency. - currency_specification: CurrencySpecification; +.. include:: exchange/post-management-denominations-H_DENOM_PUB-revoke.rst - // Names of supported KYC requirements. - // @deprected in **v24**. - supported_kyc_requirements: string[]; +.. include:: exchange/post-management-signkeys-EXCHANGE_PUB-revoke.rst - // Bank-specific dialect for the AML SPA. Determines - // which set of forms is available as well as statistics - // to show and sets of properties/events to trigger in - // AML decisions. - // @since protocol **v24**. - aml_spa_dialect?: string; - } +.. include:: exchange/post-management-auditors.rst - .. ts:def:: CurrencySpecification +.. include:: exchange/post-management-auditors-AUDITOR_PUB-disable.rst - interface CurrencySpecification { - // Name of the currency. Like "US Dollar". - name: string; - // Code of the currency. - // @deprecated in protocol **v18** for the exchange, and - // @deprecated in protocol **v6** for the merchant. - currency: string; +.. include:: exchange/post-management-wire-fee.rst - // how many digits the user may enter after the decimal_separator - num_fractional_input_digits: Integer; +.. include:: exchange/post-management-global-fees.rst - // Number of fractional digits to render in normal font and size. - num_fractional_normal_digits: Integer; - // Number of fractional digits to render always, if needed by - // padding with zeros. - num_fractional_trailing_zero_digits: Integer; - // map of powers of 10 to alternative currency names / symbols, must - // always have an entry under "0" that defines the base name, - // e.g. "0 => €" or "3 => k€". For BTC, would be "0 => BTC, -3 => mBTC". - // Communicates the currency symbol to be used. - alt_unit_names: { log10 : string }; - - // An array of common amounts that should be turned into - // display buttons in dialogs where the user might like - // a short-cut. The array should have four entries, but - // may have fewer or more entries. Wallets may omit - // later entries in the array. - // @since protocol **v30** (rev 2) - common_amounts: Amount[]; - - } +.. include:: exchange/post-management-wire.rst +.. include:: exchange/post-management-wire-disable.rst -.. http:get:: /keys - Get a list of all denomination keys offered by the exchange, - as well as the exchange's current online signing key. - - **Request:** - - :query last_issue_date: Optional argument specifying the maximum value of any of the ``stamp_start`` members of the denomination keys of a ``/keys`` response that is already known to the client. Allows the exchange to only return keys that have changed since that timestamp. The given value must be an unsigned 64-bit integer representing seconds after 1970. If the timestamp does not exactly match the ``stamp_start`` of one of the denomination keys, all keys are returned. - - **Response:** - - :http:statuscode:`200 OK`: - The exchange responds with a `ExchangeKeysResponse` object. This request should - virtually always be successful. It only fails if the exchange is misconfigured or - has not yet been provisioned with key signatures via ``taler-exchange-offline``. - - **Details:** - - .. ts:def:: ExchangeKeysResponse - - interface ExchangeKeysResponse { - // libtool-style representation of the Exchange protocol version, see - // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning - // The format is "current:revision:age". - version: string; - - // The exchange's base URL. - base_url: string; - - // The exchange's currency or asset unit. - currency: string; - - // Shopping URL where users may find shops that accept - // digital cash issued by this exchange. - // @since protocol **v21**. - shopping_url?: string; - - // Open banking gateway base URL where wallets can - // initiate wire transfers to withdraw - // digital cash from this exchange. - // @since protocol **v30**. - open_banking_gateway?: string; - - // Instructs wallets to use certain bank-specific - // language (for buttons) and/or other UI/UX customization - // for compliance with the rules of that bank. - // The specific customizations to apply are done on a per-wallet - // basis as requested by the specific bank. They only - // apply when it is clear that the wallet is using digital - // cash from that bank. This is an advisory option, not - // all wallets must support all compliance languages. - // @since protocol **v24**. - bank_compliance_language?: string; - - // How wallets should render this currency. - currency_specification: CurrencySpecification; - - // Small(est?) amount that can likely be transferred to - // the exchange. Should be the default amount for KYC - // authentication wire transfers to this exchange. - // Optional, not present if not known or not configured. - // @since protocol **v21**. - tiny_amount?: Amount; - - // Absolute cost offset for the STEFAN curve used - // to (over) approximate fees payable by amount. - stefan_abs: Amount; - - // Factor to multiply the logarithm of the amount - // with to (over) approximate fees payable by amount. - // Note that the total to be paid is first to be - // divided by the smallest denomination to obtain - // the value that the logarithm is to be taken of. - stefan_log: Amount; - - // Linear cost factor for the STEFAN curve used - // to (over) approximate fees payable by amount. - // - // Note that this is a scalar, as it is multiplied - // with the actual amount. - stefan_lin: Float; - - // Type of the asset. "fiat", "crypto", "regional" - // or "stock". Wallets should adjust their UI/UX - // based on this value. - asset_type: string; - - // Array of wire accounts operated by the exchange for - // incoming wire transfers. - accounts: ExchangeWireAccount[]; - - // Object mapping names of wire methods (i.e. "iban" or "x-taler-bank") - // to wire fees. - wire_fees: { method : AggregateTransferFee[] }; - - // List of exchanges that this exchange is partnering - // with to enable wallet-to-wallet transfers. - wads: ExchangePartnerListEntry[]; - - // Set to true if this exchange allows the use - // of reserves for rewards. - // @deprecated in protocol **v18**. - rewards_allowed: false; - - // Set to true if this exchange has KYC enabled and thus - // requires KYC auth wire transfers prior to a first deposit. - // @since in protocol **v24**. - kyc_enabled: boolean; - - // Set to TRUE if wallets should disable the direct deposit feature - // and deposits should only go via Taler merchant APIs. - // Mainly used for regional currency and event currency deployments - // where wallets are not eligible to deposit back into originating - // bank accounts and, because KYC is not enabled, wallets are thus - // likely to send money to nirvana instead of where users want it. - // @since in protocol **v30**. - disable_direct_deposit: boolean; - - // EdDSA master public key of the exchange, used to sign entries - // in ``denoms`` and ``signkeys``. - master_public_key: EddsaPublicKey; - - // Relative duration until inactive reserves are closed; - // not signed (!), can change without notice. - reserve_closing_delay: RelativeTime; - - // Threshold amounts beyond which wallet should - // trigger the KYC process of the issuing exchange. - // Optional option, if not given there is no limit. - // Currency must match ``currency``. - wallet_balance_limit_without_kyc?: Amount[]; - - // Array of limits that apply to all accounts. - // All of the given limits will be hard limits. - // Wallets and merchants are expected to obey them - // and not even allow the user to cross them. - // @since protocol **v21**. - hard_limits: AccountLimit[]; - - // Array of limits with a soft threshold of zero - // that apply to all accounts without KYC. - // Wallets and merchants are expected to trigger - // a KYC process before attempting any zero-limited - // operations. - // @since protocol **v21**. - zero_limits: ZeroLimitedOperation[]; - - // Denominations offered by this exchange - denominations: DenomGroup[]; - - // Compact EdDSA `signature` (binary-only) over the - // contatentation of all of the master_sigs (in reverse - // chronological order by group) in the arrays under - // "denominations". Signature of `TALER_ExchangeKeySetPS` - exchange_sig: EddsaSignature; - - // Public EdDSA key of the exchange that was used to generate the signature. - // Should match one of the exchange's signing keys from ``signkeys``. It is given - // explicitly as the client might otherwise be confused by clock skew as to - // which signing key was used for the ``exchange_sig``. - exchange_pub: EddsaPublicKey; - - // Denominations for which the exchange currently offers/requests recoup. - recoup: RecoupDenoms[]; - - // Array of globally applicable fees by time range. - global_fees: GlobalFees[]; - - // The date when the denomination keys were last updated. - list_issue_date: Timestamp; - - // Auditors of the exchange. - auditors: AuditorKeys[]; - - // The exchange's signing keys. - signkeys: SignKey[]; - - // Optional field with a dictionary of (name, object) pairs defining the - // supported and enabled extensions, such as ``age_restriction``. - extensions?: { name: ExtensionManifest }; - - // Signature by the exchange master key of the SHA-256 hash of the - // normalized JSON-object of field extensions, if it was set. - // The signature has purpose TALER_SIGNATURE_MASTER_EXTENSIONS. - extensions_sig?: EddsaSignature; - - } - - The specification for the account object is: +.. include:: exchange/post-management-drain.rst - .. ts:def:: ExchangeWireAccount - interface ExchangeWireAccount { - // Full ``payto://`` URI identifying the account and wire method - payto_uri: string; - - // URI to convert amounts from or to the currency used by - // this wire account of the exchange. Missing if no - // conversion is applicable. - conversion_url?: string; - - // Restrictions that apply to bank accounts that would send - // funds to the exchange (crediting this exchange bank account). - // Optional, empty array for unrestricted. - credit_restrictions: AccountRestriction[]; - - // Restrictions that apply to bank accounts that would receive - // funds from the exchange (debiting this exchange bank account). - // Optional, empty array for unrestricted. - debit_restrictions: AccountRestriction[]; - - // Signature using the exchange's offline key over - // a `TALER_MasterWireDetailsPS` - // with purpose ``TALER_SIGNATURE_MASTER_WIRE_DETAILS``. - master_sig: EddsaSignature; - - // Display label wallets should use to show this - // bank account. - // @since protocol **v19**. - bank_label?: string; +.. include:: exchange/post-management-aml-officers.rst - // *Signed* integer with the display priority for - // this bank account. Optional, 0 if missing. - // @since protocol **v19**. - priority?: Integer; +--------------- +Auditor actions +--------------- - } +.. _auditor_action: - .. ts:def:: AccountRestriction +This part of the API is for the use by auditors interacting with the exchange. - type AccountRestriction = - | RegexAccountRestriction - | DenyAllAccountRestriction - .. ts:def:: DenyAllAccountRestriction +.. include:: exchange/post-auditors-AUDITOR_PUB-H_DENOM_PUB.rst - // Account restriction that disables this type of - // account for the indicated operation categorically. - interface DenyAllAccountRestriction { - type: "deny"; - } +---------------- +Blinding Prepare +---------------- - .. ts:def:: RegexAccountRestriction +Certain denomination cipher types, such as Clause-Schnorr, require input values +from the exchange-side as preparation for the blinding of the coins. See the +Bachelor thesis of Gian Demarmels and Lucien Heuzeveldt, +`Adding Schnorr’s Blind Signature in Taler <https://www.taler.net/papers/cs-thesis.pdf>`_, +for details. - // Accounts interacting with this type of account - // restriction must have a normalized payto://-URI matching - // the given regex. - interface RegexAccountRestriction { +.. include:: exchange/post-blinding-prepare.rst - type: "regex"; - // Regular expression that the payto://-URI of the - // partner account must follow. The regular expression - // should follow posix-egrep, but without support for character - // classes, GNU extensions, back-references or intervals. See - // https://www.gnu.org/software/findutils/manual/html_node/find_html/posix_002degrep-regular-expression-syntax.html - // for a description of the posix-egrep syntax. Applications - // may support regexes with additional features, but exchanges - // must not use such regexes. - payto_regex: string; - // Hint for a human to understand the restriction - // (that is hopefully easier to comprehend than the regex itself). - human_hint: string; +.. _exchange-withdrawal: - // Map from IETF BCP 47 language tags to localized - // human hints. - human_hint_i18n?: { [lang_tag: string]: string }; +---------- +Withdrawal +---------- - } +This API is used by the wallet to obtain digital coins. - .. ts:def:: ZeroLimitedOperation +When transferring money to the exchange such as via SEPA transfers, the exchange creates +a *reserve*, which keeps the money from the customer. The customer must +specify an EdDSA reserve public key as part of the transfer, and can then +withdraw digital coins using the corresponding private key. All incoming and +outgoing transactions are recorded under the corresponding public key by the +exchange. - interface ZeroLimitedOperation { +.. note:: - // Operation that is limited to an amount of - // zero until the client has passed some KYC check. - // Must be one of "WITHDRAW", "DEPOSIT", - // (p2p) "MERGE", (wallet) "BALANCE", - // (reserve) "CLOSE", "AGGREGATE", - // "TRANSACTION" or "REFUND". - operation_type: string; + Eventually the exchange will need to advertise a policy for how long it will + keep transaction histories for inactive or even fully drained reserves. We + will therefore need some additional handler similar to ``/keys`` to + advertise those terms of service. - } +.. include:: exchange/get-reserves-RESERVE_PUB.rst - .. ts:def:: AccountLimit - interface AccountLimit { - // Operation that is limited. - // Must be one of "WITHDRAW", "DEPOSIT", - // (p2p) "MERGE", (wallet) "BALANCE", - // (reserve) "CLOSE", "AGGREGATE", - // "TRANSACTION" or "REFUND". - operation_type: string; +.. _withdraw: +.. include:: exchange/post-withdraw.rst - // Timeframe during which the limit applies. - // Not applicable for all operation_types - // (but always present in this object anyway). - timeframe: RelativeTime; - // Maximum amount allowed during the given timeframe. - // Zero if the operation is simply forbidden. - threshold: Amount; +.. ts:def:: WithdrawRequest - // True if this is a soft limit that could be raised - // by passing KYC checks. Clients *may* deliberately - // try to cross limits and trigger measures resulting - // in 451 responses to begin KYC processes. - // Clients that are aware of hard limits *should* - // inform users about the hard limit and prevent flows - // in the UI that would cause violations of hard limits. - // Made optional in **v21** with a default of 'false' if missing. - soft_limit?: boolean; - } + interface WithdrawRequest { + // Cipher that is used for the rerserve's signatures. + // For now, only ed25519 signatures are applicable, + // but this might change in future versions. + cipher: "ED25519"; - .. ts:def:: GlobalFees + // The reserve's public key, for the the cipher ED25519, + // to verify the signature ``reserve_sig``. + reserve_pub: EddsaPublicKey; - interface GlobalFees { + // Array of ``n`` hash codes of denomination public keys to order. + // 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. + // If ``max_age`` is set, these denominations MUST support + // age restriction as defined in the output to /keys. + denoms_h: HashCode[]; - // What date (inclusive) does these fees go into effect? - start_date: Timestamp; + // If set, the maximum age to commit to. This implies: + // 1.) it MUST be the same value as the maximum age + // of the reserve. + // 2.) ``coin_evs`` MUST be an array of ``n*kappa`` + // 3.) the denominations in ``denoms_h`` MUST support + // age restriction. + max_age?: Integer; - // What date (exclusive) does this fees stop going into effect? - end_date: Timestamp; + // Master seed for the Clause-Schnorr R-value creation. + // MUST match the /blinding-prepare request. + // MUST NOT have been used in any prior withdraw request. + // MUST be present if one of the fresh coin's + // denomination is of type Clause-Schnorr. + blinding_seed?: BlindingMasterSeed; - // Account history fee, charged when a user wants to - // obtain a reserve/account history. - history_fee: Amount; + // Array of blinded coin envelopes of type `CoinEnvelope`. + // If ``max_age`` is not set, MUST be n entries. + // If ``max_age`` is set, MUST be ``n*kappa`` entries, + // arranged in [0..n)..[0..n), with the first n entries + // belonging to kappa=0 etc. + // In case of age restriction, the exchange will + // respond with an index ``gamma``, which is the index + // that shall remain undisclosed during the subsequent + // reveal phase. + // This hash value along with the reserve's public key + // will also be used for recoup operations, if needed. + coin_evs: CoinEnvelope[]; - // Annual fee charged for having an open account at the - // exchange. Charged to the account. If the account - // balance is insufficient to cover this fee, the account - // is automatically deleted/closed. (Note that the exchange - // will keep the account history around for longer for - // regulatory reasons.) - account_fee: Amount; + // Signature of `TALER_WithdrawRequestPS` created with + // the `reserves's private key <reserve-priv>`. + reserve_sig: EddsaSignature; + } - // Purse fee, charged only if a purse is abandoned - // and was not covered by the account limit. - purse_fee: Amount; +.. ts:def:: WithdrawResponse - // How long will the exchange preserve the account history? - // After an account was deleted/closed, the exchange will - // retain the account history for legal reasons until this time. - history_expiration: RelativeTime; + interface WithdrawResponse { + // Array of blinded signatures over each ``coin_evs``, + // in the same order as was given in the request. + // The blinded signatures affirm the coin's validity + // after unblinding. + ev_sigs: BlindedDenominationSignature[]; - // 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; + } - // How long does an exchange keep a purse around after a purse - // has expired (or been successfully merged)? A 'GET' request - // for a purse will succeed until the purse expiration time - // plus this value. - purse_timeout: RelativeTime; - // Signature of `TALER_GlobalFeesPS`. - master_sig: EddsaSignature; +.. ts:def:: AgeWithdrawResponse - } + interface AgeWithdrawResponse { + // index of the commitments that the client doesn't + // have to disclose in the subsequent call to + // ``/reveal-withdraw``. + noreveal_index: Integer; - .. ts:def:: DenomGroup + // Signature of `TALER_WithdrawConfirmationPS` whereby + // the exchange confirms the ``noreveal_index``. + exchange_sig: EddsaSignature; - type DenomGroup = - | DenomGroupRsa - | DenomGroupCs - | DenomGroupRsaAgeRestricted - | DenomGroupCsAgeRestricted; + // `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:: DenomGroupRsa - - interface DenomGroupRsa extends DenomGroupCommon { - cipher: "RSA"; - - denoms: ({ - rsa_pub: RsaPublicKey; - } & DenomCommon)[]; - } + } - .. ts:def:: DenomGroupCs +.. ts:def:: DenominationGoneMessage - interface DenomGroupCs extends DenomGroupCommon { - cipher: "CS"; - - denoms: ({ - cs_pub: Cs25519Point; - } & DenomCommon)[]; - } - - .. ts:def:: DenomGroupRsaAgeRestricted - - interface DenomGroupRsaAgeRestricted extends DenomGroupCommon { - cipher: "RSA+age_restricted"; - age_mask: AgeMask; - - denoms: ({ - rsa_pub: RsaPublicKey; - } & DenomCommon)[]; - } - - .. ts:def:: DenomGroupCsAgeRestricted - - interface DenomGroupCsAgeRestricted extends DenomGroupCommon { - cipher: "CS+age_restricted"; - age_mask: AgeMask; - - denoms: ({ - cs_pub: Cs25519Point; - } & DenomCommon)[]; - } + interface DenominationGoneMessage { - .. ts:def:: DenomGroupCommon + // Taler error code. Note that beyond + // expiration this message format is also + // used if the key is not yet valid, or + // has been revoked. May be one of + // - ``TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE`` + // - ``TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED`` + // - ``TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED`` + code: Integer; - // Common attributes for all denomination groups - interface DenomGroupCommon { - // How much are coins of this denomination worth? - value: Amount; + // Signature by the exchange over a + // `TALER_DenominationExpiredAffirmationPS`. + // Must have purpose ``TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_EXPIRED``. + exchange_sig: EddsaSignature; - // Fee charged by the exchange for withdrawing a coin of this denomination. - fee_withdraw: Amount; + // Public key of the exchange used to create + // the 'exchange_sig. + exchange_pub: EddsaPublicKey; - // Fee charged by the exchange for depositing a coin of this denomination. - fee_deposit: Amount; + // Hash of the denomination public key that is unknown. + h_denom_pub: HashCode; - // Fee charged by the exchange for refreshing a coin of this denomination. - fee_refresh: Amount; + // When was the signature created. + timestamp: Timestamp; - // Fee charged by the exchange for refunding a coin of this denomination. - fee_refund: Amount; + // What kind of operation was requested that now + // failed? + oper: string; - } + } - .. ts:def:: DenomCommon - interface DenomCommon { - // Signature of `TALER_DenominationKeyValidityPS`. - master_sig: EddsaSignature; +.. ts:def:: WithdrawError - // When does the denomination key become valid? - stamp_start: Timestamp; + interface SingleWithdrawError { + // Text describing the error. + hint: string; - // When is it no longer possible to withdraw coins - // of this denomination? Note that while this option - // is given per denomination, all concurrently active - // denominations (of the same cipher type) - // will have exactly the same withdraw - // expiration time. Thus, the wallet can be sure what - // is the smallest denomination being offered at any - // particular point in time, and not worry about the - // exchange having merely failed to sign the key of - // only the smallest denomination unit. - stamp_expire_withdraw: Timestamp; + // Detailed error code. + code: Integer; - // When is it no longer possible to deposit coins - // of this denomination? - stamp_expire_deposit: Timestamp; + // Amount left in the reserve. + balance: Amount; - // Timestamp indicating by when legal disputes relating to these coins must - // be settled, as the exchange will afterwards destroy its evidence relating to - // transactions involving this coin. - stamp_expire_legal: Timestamp; + } - // Set to 'true' if the exchange somehow "lost" - // the private key. The denomination was not - // necessarily revoked, but still cannot be used - // to withdraw coins at this time (theoretically, - // the private key could be recovered in the - // future; coins signed with the private key - // remain valid). - lost?: boolean; - } - Fees for any of the operations can be zero, but the fields must still be - present. The currency of the ``fee_deposit``, ``fee_refresh`` and ``fee_refund`` must match the - currency of the ``value``. Theoretically, the ``fee_withdraw`` could be in a - different currency, but this is not currently supported by the - implementation. - .. ts:def:: RecoupDenoms +------------------ - interface RecoupDenoms { - // Hash of the public key of the denomination that is being revoked under - // emergency protocol (see ``/recoup``). - h_denom_pub: HashCode; - - // We do not include any signature here, as the primary use-case for - // this emergency involves the exchange having lost its signing keys, - // so such a signature here would be pretty worthless. However, the - // exchange will not honor ``/recoup`` requests unless they are for - // denomination keys listed here. - } - - A signing key in the ``signkeys`` list is a JSON object with the following fields: - - .. ts:def:: SignKey - - interface SignKey { - // The actual exchange's EdDSA signing public key. - key: EddsaPublicKey; - // Initial validity date for the signing key. - stamp_start: Timestamp; - - // Date when the exchange will stop using the signing key, allowed to overlap - // slightly with the next signing key's validity to allow for clock skew. - stamp_expire: Timestamp; - - // Date when all signatures made by the signing key expire and should - // henceforth no longer be considered valid in legal disputes. - stamp_end: Timestamp; +.. _reveal-withdraw: - // Signature over ``key`` and ``stamp_expire`` by the exchange master key. - // Signature of `TALER_ExchangeSigningKeyValidityPS`. - // Must have purpose ``TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY``. - master_sig: EddsaSignature; - } - - An entry in the ``auditors`` list is a JSON object with the following fields: - - .. ts:def:: AuditorKeys - - interface AuditorKeys { - // The auditor's EdDSA signing public key. - auditor_pub: EddsaPublicKey; - - // The auditor's URL. - auditor_url: string; - - // The auditor's name (for humans). - auditor_name: string; - - // An array of denomination keys the auditor affirms with its signature. - // Note that the message only includes the hash of the public key, while the - // signature is actually over the expanded information including expiration - // times and fees. The exact format is described below. - denomination_keys: AuditorDenominationKey[]; - } - - .. ts:def:: AuditorDenominationKey - - interface AuditorDenominationKey { - // Hash of the public RSA key used to sign coins of the respective - // denomination. Note that the auditor's signature covers more than just - // the hash, but this other information is already provided in ``denoms`` and - // thus not repeated here. - denom_pub_h: HashCode; - - // Signature of `TALER_ExchangeKeyValidityPS`. - auditor_sig: EddsaSignature; - } - - The same auditor may appear multiple times in the array for different subsets - of denomination keys, and the same denomination key hash may be listed - multiple times for the same or different auditors. The wallet or merchant - just should check that the denomination keys they use are in the set for at - least one of the auditors that they accept. +**Reveal-Withdraw** - .. note:: +This endpoint is called by the client after a call to `withdraw`_, +*if* the original request had ``max_age`` set and +the response was of type `AgeWithdrawResponse`. +Now the client has to disclose for each coin all but one of the κ secrets +that went into creating the blinded coin's planchets, +including the commitment to age restriction, +and prove that the age restriction was set correctly. - Both the individual denominations *and* the denomination list is signed, - allowing customers to prove that they received an inconsistent list. +.. include:: exchange/post-reveal-withdraw.rst - Aggregate wire transfer fees representing the fees the exchange - charges per wire transfer to a merchant must be specified as an - array in all wire transfer response objects under ``fees``. The - respective array contains objects with the following members: - .. ts:def:: AggregateTransferFee - interface AggregateTransferFee { - // Per transfer wire transfer fee. - wire_fee: Amount; +---------- +Refreshing +---------- - // Per transfer closing fee. - closing_fee: Amount; +Refreshing creates ``n`` new coins from ``m`` old coins, where the sum of +denominations of the new coins must be smaller than the sum of the old coins' +denominations plus melting (refresh) and withdrawal fees charged by the exchange. +The refreshing API can be used by wallets to melt partially spent coins, making +transactions with the freshly exchangeed coins unlinkabe to previous transactions +by anyone except the wallet itself. - // What date (inclusive) does this fee go into effect? - // The different fees must cover the full time period in which - // any of the denomination keys are valid without overlap. - start_date: Timestamp; +Refreshing is a two step process, consisting of - // What date (exclusive) does this fee stop going into effect? - // The different fees must cover the full time period in which - // any of the denomination keys are valid without overlap. - end_date: Timestamp; +1. the **melting** of the old coin, together with ``kappa`` batches + of blinded planchets candidates, +2. the **reveal** of ``kappa-1`` secrets to prove the proper construction + of the (revealed) batches of blinded planchets candidates. - // Signature of `TALER_MasterWireFeePS` with - // purpose ``TALER_SIGNATURE_MASTER_WIRE_FEES``. - sig: EddsaSignature; - } - .. ts:def:: ExchangePartnerListEntry +^^^^ +Melt +^^^^ - interface ExchangePartnerListEntry { - // Base URL of the partner exchange. - partner_base_url: string; +.. _melt: +.. include:: exchange/post-melt.rst - // Public master key of the partner exchange. - partner_master_pub: EddsaPublicKey; +^^^^^^^^^^^ +Reveal-Melt +^^^^^^^^^^^ - // Per exchange-to-exchange transfer (wad) fee. - wad_fee: Amount; +This endpoint is called by the client after a call to `melt`_. +Now the client has to disclose --for each coin-- +all but one of the κ secrets that went into creating the blinded coin's planchets, +the transfer public keys (linking the ownership of the old and new coin), +and the commitment to age restriction, +as proof that the age restriction was set correctly (if applicable). - // Exchange-to-exchange wad (wire) transfer frequency. - wad_frequency: RelativeTime; +.. include:: exchange/post-reveal-melt.rst - // When did this partnership begin (under these conditions)? - start_date: Timestamp; - // How long is this partnership expected to last? - end_date: Timestamp; +.. _deposit-par: - // Signature using the exchange's offline key over - // `TALER_WadPartnerSignaturePS` - // with purpose ``TALER_SIGNATURE_MASTER_PARTNER_DETAILS``. - master_sig: EddsaSignature; - } +------- +Deposit +------- +Deposit operations are requested f.e. by a merchant during a transaction or a +bidder during an auction. +For the deposit operation during purchase, the merchant has to obtain the +deposit permission for a coin from their customer who owns the coin. When +depositing a coin, the merchant is credited an amount specified in the deposit +permission, possibly a fraction of the total coin's value, minus the deposit +fee as specified by the coin's denomination. +For auctions, a bidder performs an deposit operation and provides all relevant +information for the auction policy (such as timeout and public key as bidder) +and can use the ``exchange_sig`` field from the `DepositSuccess` message as a +proof to the seller for the escrow of sufficient fund. ----------------------------------------------- -Management operations authorized by master key ----------------------------------------------- -.. http:get:: /management/keys +.. _deposit: - Get a list of future public keys to be used by the exchange. Only to be - used by the exchange's offline key management team. Not useful for anyone - else (but also not secret, so access is public). +.. include:: exchange/post-batch-deposit.rst - **Response:** - :http:statuscode:`200 OK`: - The exchange responds with a `FutureKeysResponse` object. This request should - virtually always be successful. - **Details:** +------ +Recoup +------ - .. ts:def:: FutureKeysResponse +The purpose of this API is to allow coins to be cashed back in, +in certain exceptional situations. +This API is only used if the exchange is either about to go out of +business or has had its private signing keys compromised (so in +either case, the protocol is only used in **abnormal** +situations). In the above cases, the exchange signals to the +wallets that the emergency cash back protocol has been activated +by putting the affected denomination keys into the cash-back +part of the ``/keys`` response. If and only if this has happened, +coins that were signed with those denomination keys can be cashed +in using this API. - interface FutureKeysResponse { +For a recoup, a coin has to provide the necessary information to +identify the original transaction (either a withdraw or a refresh) it +became minted, and proof ownership of the coin itself. - // Future denominations to be offered by this exchange - // (only those lacking a master signature). - future_denoms: FutureDenom[]; - // The exchange's future signing keys (only those lacking a master signature). - future_signkeys: FutureSignKey[]; +.. include:: exchange/post-recoup-withdraw.rst - // Master public key expected by this exchange (provided so that the - // offline signing tool can check that it has the right key). - master_pub: EddsaPublicKey; - // Public key of the denomination security module. - denom_secmod_public_key: EddsaPublicKey; +.. include:: exchange/post-recoup-refresh.rst - // Public key of the signkey security module. - signkey_secmod_public_key: EddsaPublicKey; - } - .. ts:def:: FutureDenom +.. _exchange_refund: - interface FutureDenom { - // Name in the configuration file that defines this denomination. - section_name: string; +------- +Refunds +------- - // How much are coins of this denomination worth? - value: Amount; +.. include:: exchange/post-coins-COIN_PUB-refund.rst - // When does the denomination key become valid? - stamp_start: Timestamp; - // When is it no longer possible to withdraw coins - // of this denomination? - stamp_expire_withdraw: Timestamp; +.. _reserve-history: - // When is it no longer possible to deposit coins - // of this denomination? - stamp_expire_deposit: Timestamp; +--------------- +Reserve History +--------------- - // Timestamp indicating by when legal disputes relating to these coins must - // be settled, as the exchange will afterwards destroy its evidence relating to - // transactions involving this coin. - stamp_expire_legal: Timestamp; +.. include:: exchange/get-reserves-RESERVE_PUB-history.rst - // Public key for the denomination. - denom_pub: DenominationKey; - // Fee charged by the exchange for withdrawing a coin of this denomination. - fee_withdraw: Amount; - // Fee charged by the exchange for depositing a coin of this denomination. - fee_deposit: Amount; +.. _coin-history: - // Fee charged by the exchange for refreshing a coin of this denomination. - fee_refresh: Amount; +------------ +Coin History +------------ - // Fee charged by the exchange for refunding a coin of this denomination. - fee_refund: Amount; +.. include:: exchange/get-coins-COIN_PUB-history.rst - // Signature by the denomination security module - // over `TALER_DenominationKeyAnnouncementPS` - // for this denomination with purpose - // ``TALER_SIGNATURE_SM_DENOMINATION_KEY``. - denom_secmod_sig: EddsaSignature; +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 +believe it. Usually, the wallet knows the transaction history of each coin +and thus has no need to inquire. - } +**Request:** - .. ts:def:: DenominationKey +The GET request should come with the following HTTP headers: - type DenominationKey = - | RsaDenominationKey - | CSDenominationKey; +*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. - .. ts:def:: RsaDenominationKey +*Taler-Coin-History-Signature*: + The client MUST provide Base-32 encoded EdDSA signature over a + ``TALER_SIGNATURE_COIN_HISTORY_REQUEST`` made with the respective + ``$COIN_PRIV``, affirming desire to download the current coin + transaction history. - interface RsaDenominationKey { - cipher: "RSA"; +: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. - // 32-bit age mask. - age_mask: Integer; +**Response:** - // RSA public key - rsa_pub: RsaPublicKey; - } +: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. - .. ts:def:: CSDenominationKey +**Details:** - interface CSDenominationKey { - cipher: "CS"; +.. ts:def:: CoinHistoryResponse - // 32-bit age mask. - age_mask: Integer; - - // Public key of the denomination. - cs_pub: Cs25519Point; - - } - - .. ts:def:: FutureSignKey - - interface FutureSignKey { - // The actual exchange's EdDSA signing public key. - key: EddsaPublicKey; - - // Initial validity date for the signing key. - stamp_start: Timestamp; - - // Date when the exchange will stop using the signing key, allowed to overlap - // slightly with the next signing key's validity to allow for clock skew. - stamp_expire: Timestamp; - - // Date when all signatures made by the signing key expire and should - // henceforth no longer be considered valid in legal disputes. - stamp_end: Timestamp; - - // Signature over `TALER_SigningKeyAnnouncementPS` - // for this signing key by the signkey security - // module using purpose ``TALER_SIGNATURE_SM_SIGNING_KEY``. - signkey_secmod_sig: EddsaSignature; - } - - -.. http:post:: /management/keys - - Provide master signatures for future public keys to be used by the exchange. - Only to be used by the exchange's offline key management team. Not useful - for anyone else. - - **Request:** - - The request body must be a `MasterSignatures` object. - - **Response:** - - :http:statuscode:`204 No content`: - The request was successfully processed. - :http:statuscode:`403 Forbidden`: - A provided signature is invalid. - :http:statuscode:`404 Not found`: - One of the keys for which a signature was provided is unknown to the exchange. - - **Details:** - - .. ts:def:: MasterSignatures - - interface MasterSignatures { - - // Provided master signatures for future denomination keys. - denom_sigs: DenomSignature[]; - - // Provided master signatures for future online signing keys. - signkey_sigs: SignKeySignature[]; - - } - - .. ts:def:: DenomSignature - - interface DenomSignature { - - // Hash of the public key of the denomination. - h_denom_pub: HashCode; - - // Signature over `TALER_DenominationKeyValidityPS`. - // Must have purpose ``TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY`` - master_sig: EddsaSignature; - - } - - .. ts:def:: SignKeySignature - - interface SignKeySignature { - // The actual exchange's EdDSA signing public key. - key: EddsaPublicKey; - - // Signature by the exchange master key over - // `TALER_ExchangeSigningKeyValidityPS`. - // Must have purpose ``TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY``. - master_sig: EddsaSignature; - - } - - -.. http:post:: /management/denominations/$H_DENOM_PUB/revoke - - Revoke denomination key, preventing further use by the exchange. - Only to be used by the exchange's offline key management team. Not useful - for anyone else. - - **Request:** - - The request body must be a `DenomRevocationSignature` object. - - **Response:** - - :http:statuscode:`204 No content`: - The request was successfully processed. - :http:statuscode:`403 Forbidden`: - The provided signature is invalid. - - **Details:** - - .. ts:def:: DenomRevocationSignature - - interface DenomRevocationSignature { - - // Signature by the exchange master key over a - // `TALER_MasterDenominationKeyRevocationPS`. - // Must have purpose ``TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED``. - master_sig: EddsaSignature; - - } - -.. http:post:: /management/signkeys/$EXCHANGE_PUB/revoke - - Revoke exchange online signing key, preventing further use by the exchange. - Only to be used by the exchange's offline key management team. Not useful - for anyone else. - - **Request:** - - The request body must be a `SignkeyRevocationSignature` object. - - **Response:** - - :http:statuscode:`204 No content`: - The request was successfully processed. - :http:statuscode:`403 Forbidden`: - The provided signature is invalid. - - **Details:** - - .. ts:def:: SignkeyRevocationSignature - - interface SignkeyRevocationSignature { - - // Signature by the exchange master key over a - // `TALER_MasterSigningKeyRevocationPS`. - // Must have purpose ``TALER_SIGNATURE_MASTER_SIGN_KEY_REVOKED``. - master_sig: EddsaSignature; - - } - - -.. http:post:: /management/auditors - - This request will be used to enable an auditor. - - **Request:** - - The request must be a `AuditorSetupMessage`. - - **Response:** - - :http:statuscode:`204 No content`: - The auditor was successfully enabled. - :http:statuscode:`403 Forbidden`: - The master signature is invalid. - :http:statuscode:`409 Conflict`: - The exchange has a more recent request related to this auditor key (replay detected). - - **Details:** - - .. ts:def:: AuditorSetupMessage - - interface AuditorSetupMessage { - - // Base URL of the auditor. - auditor_url: string; - - // Human-readable name of the auditor. - auditor_name: string; - - // The auditor's EdDSA signing public key. - auditor_pub: EddsaPublicKey; - - // Signature by the exchange master ke yover a - // `TALER_MasterAddAuditorPS`. - // Must have purpose ``TALER_SIGNATURE_MASTER_ADD_AUDITOR``. - master_sig: EddsaSignature; - - // When does the auditor become active? - // Should be the time when the signature was created, - // using the (monotonic!) local time of the system - // with the offline master public key. Note that - // even if the time is in the future, the auditor will - // become active immediately! Used ONLY to detect replay attacks. - validity_start: Timestamp; - - } - -.. http:post:: /management/auditors/$AUDITOR_PUB/disable - - This request will be used to disable the use of the given auditor. - We use POST instead of DELETE because the exchange will retain state - about the auditor (specifically the end date) to prevent replay - attacks abusing the `AuditorSetupMessage`. Also, DELETE would not - support a body, which is needed to provide the signature authorizing - the operation. - - **Request:** - - The request must be a `AuditorTeardownMessage`. - - **Response** - - :http:statuscode:`204 No content`: - The auditor has successfully disabled the auditor. The body is empty. - :http:statuscode:`403 Forbidden`: - The signature is invalid. - :http:statuscode:`404 Not found`: - The auditor is unknown to the exchange. - :http:statuscode:`409 Conflict`: - The exchange has a more recent request related to this auditor key (replay detected). - - **Details:** - - .. ts:def:: AuditorTeardownMessage - - interface AuditorTeardownMessage { - - // Signature by the exchange master key over a - // `TALER_MasterDelAuditorPS`. - // Must have purpose ``TALER_SIGNATURE_MASTER_AUDITOR_DEL``. - master_sig: EddsaSignature; - - // When does the auditor become inactive? - // Should be the time when the signature was created, - // using the (monotonic!) local time of the system - // with the offline master public key. Note that - // even if the time is in the future, the auditor will - // become inactive immediately! Used ONLY to detect replay attacks. - validity_end: Timestamp; - - } - - -.. http:post:: /management/wire-fee - - This request is used to configure wire fees. - - **Request:** - - The request must be a `WireFeeSetupMessage`. - - **Response:** - - :http:statuscode:`204 No content`: - The wire fee was successfully configured. - :http:statuscode:`403 Forbidden`: - The master signature is invalid. - :http:statuscode:`409 Conflict`: - The exchange has a conflicting wire fee already set up. - - **Details:** - - .. ts:def:: WireFeeSetupMessage - - interface WireFeeSetupMessage { - - // Wire method the fee applies to. - wire_method: string; - - // Signature using the exchange's offline key - // with purpose ``TALER_SIGNATURE_MASTER_WIRE_FEES``. - master_sig_wire: EddsaSignature; - - // When does the wire fee validity period start? - fee_start: Timestamp; - - // When does the wire fee validity period end (exclusive). - fee_end: Timestamp; - - // Closing fee to charge during that time period for this wire method. - closing_fee: Amount; - - // Wire fee to charge during that time period for this wire method. - wire_fee: Amount; - - } - -.. http:post:: /management/global-fees - - Provides global fee configuration for a timeframe. - - **Request:** - - The request must be a `GlobalFees` message. - - **Response:** - - :http:statuscode:`204 No content`: - The configuration update has been processed successfully. The body is empty. - :http:statuscode:`403 Forbidden`: - The signature is invalid. - :http:statuscode:`409 Conflict`: - The exchange has previously received a conflicting configuration message. - - - -.. http:post:: /management/wire - - This request will be used to enable a wire method (exchange bank account). - - **Request:** - - The request must be a `WireSetupMessage`. - - **Response:** - - :http:statuscode:`204 No content`: - The wire method was successfully enabled. - :http:statuscode:`403 Forbidden`: - The master signature is invalid. - :http:statuscode:`409 Conflict`: - The exchange has a more recent request related to this wire method (replay detected). - - **Details:** - - .. ts:def:: WireSetupMessage - - interface WireSetupMessage { - - // Full ``payto://`` URL identifying the account and wire method - payto_uri: string; - - // Signature using the exchange's offline key - // over a `TALER_MasterWireDetailsPS` - // with purpose ``TALER_SIGNATURE_MASTER_WIRE_DETAILS``. - master_sig_wire: EddsaSignature; - - // Signature using the exchange's offline key over a - // `TALER_MasterAddWirePS` - // with purpose ``TALER_SIGNATURE_MASTER_WIRE_ADD``. - master_sig_add: EddsaSignature; - - // When does the wire method become active? - // Should be the time when the signature was created, - // using the (monotonic!) local time of the system - // with the offline master public key. Note that - // even if the time is in the future, the wire method will - // become active immediately! Used ONLY to detect replay attacks. - validity_start: Timestamp; - - // Display label wallets should use to show this - // bank account. - // @since protocol **v19**. - bank_label?: string; - - // *Signed* integer with the display priority for - // this bank account. - // @since protocol **v19**. - priority?: Integer; - - } - -.. http:post:: /management/wire/disable - - This request will be used to disable the use of the given wire method. - We use POST instead of DELETE because the exchange will retain state - about the wire method (specifically the end date) to prevent replay - attacks abusing the `WireSetupMessage`. Also, DELETE would not - support a body, which is needed to provide the signature authorizing - the operation. - - **Request:** - - The request must be a `WireTeardownMessage`. - - **Response:** - - :http:statuscode:`204 No content`: - The auditor has successfully disabled the wire method. The body is empty. - :http:statuscode:`403 Forbidden`: - The signature is invalid. - :http:statuscode:`404 Not found`: - The wire method is unknown to the exchange. - :http:statuscode:`409 Conflict`: - The exchange has a more recent request related to this wire method (replay detected). - - **Details:** - - .. ts:def:: WireTeardownMessage - - interface WireTeardownMessage { - - // Full ``payto://`` URL identifying the account and wire method - payto_uri: string; - - // Signature using the exchange's offline key over a - // `TALER_MasterDelWirePS`. - // with purpose ``TALER_SIGNATURE_MASTER_WIRE_DEL``. - master_sig_del: EddsaSignature; - - // Should be the time when the signature was created, - // using the (monotonic!) local time of the system - // with the offline master public key. Note that - // even if the time is in the future, the wire method will - // become inactive immediately! Used ONLY to detect replay attacks. - validity_end: Timestamp; - - } - - -.. http:post:: /management/drain - - This request is used to drain profits from the - exchange's escrow account to another regular - bank account of the exchange. The actual drain - requires running the ``taler-exchange-drain`` tool. - - **Request:** - - The request must be a `DrainProfitsMessage`. - - **Response:** - - :http:statuscode:`204 No content`: - The profit drain was scheduled. - :http:statuscode:`403 Forbidden`: - The master signature is invalid. - - **Details:** - - .. ts:def:: DrainProfitsMessage - - interface DrainProfitsMessage { - - // Configuration section of the account to debit. - debit_account_section: string; - - // Full payto URI of the account to credit. - credit_payto_uri: string; - - // Wire transfer identifier to use. - wtid: Base32; - - // Signature by the exchange master key over a - // `TALER_MasterDrainProfitPS`. - // Must have purpose ``TALER_SIGNATURE_MASTER_DRAIN_PROFITS``. - master_sig: EddsaSignature; - - // When was the message created. - date: Timestamp; - - // Amount to be drained. - amount: Amount; - - } - - -.. http:post:: /management/aml-officers - - Update settings for an AML Officer status. - - **Request:** - - The request must be an `AmlOfficerSetup` message. - - **Response:** - - :http:statuscode:`204 No content`: - The officer settings have been updated successfully. - :http:statuscode:`403 Forbidden`: - The signature is invalid. - :http:statuscode:`409 Conflict`: - The exchange has previously received a conflicting configuration message. - - **Details:** - - .. ts:def:: AmlOfficerSetup - - interface AmlOfficerSetup { - - // Public key of the AML officer - officer_pub: EddsaPublicKey; - - // Legal full name of the AML officer - officer_name: string; - - // Is the account active? - is_active: boolean; - - // Is the account read-only? - read_only: boolean; - - // Signature by the exchange master key over a - // `TALER_MasterAmlOfficerStatusPS`. - // Must have purpose ``TALER_SIGNATURE_MASTER_AML_KEY``. - master_sig: EddsaSignature; - - // When will the change take effect? - change_date: Timestamp; - - } - - - .. http:post:: /management/partners - - Enables a partner exchange for wad transfers. - - **Request:** - - The request must be an `ExchangePartnerSetupRequest` message. - - **Response:** - - :http:statuscode:`204 No content`: - The partner has been added successfully. - :http:statuscode:`403 Forbidden`: - The signature is invalid. - :http:statuscode:`409 Conflict`: - The exchange has previously received a conflicting configuration message. - - **Details:** - - .. ts:def:: ExchangePartnerSetupRequest - - interface ExchangePartnerSetupRequest { - - // Base URL of the partner exchange - partner_base_url: string; - - // Master (offline) public key of the partner exchange. - partner_pub: EddsaPublicKey; - - // How frequently will wad transfers be made - wad_frequency: RelativeTime; - - // Signature by the exchange master key over a - // `TALER_PartnerConfigurationPS`. - // Must have purpose ``TALER_SIGNATURE_MASTER_PARTNER_DETAILS``. - master_sig: EddsaSignature; - - // When will the partner relationship start (inclusive). - start_date: Timestamp; - - // When will the partner relationship end (exclusive). - end_date: Timestamp; - - // Wad fee to be charged (to customers). - wad_fee: Amount; - - } - ---------------- -Auditor actions ---------------- - -.. _auditor_action: - -This part of the API is for the use by auditors interacting with the exchange. - - -.. http:post:: /auditors/$AUDITOR_PUB/$H_DENOM_PUB - - This is used to add an auditor signature to the ``/keys`` response. It - affirms to wallets and merchants that this auditor is indeed auditing - the coins issued by the respective denomination. There is no "delete" - operation for this, as auditors can only stop auditing a denomination - when it expires. - - **Request:** - - The request must be a `AuditorSignatureAddMessage`. - - **Response:** - - :http:statuscode:`204 No content`: - The backend has successfully stored the auditor signature. - :http:statuscode:`403 Forbidden`: - The auditor signature is invalid. - :http:statuscode:`404 Not found`: - The denomination key for which the auditor is providing a signature is unknown. - The response will be a `DenominationUnknownMessage`. - :http:statuscode:`410 Gone`: - This auditor is no longer supported by the exchange. - :http:statuscode:`412 Precondition failed`: - This auditor is not yet known to the exchange. - - **Details:** - - .. ts:def:: DenominationUnknownMessage - - interface DenominationUnknownMessage { - - // Taler error code - // ``TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN`` - code: Integer; - - // Signature by the exchange over a - // `TALER_DenominationUnknownAffirmationPS`. - // Must have purpose ``TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_UNKNOWN``. - exchange_sig: EddsaSignature; - - // Public key of the exchange used to create - // the 'exchange_sig. - exchange_pub: EddsaPublicKey; - - // Hash of the denomination public key that is unknown. - h_denom_pub: HashCode; - - // When was the signature created. - timestamp: Timestamp; - - } - - .. ts:def:: AuditorSignatureAddMessage - - interface AuditorSignatureAddMessage { - - // Signature by the auditor over a - // `TALER_ExchangeKeyValidityPS`. - // Must have purpose ``TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS``. - auditor_sig: EddsaSignature; - - } - - ----------------- -Blinding Prepare ----------------- - -Certain denomination cipher types, such as Clause-Schnorr, require input values -from the exchange-side as preparation for the blinding of the coins. See the -Bachelor thesis of Gian Demarmels and Lucien Heuzeveldt, -`Adding Schnorr’s Blind Signature in Taler <https://www.taler.net/papers/cs-thesis.pdf>`_, -for details. - -.. http:post:: /blinding-prepare - - Obtain exchange-side input values in preparation for a - blinding step of multiple coins for certain denomination - cipher types, specifically at this point for Clause-Schnorr - blind signatures. - - **Request:** - - The request body must be a `BlindingPrepareRequest` object. - - **Response:** - - :http:statuscode:`200 OK`: - The request was successful, and the response is a - `BlindingPrepareResponse`. Note that repeating exactly the same request - will again yield the same response (assuming none of the denominations is - expired). - :http:statuscode:`404 Not found`: - A denomination key is not known to the exchange. - The response is a `DenominationUnknownMessage`. - :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 `DenominationGoneMessage`. - Clients must evaluate the error code provided to understand - which of the cases this is and handle it accordingly. - - **Details:** - - - .. ts:def:: BlindingPrepareRequest - - type BlindingPrepareRequest = BlindingPrepareRequestCS; - - - .. ts:def:: BlindingPrepareRequestCS - - interface BlindingPrepareRequestCS { - // Cipher type - cipher: "CS"; - - // The type of operation this blinding is for. - operation: "withdraw" | "melt"; - - // Master seed for the Clause-Schnorr R-value creation. - // MUST not have been used in any prior request of this type. - seed: BlindingMasterSeed; - - // Array of denominations and coin offsets for - // each of the fresh coins with a CS-cipher - // denomination. - // The coin_offset values MUST be strongly increasing. - nks: BlindingInputParameter[]; - - } - - .. ts:def:: BlindingInputParameter - - interface BlindingInputParameter { - - // Offset of this coin in the list of - // fresh coins. May not match the array offset - // as the fresh coins may include non-CS - // denominations as well. - coin_offset: Integer; - - // Hash of the public key of the denomination the - // request relates to. Must be a CS denomination type. - denom_pub_hash: HashCode; - } - - - - .. ts:def:: BlindingPrepareResponse - - type BlindingPrepareResponse = BlindingPrepareResponseCS; - - - .. ts:def:: BlindingPrepareResponseCS - - interface BlindingPrepareResponseCS { - cipher: "CS"; - - // Array of pairs of CS values, one pair per input - r_pubs: CSRPublicPair[]; - } - - - .. ts:def:: CSRPublicPair - - // Pair of points (of type `CSRPublic`) on the curve Curve25519, - // one of which is randomly selected in the Clause-Schnorr - // signature scheme. - type CSRPublicPair = [CSRPublic, CSRPublic]; - - - -.. _exchange-withdrawal: - ----------- -Withdrawal ----------- - -This API is used by the wallet to obtain digital coins. - -When transferring money to the exchange such as via SEPA transfers, the exchange creates -a *reserve*, which keeps the money from the customer. The customer must -specify an EdDSA reserve public key as part of the transfer, and can then -withdraw digital coins using the corresponding private key. All incoming and -outgoing transactions are recorded under the corresponding public key by the -exchange. - -.. note:: - - Eventually the exchange will need to advertise a policy for how long it will - keep transaction histories for inactive or even fully drained reserves. We - will therefore need some additional handler similar to ``/keys`` to - advertise those terms of service. - - -.. http:get:: /reserves/$RESERVE_PUB - - Request summary information about a reserve. - - **Request:** - - :query timeout_ms=MILLISECONDS: *Optional.* If specified, the exchange will wait up to MILLISECONDS for incoming funds before returning a 404 if the reserve does not yet exist. - - **Response:** - - :http:statuscode:`200 OK`: - The exchange responds with a `ReserveSummary` object; the reserve was known to the exchange. - :http:statuscode:`404 Not found`: - The reserve key does not belong to a reserve known to the exchange. - - **Details:** - - .. ts:def:: ReserveSummary - - interface ReserveSummary { - // Balance left in the reserve. - balance: Amount; - - // Full payto URI of the bank account that - // (most recently) funded this reserve. - // Useful as a hint for deposit operations for wallets. - // Missing if this reserve was only filled via P2P merges. - // @since protocol **v23**. - last_origin?: string; - - // The expiration date of the reserve. - // Any residual value of in the reserve after this date - // will be returned to the ``last_origin``, if present. - // @since protocol **vRECOUP**. - reserve_expiration: Timestamp; - - // If set, proof of age restriction is required and age restriction needs - // to be set for each coin to this value during the withdrawal from this reserve. - // The client then MUST use a denomination with support for age restriction enabled - // for the withdrawal and MUST set an appropriate value for ``max_age`` - // The value represents a valid age group from the list of permissible - // age groups as defined by the exchange's output to /keys. - maximum_age_group?: Integer; - } - - - -.. _withdraw: -.. http:post:: /withdraw - - 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. - - **Request:** - - The request body must be a `WithdrawRequest` object. - - **Response:** - - :http:statuscode:`200 OK`: - The request was successful, and ``max_age`` was not set. - 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 coins signature to disk, the coins are not lost. - :http:statuscode:`201 Created`: - The request was successful, and ``max_age`` was set. - The response is a `AgeWithdrawResponse`. The client is expected - to call ``/reveal-withdraw`` next. - 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 coins signature to disk, the coins are not lost. - :http:statuscode:`403 Forbidden`: - A signature is invalid. This is usually the reserve signature. - This response comes with a standard `ErrorDetail` response with - a code of ``TALER_EC_EXCHANGE_WITHDRAW_RESERVE_SIGNATURE_INVALID``. - :http:statuscode:`404 Not found`: - One of the following reasons occured: - - 1. The reserve is unknown. The response comes with a standard - `ErrorDetail` response with error-code - ``TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN``. - If the reserve is unknown, the wallet should not report a - hard error, but instead long-poll for the reserve status to wait - for the wire transfer to complete. - Once the wire transfer has arrived, - the wallet should repeat the exact same request later again, - possibly using exactly the same blinded coins. - 2. A denomination keyis not known to the exchange. - The response comes with a standard - `ErrorDetail` response with error-code - ``TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN``. - This suggests the wallet has outdated ``/keys`` and - should fetch the latest ``/keys``. - :http:statuscode:`409 Conflict`: - One of the following reasons occured: - - 1. The balance of the reserve is not sufficient to withdraw the - coins of the indicated denominations. - The response is `WithdrawError` object with an error code of - ``TALER_EC_EXCHANGE_WITHDRAW_INSUFFICIENT_FUNDS``. An operation - withdrawing less money should succeed. - 2. The reserve has a birthday set and requires the request to - provide a ``max_age`` value. - The response comes with a standard `ErrorDetail` response with - an error-code of - ``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 a call to ``/withdraw``, this time - with ``max_age`` set accordingly and ``coin_evs`` being an array - of ``n*kappa`` elements of type `CoinEnvelope`. - 3. 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 ``/withdraw``. - 4. The request uses a nonce value that was previously seen by - the exchange for a different request. As nonces must be unique, - the request is rejected. This can only happen with some cipher - types that use nonces. - :http:statuscode:`410 Gone`: - A requested denomination key is no longer valid. There are two cases: - - 1. The denomination key is past its expiration. - The response is a `DenominationGoneMessage` with a code of - ``TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED``. - 2. The denominatoin key was revoked. The response is a - plain `ErrorDetail` with a code of ``TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED``. - :http:statuscode:`412 Precondition Failed`: - A requested denomination key is not yet valid. - It is before the validity start time. - The response is a `DenominationGoneMessage` with - ``TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE``. - A common case might be a difference in the current time between - wallet and exchange. The wallet could probably just wait a bit and - retry. Checking the server's ``Date:`` header should allow the - wallet to figure out how long to wait. Alternatively, the wallet - could try with an the previous denomination key generation. - Note: this is a bit of an abuse of the HTTP status code. - :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 `LegitimizationNeededResponse` 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. - :http:statuscode:`501 Not implemented`: - The client has provided a cipher that is not supported. - :http:statuscode:`502 Bad gateway`: - This indicates the exchange could not communicate with an - external process. This usually means the exchange could - not talk to one of its secmod helpers. - Here, a standard error message with a code of - ``TALER_EC_EXCHANGE_SIGNKEY_HELPER_UNAVAILABLE`` - is returned. - Wallets should retry the requests (with some delays) at - a later time. - :http:statuscode:`503 Service Unavailable`: - This primarily happens when the exchange currently has no - denomination signing keys at all, for example because the - offline signature did not yet happen. In this case, a standard - error message with a code of - ``TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING`` is returned. - - **Details:** - - -.. ts:def:: WithdrawRequest - - interface WithdrawRequest { - // Cipher that is used for the rerserve's signatures. - // For now, only ed25519 signatures are applicable, - // but this might change in future versions. - cipher: "ED25519"; - - // The reserve's public key, for the the cipher ED25519, - // to verify the signature ``reserve_sig``. - reserve_pub: EddsaPublicKey; - - // Array of ``n`` hash codes of denomination public keys to order. - // 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. - // If ``max_age`` is set, these denominations MUST support - // age restriction as defined in the output to /keys. - denoms_h: HashCode[]; - - // If set, the maximum age to commit to. This implies: - // 1.) it MUST be the same value as the maximum age - // of the reserve. - // 2.) ``coin_evs`` MUST be an array of ``n*kappa`` - // 3.) the denominations in ``denoms_h`` MUST support - // age restriction. - max_age?: Integer; - - // Master seed for the Clause-Schnorr R-value creation. - // MUST match the /blinding-prepare request. - // MUST NOT have been used in any prior withdraw request. - // MUST be present if one of the fresh coin's - // denomination is of type Clause-Schnorr. - blinding_seed?: BlindingMasterSeed; - - // Array of blinded coin envelopes of type `CoinEnvelope`. - // If ``max_age`` is not set, MUST be n entries. - // If ``max_age`` is set, MUST be ``n*kappa`` entries, - // arranged in [0..n)..[0..n), with the first n entries - // belonging to kappa=0 etc. - // In case of age restriction, the exchange will - // respond with an index ``gamma``, which is the index - // that shall remain undisclosed during the subsequent - // reveal phase. - // This hash value along with the reserve's public key - // will also be used for recoup operations, if needed. - coin_evs: CoinEnvelope[]; - - // Signature of `TALER_WithdrawRequestPS` created with - // the `reserves's private key <reserve-priv>`. - reserve_sig: EddsaSignature; - } - -.. ts:def:: WithdrawResponse - - interface WithdrawResponse { - // Array of blinded signatures over each ``coin_evs``, - // in the same order as was given in the request. - // The blinded signatures affirm the coin's validity - // after unblinding. - ev_sigs: BlindedDenominationSignature[]; - - } - - -.. ts:def:: AgeWithdrawResponse - - interface AgeWithdrawResponse { - // index of the commitments that the client doesn't - // have to disclose in the subsequent call to - // ``/reveal-withdraw``. - noreveal_index: Integer; - - // Signature of `TALER_WithdrawConfirmationPS` whereby - // the exchange confirms the ``noreveal_index``. - exchange_sig: EddsaSignature; - - // `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:: DenominationGoneMessage - - interface DenominationGoneMessage { - - // Taler error code. Note that beyond - // expiration this message format is also - // used if the key is not yet valid, or - // has been revoked. May be one of - // - ``TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE`` - // - ``TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED`` - // - ``TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED`` - code: Integer; - - // Signature by the exchange over a - // `TALER_DenominationExpiredAffirmationPS`. - // Must have purpose ``TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_EXPIRED``. - exchange_sig: EddsaSignature; - - // Public key of the exchange used to create - // the 'exchange_sig. - exchange_pub: EddsaPublicKey; - - // Hash of the denomination public key that is unknown. - h_denom_pub: HashCode; - - // When was the signature created. - timestamp: Timestamp; - - // What kind of operation was requested that now - // failed? - oper: string; - - } - - -.. ts:def:: WithdrawError - - interface SingleWithdrawError { - // Text describing the error. - hint: string; - - // Detailed error code. - code: Integer; - - // Amount left in the reserve. - balance: Amount; - - } - - - ------------------- - - -.. _reveal-withdraw: - -**Reveal-Withdraw** - -This endpoint is called by the client after a call to `withdraw`_, -*if* the original request had ``max_age`` set and -the response was of type `AgeWithdrawResponse`. -Now the client has to disclose for each coin all but one of the κ secrets -that went into creating the blinded coin's planchets, -including the commitment to age restriction, -and prove that the age restriction was set correctly. - -.. http:post:: /reveal-withdraw - - Reveal previously committed values to the exchange, except for the values - corresponding to the ``noreveal_index`` returned by the ``/withdraw`` step. - - The base URL for ``/reveal-withdraw``-request may differ from the main base URL of - the exchange. Clients SHOULD respect the ``reveal_base_url`` returned for the - coin during melt operations. The exchange MUST return a - 307 or 308 redirection to the correct base URL if the client failed to - respect the ``reveal_base_url`` or if the allocation has changed. - - The request body is a `RevealWithdrawRequest`. - - This endpoint was introduced in this form in protocol **v32**. - - :http:statuscode:`200 OK`: - The coin's' secret material matched the commitment and the original request was well-formed. - The response body is a `RevealResponse`. - :http:statuscode:`404 Not found`: - The provided commitment $RCH is unknown. - :http:statuscode:`409 Conflict`: - There is a problem between the original commitment and the revealed secret data. - The returned information is proof of the mismatch, - and therefore rather verbose, as it includes most of the original /withdraw request, - but of course expected to be primarily used for diagnostics. - - The response body is a `RevealConflictResponse`. - - The following specific error codes can be returned: - - - 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``. - - The computation of the hash of the commitment with provided input does - result in the value of field ``h_commitment``. - Error code: - ``TALER_EC_EXCHANGE_AGE_WITHDRAW_REVEAL_INVALID_HASH`` - - **Details:** - - Request body for a ``reveal-withdraw`` request - contains a JSON object with the following fields: - - .. ts:def:: RevealWithdrawRequest - - interface RevealWithdrawRequest { - // The reserve's public key from the previous call to /withdraw. - reserve_pub: string; - - // This is the h_planchets running hash of all blinded planchets - // from the previous call to /withdraw. - h_planchets: string; - - // Array of ``(kappa - 1)`` disclosed batch secrets, - // from which for each of the n coins in a batch - // their coin master secret is derived, - // from which in turn their private key, - // blinding, nonce (for Clause-Schnorr) and - // age-restriction is calculated. - disclosed_batch_seeds: AgeRestrictedPlanchetSeed[]; - - } - - .. ts:def:: AgeRestrictedPlanchetSeed - - // The master seed material from which for n coins in a batch, - // each 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+1,...,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 AgeRestrictedPlanchetSeed = string; - - .. ts:def:: PublishedAgeRestrictionBaseKey - - // 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 prove that they - // derived all public keys to age groups higher than their allowed maximum - // from this particular value. - const PublishedAgeRestrictionBaseKey = - new Edx25519PublicKey("CH0VKFDZ2GWRWHQBBGEK9MWV5YDQVJ0RXEE0KYT3NMB69F0R96TG"); - - - ----------- -Refreshing ----------- - -Refreshing creates ``n`` new coins from ``m`` old coins, where the sum of -denominations of the new coins must be smaller than the sum of the old coins' -denominations plus melting (refresh) and withdrawal fees charged by the exchange. -The refreshing API can be used by wallets to melt partially spent coins, making -transactions with the freshly exchangeed coins unlinkabe to previous transactions -by anyone except the wallet itself. - -Refreshing is a two step process, consisting of - -1. the **melting** of the old coin, together with ``kappa`` batches - of blinded planchets candidates, -2. the **reveal** of ``kappa-1`` secrets to prove the proper construction - of the (revealed) batches of blinded planchets candidates. - - -^^^^ -Melt -^^^^ - -.. _melt: -.. http:post:: /melt - - "Melts" a coin. Invalidates the coins and prepares for exchanging of fresh - coins. Taler uses a global parameter ``kappa`` for the cut-and-choose - component of the protocol, for which this request is the commitment. Thus, - various arguments are given ``kappa``-times in this step. At present ``kappa`` - is always 3. - - The base URL for ``/melt/``-requests may differ from the main base URL of the - exchange. The exchange MUST return a 307 or 308 redirection to the correct - base URL if this is the case. - - This endpoint was introduced in this form in protocol **v32**. - - :http:statuscode:`200 OK`: - The request was successful. The response body is `MeltResponse` in this case. - :http:statuscode:`403 Forbidden`: - One of the signatures is invalid. - :http:statuscode:`404 Not found`: - The exchange does not recognize the denomination key as belonging to the exchange, - or it has expired. - If the denomination key is unknown, the response will be - a `DenominationUnknownMessage`. - :http:statuscode:`409 Conflict`: - The operation is not allowed as the 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_GENERIC_INSUFFICIENT_FUNDS`` or - ``TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY``). - The response is `MeltForbiddenResponse` in both cases. - :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 - `DenominationGoneMessage`. Clients must evaluate - the error code provided to understand which of the - cases this is and handle it accordingly. - - **Details:** - - .. ts:def:: MeltRequest - - interface MeltRequest { - // The old coin's public key - old_coin_pub: CoinPublicKey; - - // Hash of the denomination public key of the old coin, - // to determine total coin value. - old_denom_pub_h: HashCode; - - // The hash of the age-commitment for the old coin. Only present - // if the denomination has support for age restriction. - old_age_commitment_h?: AgeCommitmentHash; - - // Signature over the old `coin public key <eddsa-coin-pub>` by the denomination. - old_denom_sig: DenominationSignature; - - // Amount of the value of the old coin that should be melted as part of - // this refresh operation, including melting fee. I.e.: - // melting fee of the old coin - // + sum over all values of fresh coins - // + sum over all withdraw fees for the fresh coins - value_with_fee: Amount; - - // @since v27 - // @deprecated **v32** - // Seed from which the nonces for the n*κ coin candidates are derived from. - // - // @since **v32** - // The ``refresh_seed`` is an opaque value to the exchange. - // It is provided by the client and is verified with the ``coin_sig`` below. - // Its purpose is to ensure that the honest owner of the old coin - // can replay a /melt request from data in the coin history, - // provided by the exchange and including this value, in case a wallet - // was restored into a state prior to the refresh operation. - // - // The honest owner of the old coin SHOULD use this value - // and the old coin's private key to derive kappa many - // batch seeds (one for each cut-and-choose candidate) - // like this: - // - // ``bs[] = HKDF(kappa*sizeof(HashCode),`` - // ``"refresh-batch-seeds",`` - // ``old_coin_priv,`` - // ``refresh_seed)`` - // - // These batch seeds (however constructed) are relevant in the - // subsequent reveal step of the cut-and-chose. Each of the - // revealed seeds is expanded to a batch of ``n`` transfer private keys - // via HKDF: - // - // ``tp[k][] = HKDF(n*sizeof(HashCode),`` - // ``"refresh-transfer-private-keys",`` - // ``bs[k])`` - // - // An individual coin's transfer private key at kappa-index k and - // coin index i in the batch is then ``tp[k][i]``. The corresponding - // transfer _public_ keys are given in the field ``transfer_pubs``. - refresh_seed: HashCode; - - // Master seed for the Clause-Schnorr R-value - // creation. Must match the /blinding-prepare request. - // Must not have been used in any prior melt request. - // Must be present if one of the fresh coin's - // denominations is of type Clause-Schnorr. - blinding_seed?: BlindingMasterSeed; - - // Array of ``n`` new hash codes of denomination public keys - // for the new coins to order. - denoms_h: HashCode[]; - - // ``kappa`` arrays of ``n`` entries for blinded coin candidates, - // each matching the respective entries in ``denoms_h``. - coin_evs: CoinEnvelope[kappa][]; - - // @since **v32** - // ``kappa`` arrays of ``n`` entries of transfer public keys each. - // These are ephemeral ECDHE keys that allow the owner of a coin - // to (re-)obtain the derived coins from a refresh operation, f.e. should - // the wallet state be restored from a backup, prior to the refresh operation. - transfer_pubs: EddsaPublicKey[kappa][]; - - // Signature by the `coin <coin-priv>` over `TALER_RefreshMeltCoinAffirmationPS`. - confirm_sig: EddsaSignature; - - } - - For details about the HKDF used to derive the new coin private keys and - the blinding factors from ECDHE between the transfer public keys and - the private key of the melted coin, please refer to the - implementation in ``libtalerutil``. - - .. ts:def:: MeltResponse - - interface MeltResponse { - // Which of the ``kappa`` indices does the client not have to reveal - // by calling the ``/reveal-melt`` endpoint. - noreveal_index: Integer; - - // Signature of `TALER_RefreshMeltConfirmationPS` whereby the exchange - // affirms the successful melt and confirming the ``noreveal_index``. - exchange_sig: EddsaSignature; - - // `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; - - // Base URL to use for operations on the refresh context - // (so the reveal operation). If not given, - // the base URL is the same as the one used for this request. - // Can be used if the base URL for ``/reveal-melt/`` differs from that - // for ``/melt/``, i.e. for load balancing. Clients SHOULD - // respect the reveal_base_url if provided. Any HTTP server - // belonging to an exchange MUST generate a 307 or 308 redirection - // to the correct base URL should a client uses the wrong base - // URL, or if the base URL has changed since the melt. - // - // When melting the same coin twice (technically allowed - // as the response might have been lost on the network), - // the exchange may return different values for the ``reveal_base_url``. - reveal_base_url?: string; - - } - - .. ts:def:: MeltForbiddenResponse - - interface MeltForbiddenResponse { - - // Must be TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS - // or TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY - code: Integer; - - // A string explaining that the user tried to - // double-spend. - hint: string; - - // EdDSA public key of a coin being double-spent. - coin_pub: EddsaPublicKey; - - // Hash of the public key of the denomination of the coin. - h_denom_pub: HashCode; - - } - -^^^^^^^^^^^ -Reveal-Melt -^^^^^^^^^^^ - -This endpoint is called by the client after a call to `melt`_. -Now the client has to disclose --for each coin-- -all but one of the κ secrets that went into creating the blinded coin's planchets, -the transfer public keys (linking the ownership of the old and new coin), -and the commitment to age restriction, -as proof that the age restriction was set correctly (if applicable). - -.. http:post:: /reveal-melt - - Reveal previously committed values to the exchange, except for the values - corresponding to the ``noreveal_index`` returned by the ``/melt`` step. - - The base URL for ``/reveal-melt``-request may differ from the main base URL of - the exchange. Clients SHOULD respect the ``reveal_base_url`` returned for the - coin during melt operations. The exchange MUST return a - 307 or 308 redirection to the correct base URL if the client failed to - respect the ``reveal_base_url`` or if the allocation has changed. - - The request body is a `RevealMeltRequest`. - - This endpoint was introduced in this form in protocol **v32**. - - :http:statuscode:`200 OK`: - The coin's' secret material matched the commitment and the original request was well-formed. - The response body is a `RevealResponse`. - :http:statuscode:`403 Forbidden`: - One of the signatures is invalid. - This response comes with a standard `ErrorDetail` response. - :http:statuscode:`404 Not found`: - The provided commitment is unknown. - :http:statuscode:`409 Conflict`: - There is a problem between the original commitment and the revealed secret data. - The returned information is proof of the mismatch, - and therefore rather verbose, as it includes most of the original /melt request, - but of course expected to be primarily used for diagnostics. - - The response body is a `RevealConflictResponse`. - - **Details:** - - Request body for a ``reveal-melt`` request - contains a JSON object with the following fields: - - .. ts:def:: RevealMeltRequest - - interface RevealMeltRequest { - // The refresh commitment from the ``/melt/`` step, - // see `TALER_RefreshCommitmentP`. - rc: HashCode; - - // @since v27 - // @deprecated **v32** - // The disclosed kappa-1 signatures by the old coin's private key, - // over Hash1a("Refresh", Cp, r, i), where Cp is the melted coin's public key, - // r is the public refresh nonce from the metling step and i runs over the - // _disclosed_ kappa-1 indices. - signatures: CoinSignature[kappa-1]; - - // @since **v32** - // The batch seeds for the transfer private keys to reveal, - // as they were generated for the previous `MeltRequest`. - // That is, assuming an honest client who had generated - // kappa many batch seeds via HKDF like this: - // - // ``bs[] = HKDF(kappa*sizeof(HashCode),`` - // ``"refresh-batch-seeds",`` - // ``old_coin_priv,`` - // ``refresh_seed)``, - // - // this field contains the entries in ``bs[]`` for all the indeces - // *except* the ``noreveal_index``. - batch_seeds: HashCode[kappa-1]; - - // IFF the denomination of the old coin had support for age restriction, - // the client MUST provide the original age commitment, i. e. the - // vector of public keys, or omitted otherwise. - // The size of the vector MUST be the number of age groups as defined by the - // Exchange in the field ``.age_groups`` of the extension ``age_restriction``. - age_commitment?: Edx25519PublicKey[]; - - } - - .. ts:def:: RevealResponse - - type RevealResponse = WithdrawResponse; - - - .. ts:def:: RevealConflictResponse - - interface RevealConflictResponse { - // Text describing the error. - hint: string; - - // Detailed error code. - code: Integer; - - // Commitment as calculated by the exchange from the revealed data. - rc_expected: HashCode; - - } - - -.. _deposit-par: - -------- -Deposit -------- - -Deposit operations are requested f.e. by a merchant during a transaction or a -bidder during an auction. - -For the deposit operation during purchase, the merchant has to obtain the -deposit permission for a coin from their customer who owns the coin. When -depositing a coin, the merchant is credited an amount specified in the deposit -permission, possibly a fraction of the total coin's value, minus the deposit -fee as specified by the coin's denomination. - -For auctions, a bidder performs an deposit operation and provides all relevant -information for the auction policy (such as timeout and public key as bidder) -and can use the ``exchange_sig`` field from the `DepositSuccess` message as a -proof to the seller for the escrow of sufficient fund. - - -.. _deposit: - -.. http:post:: /batch-deposit - - Deposit multiple coins and ask the exchange to transfer the given :ref:`amount` - into the merchant's bank account. This API is used by the merchant to redeem - the digital coins. - - **Request:** - - The request body must be a `BatchDepositRequest` object. - - **Response:** - - :http:statuscode:`200 OK`: - The operation succeeded, the exchange confirms that no double-spending took - place. The response will include a `DepositSuccess` object. - :http:statuscode:`403 Forbidden`: - One of the signatures is invalid. - This response comes with a standard `ErrorDetail` response. - :http:statuscode:`404 Not found`: - Either one of the denomination keys is not recognized (expired or invalid), - or the wire type is not recognized. - If a denomination key is unknown, the response will be - a `DenominationUnknownMessage`. - :http:statuscode:`409 Conflict`: - The deposit operation has either failed because a coin has insufficient - residual value, or because the same public key of a coin has been - previously used with a different denomination. - Which case it is can be decided by looking at the error code: - - 1. ``TALER_EC_EXCHANGE_DEPOSIT_CONFLICTING_CONTRACT`` (same coin used in different ways), - 2. ``TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS`` (balance insufficient), - 3. ``TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY`` (same coin public key, but different denomination). - 4. ``TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_AGE_HASH`` (same coin public key, but different age commitment). - - The request should not be repeated again with this coin. Instead, the client - can get from the exchange via the ``/coin/$COIN_PUB/history`` endpoint the record - of the transactions known for this coin's public key. - :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 - `DenominationGoneMessage`. 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 merchant has not yet passed the KYC checks. - The client must pass KYC checks before proceeding with the deposit. - The response will be an `LegitimizationNeededResponse` object. - @since protocol **v21**. - - **Details:** - - .. ts:def:: BatchDepositRequest - - interface BatchDepositRequest { - - // The merchant's account details as a full payto URI. - merchant_payto_uri: string; - - // The salt is used to hide the ``payto_uri`` from customers - // when computing the ``h_wire`` of the merchant. - wire_salt: WireSalt; - - // SHA-512 hash of the contract of the merchant with the customer. Further - // details are never disclosed to the exchange. - h_contract_terms: HashCode; - - // Merchant's signature over the h_contract_terms. - // @since protocol **v22** - merchant_sig: EddsaSignature; - - // The list of coins that are going to be deposited with this Request. - coins: BatchDepositRequestCoin[]; - - // Timestamp when the contract was finalized. - timestamp: Timestamp; - - // Indicative time by which the exchange undertakes to transfer the funds to - // the merchant, in case of successful payment. A wire transfer deadline of 'never' - // is not allowed. - wire_transfer_deadline: Timestamp; - - // EdDSA `public key of the merchant <merchant-pub>`, so that the client can identify the - // merchant for refund requests. - merchant_pub: EddsaPublicKey; - - // Date until which the merchant can issue a refund to the customer via the - // exchange, to be omitted if refunds are not allowed. - // - // THIS FIELD WILL BE DEPRICATED, once the refund mechanism becomes a - // policy via extension. - refund_deadline?: Timestamp; - - // CAVEAT: THIS IS WORK IN PROGRESS - // (Optional) policy for the batch-deposit. - // This might be a refund, auction or escrow policy. - policy?: DepositPolicy; - } - - .. ts:def:: BatchDepositRequestCoin - - interface BatchDepositRequestCoin { - // EdDSA public key of the coin being deposited. - coin_pub: EddsaPublicKey; - - // 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: DenominationSignature; - - // Amount to be deposited, can be a fraction of the - // coin's total value. - contribution: Amount; - - // Signature over `TALER_DepositRequestPS`, made by the customer with the - // `coin's private key <coin-priv>`. - coin_sig: EddsaSignature; - } - - .. ts:def:: DenominationSignature - - type DenominationSignature = DenomCipher & ( - | RsaDenominationSignature - | CSDenominationSignature - ); - - .. ts:def:: RsaDenominationSignature - - interface RsaDenominationSignature extends DenomCipher { - cipher: "RSA"; - - // RSA signature - rsa_signature: RsaSignature; - } - - .. ts:def:: CSDenominationSignature - - interface CSDenominationSignature extends DenomCipher { - cipher: "CS"; - - // R value component of the signature. - cs_signature_r: Cs25519Point; - - // s value component of the signature. - cs_signature_s: Cs25519Scalar; - - } - - .. ts:def:: DepositPolicy - - type DepositPolicy = - | PolicyMerchantRefund - | PolicyBrandtVickreyAuction - | PolicyEscrowedPayment; - - .. ts:def:: PolicyMerchantRefund - - // CAVEAT: THIS IS STILL WORK IN PROGRESS. - // This policy is optional and might not be supported by the exchange. - // If it does, the exchange MUST show support for this policy in the - // ``extensions`` field in the response to ``/keys``. - interface PolicyMerchantRefund { - type: "merchant_refund"; - - // EdDSA `public key of the merchant <merchant-pub>`, so that the client - // can identify the merchant for refund requests. - merchant_pub: EddsaPublicKey; - - // Date until which the merchant can issue a refund to the customer via - // the ``/extensions/policy_refund``-endpoint of the exchange. - deadline: Timestamp; - } - - .. ts:def:: PolicyBrandtVickreyAuction - - // CAVEAT: THIS IS STILL WORK IN PROGRESS. - // This policy is optional and might not be supported by the exchange. - // If it does, the exchange MUST show support for this policy in the - // ``extensions`` field in the response to ``/keys``. - interface PolicyBrandtVickreyAuction { - type: "brandt_vickrey_auction"; - - // Public key of this bidder. - // - // The bidder uses this key to sign the auction information and - // the messages it sends to the seller during the auction. - bidder_pub: EddsaPublicKey; - - // Hash of the auction terms - // - // The hash should be taken over a normalized JSON object of type - // `BrandtVickreyAuction`. - h_auction: HashCode; - - // The amount that this bidder commits to for this auction - // - // This amount can be larger than the contribution of a single coin. - // The bidder can increase funding of this auction policy by using - // sufficiently many coins during the deposit operation (single or batch) - // with the same policy. - commitment: Amount; - - // Date until the auction must have been successfully executed and - // a valid transcript provided to the - // ``/extensions/policy_brandt_vickrey_auction``-endpoint of the - // exchange. - // - // [If the auction has not been executed by then] OR [has been executed - // before then, but this bidder did not win], the coin's value doesn't - // change and the owner can refresh the coin. - // - // If this bidder won the auction, the winning price/amount from the - // outcome will be substracted from the coin and transfered to the - // merchant's ``payout_uri`` from the deposit request (minus a potential - // auction fee). For any remaining value, the bidder can refresh the - // coin to retrieve change. - deadline: Timestamp; - } - - .. ts:def:: BrandtVickreyAuction - - // CAVEAT: THIS IS STILL WORK IN PROGRESS. - // This structure defines an auction of Brandt-Vickory kind. - // It is used for the `PolicyBrandtVickreyAuction`. - interface BrandtVickreyAuction { - // Start date of the auction - time_start: Timestamp; - - // Maximum duration per round. There are four rounds in an auction of - // Brandt-Vickrey kind. - time_round: RelativeTime; - - // This integer m refers to the (m+1)-type of the Brandt-Vickrey-auction. - // - Type 0 refers to an auction with one highest-price winner, - // - Type 1 refers to an auction with one winner, paying the second - // highest price, - // - Type 2 refers to an auction with two winners, paying - // the third-highest price, - // - etc. - auction_type: Integer; - - // The vector of prices for the Brandt-Vickrey auction. The values MUST - // be in strictly increasing order. - prices: Amount[]; - - // The type of outcome of the auction. - // In case the auction is declared public, each bidder can calculate the - // winning price. This field is not relevant for the replay of a - // transcript, as the transcript must be provided by the seller who sees - // the winner(s) and winning price of the auction. - outcome_public: boolean; - - // The public key of the seller. - pubkey: EddsaPublicKey; - - // The seller's account details as a full payto URI. - payto_uri: string; - } - - - .. ts:def:: PolicyEscrowedPayment - - // CAVEAT: THIS IS STILL WORK IN PROGRESS - // This policy is optional and might not be supported by the exchange. - // If it does, the exchange MUST show support for this policy in the - // ``extensions`` field in the response to ``/keys``. - interface PolicyEscrowedPayment { - type: "escrowed_payment"; - - // Public key of this trustor, the owner of the coins. - // - // To claim the deposit, the merchant must provide the valid signature - // of the ``h_contract_terms`` field from the deposit, signed by _this_ - // key, to the ``/extensions/policy_escrow``-endpoint of the exchange, - // after the date specified in ``not_before`` and before the date - // specified in ``not_after``. - trustor_pub: EddsaPublicKey; - - // Latest date by which the deposit must be claimed. If the deposit - // has not been claimed by that date, the deposited coins can be - // refreshed by the (still) owner. - deadline: Timestamp; - } - - The deposit operation succeeds if the coin is valid for making a deposit and - has enough residual value that has not already been deposited or melted. - - .. ts:def:: DepositSuccess - - interface DepositSuccess { - // Optional base URL of the exchange for looking up wire transfers - // associated with this transaction. If not given, - // the base URL is the same as the one used for this request. - // Can be used if the base URL for ``/transactions/`` differs from that - // for ``/coins/``, i.e. for load balancing. Clients SHOULD - // respect the ``transaction_base_url`` if provided. Any HTTP server - // belonging to an exchange MUST generate a 307 or 308 redirection - // to the correct base URL should a client uses the wrong base - // URL, or if the base URL has changed since the deposit. - transaction_base_url?: string; - - // Timestamp when the deposit was received by the exchange. - exchange_timestamp: Timestamp; - - // `Public EdDSA key of the exchange <sign-key-pub>` that was used to - // generate the signature. - // Should match one of the exchange's signing keys from ``/keys``. It is given - // explicitly as the client might otherwise be confused by clock skew as to - // which signing key was used. - exchange_pub: EddsaPublicKey; - - // Deposit confirmation signature from the exchange. - // The EdDSA signature of `TALER_DepositConfirmationPS` using a current - // `signing key of the exchange <sign-key-priv>` affirming the successful - // deposit and that the exchange will transfer the funds after the refund - // deadline, or as soon as possible if the refund deadline is zero. - exchange_sig: EddsaSignature; - } - - .. ts:def:: DepositDoubleSpendError - - interface DepositDoubleSpendError { - - // Must be TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS - // or TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY - code: Integer; - - // A string explaining that the user tried to - // double-spend. - hint: string; - - // EdDSA public key of a coin being double-spent. - coin_pub: EddsaPublicKey; - - // Hash of the public key of the denomination of the coin. - h_denom_pub: HashCode; - - } - - - ------- -Recoup ------- - -The purpose of this API is to allow coins to be cashed back in, -in certain exceptional situations. -This API is only used if the exchange is either about to go out of -business or has had its private signing keys compromised (so in -either case, the protocol is only used in **abnormal** -situations). In the above cases, the exchange signals to the -wallets that the emergency cash back protocol has been activated -by putting the affected denomination keys into the cash-back -part of the ``/keys`` response. If and only if this has happened, -coins that were signed with those denomination keys can be cashed -in using this API. - -For a recoup, a coin has to provide the necessary information to -identify the original transaction (either a withdraw or a refresh) it -became minted, and proof ownership of the coin itself. - - -.. http:post:: /recoup-withdraw - - Demand that a batch of coins be refunded to the reserve, - from which the coins were originally withdrawn. - The coins must have been originated from the same call to withdraw, and be - a subset of that original batch. - The remaining amount on the coin will be credited to the reserve - that the coins were withdrawn from, in the same withdraw request. - - Note that the original withdrawal fees will **not** be recouped. - - .. note:: This endpoint still Work-in-Progress. It will be implemented in **vRECOUP**, sometime after **v32**. - - **Request:** - - The request body must be a `RecoupWithdrawRequest` object. - - It provides sufficient information to - a) identify the originating withdraw request - b) proof that the coins to be recouped were part of that withdraw request - c) proof ownership of all coins requested to be recouped. - - **Response:** - - :http:statuscode:`200 OK`: - The request was successful, and the response is a `ReserveSummary`. - :http:statuscode:`403 Forbidden`: - A coin's signature is invalid. - This response comes with a standard `ErrorDetail` response. - :http:statuscode:`404 Not found`: - A denomination key is unknown, - the withdraw commitment is unknown - or a blinded coin is not known to have been withdrawn. - If a denomination key is unknown, the response will be - a `DenominationUnknownMessage`. - :http:statuscode:`409 Conflict`: - The operation is not allowed - as a coin has insufficient residual value, - or because the same public key of a coin - has been previously used with a different denomination. - Which case it is can be decided by looking at the error code - (usually ``TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS``). - The response is a `DepositDoubleSpendError`. - :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 not yet revoked. - The response is a `DenominationGoneMessage`. - Clients must evaluate the error code provided - to understand which of the cases this is and handle it accordingly. - - **Details:** - - .. ts:def:: RecoupWithdrawRequest - - interface RecoupWithdrawRequest { - // Public key of the reserve that will receive the recoup. - // MUST be the same as the one from the original withdraw. - reserve_pub: EddsaPublicKey; - - // The details about the coins: - // An array of either - // a) the hash code of a blinded coin envelope (not to be recouped) - // b) the disclosed coin details, in order to recoup it. - // From these, the hash of all coin envelopes - // from the original withdraw can be reconstructed. - coin_data: RecoupCoinData[]; - } - - .. ts:def:: RecoupCoinData - - // This is either - // a) the hash code of a blinded coin envelope (not to be recouped) - // b) the disclosed coin details, in order to recoup it. - type RecoupCoinData = - | NonRecoupedCoin - | RecoupDisclosedCoinDetails; - - .. ts:def:: NonRecoupedCoin - - interface NonRecoupedCoin { - type: "non_recouped_coin"; - - // This is the SHA512 hash code of a blinded coin envelope, - // including the corresponding denomination's hash. - // It is the output of the TALER_coin_ev_hash function - // from libtalerutil. - coin_ev: BlindedCoinEnvelopeHash; - }; - - .. ts:def:: BlindedCoinEnvelopeHash - - // The hash value of a blinded coin envelope, - // as it its generated by the function TALER_coin_ev_hash - // in libtalerutil. - type BlindedCoinEnvelopeHash = HashCode; - - .. ts:def:: RecoupDisclosedCoinDetails - - // This object provides all necessary coin data - // in order to call TALER_denom_blind and retrieve - // a blinded coin planchet, from which we can - // calculate the blinded coin envelope hash. - // It also contains the denomination's signature - // for the (unblinded) coin's public key, - // and the coin's signature to authorize the recoup request. - interface RecoupDisclosedCoinDetails { - type: "recoup_coin_details"; - - // The coin's public key - coin_pub: CoinPublicKey; - - // The blinding secret for this coin - // that was used during withdraw - coin_blinding_key_secret: DenominationBlindingKeySecret; - - // The coin's commitment for age restriction, - // if the denomination had age restriction support. - age_commitment_h?: AgeCommitmentHash; - - // The blinding nonce that went into this coin's - // blinded envelope - cs_session_nonce?: HashCode; - - // In case of Clause-Schnorr denomination, - // the blinding values that were provided - // for this coin, by the exchange, as response - // to a call to /blinding-prepare. - cs_r_pubs?: CSRPublicPair; - - // Unblinded signature of the coins' public key, - // signed by the denomination key. - denom_pub_sig: DenominationSignature; - - // The denomination public key. - // This denomination MUST be eligible for recoup, - // i.e. being listed in the "recoup" section of /config. - denom_pub_h: HashCode; - - // Signature of `TALER_RecoupRequestPS`, - // created by this coin's private key. - coin_sig: EddsaSignature; - } - - -.. http:post:: /recoup-refresh - - Demand that a batch of coins be refunded to the original coin, - from which the coins were originally refreshed. - The coins must have been originated from the same call to refresh, and be - a subset of that original batch. - The remaining amount on the coin will be credited to the original coin - that the coins were refreshed from, in the same refresh request. - - The base URL for coin related requests may differ from the main base URL of the - exchange. The exchange MUST return a 307 or 308 redirection to the correct - base URL if this is the case. - - Note that the original refresh fees will **not** be recouped. - - .. note:: This endpoint still Work-in-Progress. It will be implemented in **vRECOUP**, sometime after **v32**. - - **Request:** - - The request body must be a `RecoupRefreshRequest` object. - - **Response:** - - :http:statuscode:`200 OK`: - The request was successful, and the response is a `RecoupRefreshConfirmation`. - 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 coin's signature is invalid. - This response comes with a standard `ErrorDetail` response. - :http:statuscode:`404 Not found`: - The denomination key is unknown, or the blinded - coin is not known to have been withdrawn. - If the denomination key is unknown, the response will be - a `DenominationUnknownMessage`. - :http:statuscode:`409 Conflict`: - The operation is not allowed as the 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 - (usually ``TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_BALANCE``). - The response is a `DepositDoubleSpendError`. - :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 not yet revoked. The response is a - `DenominationGoneMessage`. Clients must evaluate - the error code provided to understand which of the - cases this is and handle it accordingly. - - **Details:** - - .. ts:def:: RecoupRefreshRequest - - interface RecoupRefreshRequest { - // Public key of the original coin that will receive the recoup. - // MUST be the same as the one from the original refresh request. - old_coin_pub: EddsaPublicKey; - - // The details about the coins: - // An array of either - // a) the hash code of a blinded coin envelope (not to be recouped) - // b) the disclosed coin details, in order to recoup it. - // From these, the hash of all coin envelopes - // from the original refresh can be reconstructed. - coin_data: RecoupCoinData[]; - } - - - .. ts:def:: RecoupRefreshConfirmation - - interface RecoupRefreshConfirmation { - // Public key of the old coin that will receive the recoup. - old_coin_pub: EddsaPublicKey; - - // The new balance of the old coin, after it has absorved - // the residual values of the coins from the request. - balance: Amount; - } - - - -.. _exchange_refund: - -------- -Refunds -------- - -.. http:post:: /coins/$COIN_PUB/refund - - Undo deposit of the given coin, restoring its value. - - **Request:** - - The request body must be a `RefundRequest` object. - - **Response:** - - :http:statuscode:`200 OK`: - The operation succeeded, the exchange confirms that the coin can now be refreshed. The response will include a `RefundSuccess` object. - :http:statuscode:`403 Forbidden`: - Merchant 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 a matching deposit operation (coin, contract, transaction ID and merchant public key must all match). - This response comes with a standard `ErrorDetail` response. - :http:statuscode:`409 Conflict`: - The exchange has previously received a refund request for the same coin, merchant and contract, but specifying a different amount for the same refund transaction ID. The response will be a `RefundFailure` object. - :http:statuscode:`410 Gone`: - It is too late for a refund by the exchange, the money was already sent to the merchant. - This response comes with a standard `ErrorDetail` response. - :http:statuscode:`412 Precondition failed`: - The request transaction ID is identical to a previous refund request by the same - merchant for the same coin and contract, but the refund amount differs. (The - failed precondition is that the ``rtransaction_id`` is not unique.) - The response will be a `RefundFailure` object with the conflicting refund request. - - **Details:** - - .. ts:def:: RefundRequest - - interface RefundRequest { - - // Amount to be refunded, can be a fraction of the - // coin's total deposit value (including deposit fee); - // must be larger than the refund fee. - refund_amount: Amount; - - // SHA-512 hash of the contact of the merchant with the customer. - h_contract_terms: HashCode; - - // 64-bit transaction id of the refund transaction between merchant and customer. - rtransaction_id: Integer; - - // EdDSA public key of the merchant. - merchant_pub: EddsaPublicKey; - - // EdDSA signature of the merchant over a - // `TALER_RefundRequestPS` with purpose - // ``TALER_SIGNATURE_MERCHANT_REFUND`` - // affirming the refund. - merchant_sig: EddsaPublicKey; - - } - - .. ts:def:: RefundSuccess - - interface RefundSuccess { - - // The EdDSA :ref:`signature` (binary-only) with purpose - // ``TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND`` over - // a `TALER_RecoupRefreshConfirmationPS` - // using a current signing key of the - // exchange affirming the successful refund. - exchange_sig: EddsaSignature; - - // Public EdDSA key of the exchange that was used to generate the signature. - // Should match one of the exchange's signing keys from ``/keys``. It is given - // explicitly as the client might otherwise be confused by clock skew as to - // which signing key was used. - exchange_pub: EddsaPublicKey; - } - - .. ts:def:: RefundFailure - - interface RefundFailure { - - // Numeric error code unique to the condition, which can be either - // related to the deposit value being insufficient for the requested - // refund(s), or the requested refund conflicting due to refund - // transaction number re-use (with different amounts). - code: Integer; - - // Human-readable description of the error message. - hint: string; - - // Information about the conflicting refund request(s). - // This will not be the full history of the coin, but only - // the relevant subset of the transactions. - history: CoinSpendHistoryItem[]; - } - - -.. _reserve-history: - ---------------- -Reserve History ---------------- - -.. http:get:: /reserves/$RESERVE_PUB/history - - Request information about the full history of - a reserve or an account. - - **Request:** - - 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 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. - - :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. - - **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. - - **Details:** - - .. ts:def:: ReserveHistory - - interface ReserveHistory { - // Balance left in the reserve. - balance: Amount; - - // If set, gives the maximum age group that the client is required to set - // during withdrawal. - maximum_age_group: Integer; - - // Transaction history for this reserve. - // May be partial (!). - history: TransactionHistoryItem[]; - } - - Objects in the transaction history have the following format: - - .. ts:def:: TransactionHistoryItem - - // Union discriminated by the "type" field. - type TransactionHistoryItem = - | AccountSetupTransaction - | ReserveWithdrawTransaction - | ReserveCreditTransaction - | ReserveClosingTransaction - | ReserveOpenRequestTransaction - | ReserveCloseRequestTransaction - | PurseMergeTransaction; - - .. ts:def:: AccountSetupTransaction - - interface AccountSetupTransaction { - type: "SETUP"; - - // Offset of this entry in the reserve history. - // Useful to request incremental histories via - // the "start" query parameter. - history_offset: Integer; - - // KYC fee agreed to by the reserve owner. - kyc_fee: Amount; - - // Time when the KYC was triggered. - kyc_timestamp: Timestamp; - - // 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; - - // 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:: ReserveWithdrawTransaction - - interface ReserveWithdrawTransaction { - type: "WITHDRAW"; - - // Offset of this entry in the reserve history. - // Useful to request incremental histories via - // the "start" query parameter. - history_offset: Integer; - - // Amount withdrawn. - amount: Amount; - - // Total fee that is charged for withdraw. - withdraw_fee: Amount; - - // Total number of coins in the withdraw request - num_coins: Integer; - - // Signature over a `TALER_WithdrawRequestPS` - // with purpose ``TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW`` - // created with the reserve's private key. - reserve_sig: EddsaSignature; - - // The hash of the all the planchets that were provided during the - // call to /withdraw. - h_planchets: HashCode; - - // The blinding seed that was provided. It will be NULL if - // no denominations of cipher type Clause-Schnorr were invovled - blinding_seed?: BlindingMasterSeed; - - // The array of hashes of public key of denominations for the coins. - denom_pub_hashes: HashCode[]; - - // The maximum age committed to, if the withdraw request - // required age-restriction - max_age?: Integer; - - // The noreveal index that was returned as part - // of a age-restricted withdraw, if applicable - noreveal_index?: Integer; - - } - - - .. ts:def:: ReserveCreditTransaction - - 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; - - // Amount deposited. - amount: Amount; - - // Sender account full payto:// URI. - sender_account_url: string; - - // 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:: ReserveClosingTransaction - - 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; - - // Closing balance. - amount: Amount; - - // Closing fee charged by the exchange. - closing_fee: Amount; - - // Wire transfer subject. - wtid: Base32; - - // Full payto URI of the wire account into which the funds were returned to. - receiver_account_details: string; - - // 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; - } - - - .. ts:def:: ReserveOpenRequestTransaction - - 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; - - // Open fee paid from the reserve. - open_fee: Amount; - - // This is a signature over - // a struct `TALER_ReserveOpenPS` with purpose - // ``TALER_SIGNATURE_WALLET_RESERVE_OPEN``. - reserve_sig: EddsaSignature; - - // Timestamp of the open request. - request_timestamp: Timestamp; - - // Requested expiration. - requested_expiration: Timestamp; - - // Requested number of free open purses. - requested_min_purses: Integer; - - } - - .. ts:def:: ReserveCloseRequestTransaction - - interface ReserveCloseRequestTransaction { - type: "CLOSE"; - - // Offset of this entry in the reserve history. - // Useful to request incremental histories via - // the "start" query parameter. - history_offset: Integer; - - // This is a signature over - // a struct `TALER_ReserveClosePS` with purpose - // ``TALER_SIGNATURE_WALLET_RESERVE_CLOSE``. - reserve_sig: EddsaSignature; - - // Hash over the full payto URI of the target account. - h_payto?: FullPaytoHash; - - // Timestamp of the close request. - request_timestamp: Timestamp; - } - - .. ts:def:: PurseMergeTransaction - - 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; - - // 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; - - // 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; - - // Client-side timestamp of when the merge request was made. - merge_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 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; - } - - - -.. _coin-history: - ------------- -Coin History ------------- - -.. 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 -believe it. Usually, the wallet knows the transaction history of each coin -and thus has no need to inquire. - -**Request:** - -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 - ``$COIN_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. - -**Details:** - -.. ts:def:: CoinHistoryResponse - - interface CoinHistoryResponse { - // Current balance of the coin. - balance: Amount; - - // Hash of the coin's denomination. - h_denom_pub: HashCode; - - // Transaction history for the coin. - history: CoinSpendHistoryItem[]; - } - -.. ts:def:: CoinSpendHistoryItem - - // Union discriminated by the "type" field. - type CoinSpendHistoryItem = - | CoinDepositTransaction - | CoinMeltTransaction - | CoinRefundTransaction - | CoinRecoupWithdrawTransaction - | CoinRecoupRefreshTransaction - | CoinRecoupRefreshReceiverTransaction - | CoinPurseDepositTransaction - | CoinPurseRefundTransaction - | CoinReserveOpenDepositTransaction; - -.. ts:def:: CoinDepositTransaction - - 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 - // the deposit fee. The current coin value can thus be computed by - // subtracting this amount. - amount: Amount; - - // Deposit fee. - deposit_fee: Amount; - - // Public key of the merchant. - merchant_pub: EddsaPublicKey; - - // Date when the operation was made. - timestamp: Timestamp; - - // Date until which the merchant can issue a refund to the customer via the - // exchange, possibly zero if refunds are not allowed. - refund_deadline?: Timestamp; - - // Hash over the proposal data of the contract that - // is being paid. - h_contract_terms: HashCode; - - // Hash of the bank account from where we received the funds. - h_wire: HashCode; - - // Hash of the public denomination key used to sign the coin. - // Needed because 'coin_sig' signs over this, and - // that is important to fix the coin's denomination. - h_denom_pub: HashCode; - - // Hash over the deposit policy extension. Optional. - h_policy?: HashCode; - - // Hash over auxiliary wallet data provided by the wallet - // to complete the contract. Optional. - wallet_data_hash?: HashCode; - - // Hash over the age commitment of the coin. Optional. - h_age_commitment?: HashCode; - - // Signature over `TALER_DepositRequestPS`, made by the customer with the - // `coin's private key <coin-priv>`. - coin_sig: EddsaSignature; - - } - -.. ts:def:: CoinMeltTransaction - - 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 - // subtracting the amounts. - amount: Amount; - - // Melt fee. - melt_fee: Amount; - - // Commitment from the melt operation, see `TALER_RefreshCommitmentP` - rc: HashCode; - - // Hash of the public denomination key used to sign the old coin. - // Needed because 'coin_sig' signs over this, and - // that is important to fix the coin's denomination. - old_denom_pub_h: HashCode; - - // Hash over the age commitment of the coin. Optional. - old_age_commitment_h?: AgeCommitmentHash; - - // @since **v32** - // This value is opaque to the exchange. It was provided by the client - // as part of the original refresh request, and was therefore verified - // with the confirm_sig below. - // If the reveal step was not performed yet by the old coin owner, - // they can use this value and the old coin's private key to derive - // all indivual seeds for the n*κ coin candidates for the original - // refresh request and replay it - refresh_seed: HashCode; - - // @since **v32** - // The kappa*n list of transfer public keys that were provided by the - // old coin owner during the melt request. - transfer_pubs: EddsaPublicKey[kappa][]; - - // @since **v32** - // The n denomination public keys for the fresh coins - // that the coin owner had requested. - denoms_h: HashCode[]; - - // @since **v32** - // The ``noreveal_index`` value that was returned by the exchange as response - // to the melt request. - noreveal_index: Integer; - - // @since **v32** - // If the reveal step was successfully peformed by the coin owner, - // this field contains the blind coin signatures that were returned - // by the exchange for the chosen batch of coins. - ev_sigs?: BlindedDenominationSignature[]; - - // Master seed for the Clause-Schnorr R-value - // Present if one of the fresh coin's - // denominations is of type Clause-Schnorr. - blinding_seed?: BlindingMasterSeed; - - // Signature by the coin over a - // `TALER_RefreshMeltCoinAffirmationPS` of - // purpose ``TALER_SIGNATURE_WALLET_COIN_MELT``. - confirm_sig: EddsaSignature; - - } - -.. ts:def:: CoinRefundTransaction - - 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. - // The current coin value can thus be computed by - // adding the amounts to the coin's denomination value. - amount: Amount; - - // Refund fee. - refund_fee: Amount; - - // Hash over the proposal data of the contract that - // is being refunded. - h_contract_terms: HashCode; - - // Public key of the merchant. - merchant_pub: EddsaPublicKey; - - // Refund transaction ID. - rtransaction_id: Integer; - - // `EdDSA Signature <eddsa-sig>` authorizing the REFUND over a - // `TALER_MerchantRefundConfirmationPS` with - // purpose ``TALER_SIGNATURE_MERCHANT_REFUND_OK``. Made with - // the `public key of the merchant <merchant-pub>`. - merchant_sig: EddsaSignature; - - } - - -.. note:: - - The `CoinRecoupWithdrawTransaction` interface defintion is WIP. - It will be fully specified and implemented with **vRECOUP**. - -.. ts:def:: CoinRecoupWithdrawTransaction - - // This represents a transaction of a call to /recoup-withdraw - // where the coin's residual value has been credited to the - // original reserve, from which this coin was withdrawn. - interface CoinRecoupWithdrawTransaction { - type: "RECOUP-WITHDRAW"; - - // 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 - // subtracting the amount from - // the coin's denomination value. - amount: Amount; - - // Signature by the exchange over a - // `TALER_RecoupConfirmationPS`, must be - // of purpose ``TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP``. - exchange_sig: EddsaSignature; - - // Public key of the private key used to create 'exchange_sig'. - exchange_pub: EddsaPublicKey; - - // Signature by the coin over a - // `TALER_RecoupRequestPS` with purpose - // ``TALER_SIGNATURE_WALLET_COIN_RECOUP``. - coin_sig: EddsaSignature; - - // Hash of the public denomination key used to sign the coin. - // Needed because 'coin_sig' signs over this, and - // that is important to fix the coin's denomination. - h_denom_pub: HashCode; - - // Coin blinding key that was used in the original withdraw request. - coin_blind: DenominationBlindingKeyP; - - // The hash of the withdraw commitment of the original withdraw - // request that this coin was part of - h_commitment: HashCode; - - // Coin's index in the original withdraw request, starting at 0 - coin_index: Integer; - - // Reserve receiving the recoup. - reserve_pub: EddsaPublicKey; - - // Date when the operation was made. - timestamp: Timestamp; - - } - - -.. note:: - - The `CoinRecoupRefreshTransaction` interface defintion is WIP. - It will be fully specified and implemented with **vRECOUP**. - -.. ts:def:: CoinRecoupRefreshTransaction - - // This represents a transaction of a call to /recoup-refresh - // where this coin was _part_ of the batch of coins whose - // residual values were credited to the original coin, from - // which also this coin was refresh from. - 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 - // subtracting this amounts from - // the coin's denomination value. - amount: Amount; - - // Signature by the exchange over a - // `TALER_RecoupRefreshConfirmationPS` - // of purpose ``TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_REFRESH``. - exchange_sig: EddsaSignature; - - // Public key used to sign 'exchange_sig'. - exchange_pub: EddsaPublicKey; - - // The original coin, from which this coin was derived from - // in a call to /refresh, and which was then credited with - // the residual value of this coin in a call to /recoup-refresh. - old_coin_pub: EddsaPublicKey; - - // Signature by the coin over a `TALER_RecoupRequestPS` - // with purpose ``TALER_SIGNATURE_WALLET_COIN_RECOUP``. - coin_sig: EddsaSignature; - - // Hash of the public denomination key used to sign the coin. - // Needed because 'coin_sig' signs over this, and - // that is important to fix the coin's denomination. - h_denom_pub: HashCode; - - // Coin blinding key that was used in the original refresh request. - coin_blind: DenominationBlindingKeyP; - - // The hash of the refresh commitment of the original refresh - // request that this coin was derived from. - h_commitment: HashCode; - - // Coin's index in the original refresh request, starting at 0 - coin_index: Integer; - - // Blinding factor of the revoked new coin. - new_coin_blinding_secret: DenominationBlindingKeySecret; - - // Blinded public key of the revoked new coin. - new_coin_ev: DenominationBlindingKeySecret; - - // Date when the operation was made. - timestamp: Timestamp; - - } - - -.. note:: - - The `CoinRecoupRefreshReceiverTransaction` interface defintion is WIP. - It will be fully specified and implemented with **vRECOUP**. - -.. ts:def:: CoinRecoupRefreshReceiverTransaction - - // This represents a transaction of a call to /recoup-refresh - // where this coin was the _receiver_ of the residual values - // from coins, that originated from a call to /refresh of this coin. - interface CoinRecoupRefreshReceiverTransaction { - type: "RECOUP-REFRESH-RECEIVER"; - - // 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 - // adding the amount to the coin's denomination value. - amount: Amount; - - // Date when the operation was made. - timestamp: Timestamp; - - // Signature by the exchange over a - // `TALER_RecoupRefreshConfirmationPS` - // of purpose ``TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_REFRESH``. - exchange_sig: EddsaSignature; - - // Public key of the private key used to create 'exchange_sig'. - exchange_pub: EddsaPublicKey; - - } - -.. ts:def:: CoinPurseDepositTransaction - - 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 - // the deposit fee. The current coin value can thus be computed by - // subtracting the amount from - // the coin's denomination value. - amount: Amount; - - // Base URL of the exchange the purse lives at. - exchange_base_url: string; - - // The hash of the age-commitment for the coin. Only present - // if the denomination has support for age restriction. - h_age_commitment?: AgeCommitmentHash; - - // Deposit fee. - deposit_fee: Amount; - - // Public key of the purse. - purse_pub: EddsaPublicKey; - - // True if the deposit was refunded for any reason. - refunded: boolean; - - // Signature by the coin over a - // `TALER_PurseDepositSignaturePS` of - // purpose ``TALER_SIGNATURE_PURSE_DEPOSIT``. - coin_sig: EddsaSignature; - - // Hash of the public denomination key used to sign the coin. - // Needed because 'coin_sig' signs over this, and - // that is important to fix the coin's denomination. - h_denom_pub: HashCode; - - } - -.. ts:def:: CoinPurseRefundTransaction - - 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. - // The current coin value can thus be computed by - // adding the amount to the coin's denomination value. - amount: Amount; - - // Refund fee (of the coin's denomination). The deposit - // fee will be waived. - refund_fee: Amount; - - // Signature by the exchange over a - // ``TALER_CoinPurseRefundConfirmationPS`` - // of purpose ``TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_REFUND``. - exchange_sig: EddsaSignature; - - // Public key used to sign 'exchange_sig'. - exchange_pub: EddsaPublicKey; - - // Public key of the purse that expired. - purse_pub: EddsaPublicKey; - - } - -.. ts:def:: CoinReserveOpenDepositTransaction - - 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 - // the deposit fee. - coin_contribution: Amount; - - // Signature of the reserve open operation being paid for. - reserve_sig: EddsaSignature; - - // Signature by the coin over a - // `TALER_ReserveOpenDepositSignaturePS` of - // purpose ``TALER_SIGNATURE_RESERVE_OPEN_DEPOSIT``. - coin_sig: EddsaSignature; - - } - - ------------------------ -Tracking wire transfers ------------------------ - -This API is used by merchants that need to find out which wire -transfers (from the exchange to the merchant) correspond to which deposit -operations. Typically, a merchant will receive a wire transfer with a -**wire transfer identifier** and want to know the set of deposit -operations that correspond to this wire transfer. This is the -preferred query that merchants should make for each wire transfer they -receive. If a merchant needs to investigate a specific deposit -operation (i.e. because it seems that it was not paid), then the -merchant can also request the wire transfer identifier for a deposit -operation. - -Sufficient information is returned to verify that the coin signatures -are correct. This also allows governments to use this API when doing -a tax audit on merchants. - -Naturally, the returned information may be sensitive for the merchant. -We do not require the merchant to sign the request, as the same requests -may also be performed by the government auditing a merchant. -However, wire transfer identifiers should have sufficient entropy to -ensure that obtaining a successful reply by brute-force is not practical. -Nevertheless, the merchant should protect the wire transfer identifiers -from his bank statements against unauthorized access, lest his income -situation is revealed to an adversary. (This is not a major issue, as -an adversary that has access to the line-items of bank statements can -typically also view the balance.) - - -.. http:get:: /transfers/$WTID - - Provides deposits associated with a given wire transfer. The - wire transfer identifier (WTID) and the base URL for tracking - the wire transfer are both given in the wire transfer subject. - - **Request:** - - **Response:** - - :http:statuscode:`200 OK`: - The wire transfer is known to the exchange, details about it follow in the body. - The body of the response is a `TrackTransferResponse`. - :http:statuscode:`404 Not found`: - The wire transfer identifier is unknown to the exchange. - - .. ts:def:: TrackTransferResponse - - interface TrackTransferResponse { - // Actual amount of the wire transfer, excluding the wire fee. - total: Amount; - - // Applicable wire fee that was charged. - wire_fee: Amount; - - // Public key of the merchant (identical for all deposits). - merchant_pub: EddsaPublicKey; - - // Hash of the payto:// account URI (identical for all deposits). - h_payto: FullPaytoHash; - - // Time of the execution of the wire transfer by the exchange. - execution_time: Timestamp; - - // Details about the deposits. - deposits: TrackTransferDetail[]; - - // Signature from the exchange made with purpose - // ``TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT`` - // over a `TALER_WireDepositDataPS`. - exchange_sig: EddsaSignature; - - // Public EdDSA key 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: EddsaSignature; - } - - .. ts:def:: TrackTransferDetail - - interface TrackTransferDetail { - // SHA-512 hash of the contact of the merchant with the customer. - h_contract_terms: HashCode; - - // Coin's public key, both ECDHE and EdDSA. - coin_pub: CoinPublicKey; - - // The total amount the original deposit was worth, - // including fees and after applicable refunds. - deposit_value: Amount; - - // Applicable fees for the deposit, possibly - // reduced or waived due to refunds. - deposit_fee: Amount; - - // Refunds that were applied to the value of - // this coin. Optional. - // @since protocol **v19**. Before, refunds were - // incorrectly still included in the - // ``deposit_value`` (!). - refund_total?: Amount; - - } - -.. http:get:: /deposits/$H_WIRE/$MERCHANT_PUB/$H_CONTRACT_TERMS/$COIN_PUB - - Provide the wire transfer identifier associated with an (existing) deposit operation. - The arguments are the hash of the merchant's payment details (H_WIRE), the - merchant's public key (EdDSA), the hash of the contract terms that were paid - (H_CONTRACT_TERMS) and the public key of the coin used for the payment (COIN_PUB). - - **Request:** - - :query merchant_sig: EdDSA signature of the merchant made with purpose - ``TALER_SIGNATURE_MERCHANT_TRACK_TRANSACTION`` over a - ``TALER_DepositTrackPS``, affirming that it is really the merchant who - requires obtaining the wire transfer identifier. - :query timeout_ms=NUMBER: *Optional.* If specified, the exchange will wait - up to ``NUMBER`` milliseconds for completion of a deposit operation before - sending the HTTP response. - :query lpt=TARGET: *Optional*. - Specifies what status change we are long-polling for. - Use 1 to wait for the a 202 state where ``kyc_ok`` is false *or* a 200 OK response. - 2 to wait exclusively for a 200 OK response. - @since protocol **v21**. - - **Response:** - - :http:statuscode:`200 OK`: - The deposit has been executed by the exchange and we have a wire transfer identifier. - The response body is a `TrackTransactionResponse` object. - :http:statuscode:`202 Accepted`: - The deposit request has been accepted for processing, but was not yet - executed. Hence the exchange does not yet have a wire transfer identifier. The - merchant should come back later and ask again. - The response body is a `TrackTransactionAcceptedResponse`. - :http:statuscode:`403 Forbidden`: - A signature is invalid. - This response comes with a standard `ErrorDetail` response. - :http:statuscode:`404 Not found`: - The deposit operation is unknown to the exchange. - - **Details:** - - .. ts:def:: TrackTransactionResponse - - interface TrackTransactionResponse { - - // Raw wire transfer identifier of the deposit. - wtid: Base32; - - // When was the wire transfer given to the bank. - execution_time: Timestamp; - - // The contribution of this coin to the total (without fees) - coin_contribution: Amount; - - // Binary-only Signature_ with purpose ``TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE`` - // over a `TALER_ConfirmWirePS` - // whereby the exchange affirms the successful wire transfer. - exchange_sig: EddsaSignature; - - // Public EdDSA key 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:: TrackTransactionAcceptedResponse - - interface TrackTransactionAcceptedResponse { - - // Legitimization row. Largely useless, except - // not present if the deposit has not - // yet been aggregated to the point that a KYC - // requirement has been evaluated. - requirement_row?: Integer; - - // True if the KYC check for the merchant has been - // satisfied. False does not mean that KYC - // is strictly needed, unless also a - // legitimization_uuid is provided. - kyc_ok: boolean; - - // Time by which the exchange currently thinks the deposit will be executed. - // Actual execution may be later if the KYC check is not satisfied by then. - execution_time: Timestamp; - - // Public key associated with the account. The client must sign - // the initial request for the KYC status using the corresponding - // private key. Will be the merchant (instance) public key. - // - // This is ONLY given if the merchant did a KYC auth wire transfer. - // It is not given if the deposit was made to a reserve public key. - // The wallet would already know the reserve public key, plus there - // could be various reserve public keys (which do not change), while - // there is only the *latest* KYC auth wire transfer public key. - // - // Absent if no public key is currently associated - // with the account and the client MUST thus first - // credit the exchange via an inbound wire transfer - // to associate a public key with the debited account. - // Note that absence does *not* imply a need for a KYC auth - // transfer if the deposit was made to a reserve public key - // that was also used for the deposit. - // - // @since protocol **v20**. - account_pub?: EddsaPublicKey; - - } - - -.. _exchange_w2w: - --------------------------- -Wallet-to-wallet transfers --------------------------- - -.. http:get:: /purses/$PURSE_PUB/merge -.. http:get:: /purses/$PURSE_PUB/deposit - - Obtain information about a purse. Depending on the suffix, - the long-polling (if any) will wait for either a merge or - a deposit event. - - **Request:** - - :query timeout_ms=NUMBER: *Optional.* If specified, - the exchange - will wait up to ``NUMBER`` 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 ``NUMBER`` milliseconds for completion - of a deposit operation before sending the HTTP response. - - **Response:** - - :http:statuscode:`200 OK`: - The operation succeeded, the exchange provides details - about the purse. - The response will include a `PurseStatus` object. - :http:statuscode:`404 Not found`: - The purse is unknown to the exchange. - :http:statuscode:`410 Gone`: - The purse expired before the deposit or merge was completed. - - **Details:** - - .. ts:def:: PurseStatus - - interface PurseStatus { - - // Total amount deposited into the purse so far. - // If 'total_deposit_amount' minus 'deposit_fees' - // exceeds 'merge_value_after_fees', and a - // 'merge_request' exists for the purse, then the - // purse will (have been) merged with the account. - balance: Amount; - - // Time of the merge, missing if "never". - merge_timestamp?: Timestamp; - - // Time of the deposits being complete, missing if "never". - // Note that this time may not be "stable": once sufficient - // deposits have been made, is "now" before the purse - // expiration, and otherwise set to the purse expiration. - // However, this should also not be relied upon. The key - // property is that it is either "never" or in the past. - deposit_timestamp?: Timestamp; - - // Time when the purse expires and - // funds that were not merged are refunded - // on the deposited coins. - // FIXME: Document the exchange protocol version - // in which this field became available. - purse_expiration: Timestamp; - - // EdDSA signature of the exchange over a - // `TALER_PurseStatusResponseSignaturePS` - // with purpose ``TALER_SIGNATURE_PURSE_STATUS_RESPONSE`` - // affirming the purse status. - exchange_sig: EddsaSignature; - - // EdDSA public key exchange used for 'exchange_sig'. - exchange_pub: EddsaPublicKey; - - } - - - -.. http:post:: /purses/$PURSE_PUB/create - - Create a purse by depositing money into it. First step of a PUSH payment. - - **Request:** - - The request body must be a `PurseCreate` 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 `PurseCreateSuccessResponse` object. - :http:statuscode:`403 Forbidden`: - A coin, denomination or contract signature is invalid. - This response comes with a standard `ErrorDetail` response. - :http:statuscode:`404 Not Found`: - The denomination of one of the coins is unknown to the exchange. - :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, or because a purse with - the same public key but different meta data was created previously. - Which case it is - can be decided by looking at the error code - (``TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS`` or - ``TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY`` or - ``TALER_EC_EXCHANGE_PURSE_CREATE_CONFLICTING_META_DATA`` or - ``TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA`` or - ``TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA``). - The specific fields of the response depend on the error code - and include the signatures (and what was signed over) proving the - conflict. - :http:statuscode:`425 Too Early`: - This response type is used if the given purse expiration time - is too far in the future (at least from the perspective - of the exchange). Thus, retrying at a later time may - succeed. The client should look at the ``Date:`` header - of the response to see if a minor time difference is to - blame and possibly adjust the request accordingly. - (Note: this status code is not yet used.) - - - **Details:** - - .. ts:def:: PurseCreate - - interface PurseCreate { - - // Total value of the purse, excluding fees. - amount: Amount; - - // Minimum age required for all coins deposited into the purse. - min_age: Integer; - - // Optional encrypted contract, in case the buyer is - // proposing the contract and thus establishing the - // purse with the payment. - econtract?: EncryptedContract; - - // EdDSA public key used to approve merges of this purse. - merge_pub: EddsaPublicKey; - - // EdDSA signature of the purse over a - // `TALER_PurseRequestSignaturePS` - // of purpose ``TALER_SIGNATURE_WALLET_PURSE_CREATE`` - // confirming the key - // invariants associated with the purse. - // (amount, h_contract_terms, expiration). - purse_sig: EddsaSignature; - - // SHA-512 hash of the contact of the purse. - h_contract_terms: HashCode; - - // Array of coins being deposited into the purse. - // Maximum length is 128. - deposits: PurseDeposit[]; - - // 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; - - } - - .. ts:def:: EncryptedContract - - interface EncryptedContract { - - // Encrypted contract. - econtract: string; - - // Signature over the (encrypted) contract. - econtract_sig: EddsaSignature; - - // Ephemeral public key for the DH operation to decrypt the encrypted contract. - contract_pub: EddsaPublicKey; - - } - - .. ts:def:: PurseCreateSuccessResponse - - interface PurseCreateSuccessResponse { - - // Total amount deposited into the purse so far (without fees). - total_deposited: Amount; - - // Time at the exchange. - exchange_timestamp: Timestamp; - - // EdDSA signature of the exchange affirming the payment, - // of purpose ``TALER_SIGNATURE_PURSE_DEPOSIT_CONFIRMED`` - // over a `TALER_PurseDepositConfirmedSignaturePS`. - // Signs over the above and the purse public key and - // the hash of the contract terms. - exchange_sig: EddsaSignature; - - // public key used to create the signature. - exchange_pub: EddsaPublicKey; - - } - - .. ts:def:: PurseConflict - - // Union discriminated by the "code" field. - type PurseConflict = - | DepositDoubleSpendError - | PurseCreateConflict - | PurseDepositConflict - | PurseContractConflict; - - .. ts:def:: PurseCreateConflict - - interface PurseCreateConflict { - // Must be equal to TALER_EC_EXCHANGE_PURSE_CREATE_CONFLICTING_META_DATA - code: Integer; - - // Total amount to be merged into the reserve. - // (excludes fees). - amount: Amount; - - // Minimum age required for all coins deposited into the purse. - min_age: Integer; - - // 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 over - // `TALER_PurseMergeSignaturePS` of - // purpose ``TALER_SIGNATURE_WALLET_PURSE_MERGE`` - // confirming that the - // above details hold for this purse. - purse_sig: EddsaSignature; - - // 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; - } - - .. ts:def:: PurseDepositConflict - - interface PurseDepositConflict { - // Must be equal to TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA - code: Integer; - - // Public key of the coin being deposited into the purse. - coin_pub: EddsaPublicKey; - - // Signature over `TALER_PurseDepositSignaturePS` - // of purpose ``TALER_SIGNATURE_WALLET_PURSE_DEPOSIT`` - // made by the customer with the - // `coin's private key <coin-priv>`. - coin_sig: EddsaSignature; - - // Target exchange URL for the purse. Not present for the - // same exchange. - partner_url?: string; - - // Amount to be contributed to the purse by this coin. - amount: Amount; - - } - - .. ts:def:: PurseContractConflict - - interface PurseContractConflict { - // Must be equal to TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA - code: Integer; - - // Hash of the encrypted contract. - h_econtract: HashCode; - - // Signature over the contract. - econtract_sig: EddsaSignature; - - // Ephemeral public key for the DH operation to decrypt the contract. - contract_pub: EddsaPublicKey; - - } - - -.. http:delete:: /purses/$PURSE_PUB - - Delete a purse that is unmerged and not yet expired. Refunds any money that - is already in the purse. - - **Request:** - - The request body must be empty, as recommended for HTTP delete in general. - - To authorize the request, the header must contain the following HTTP header: - - *Taler-Purse-Signature*: - $PURSE_SIG`` where ``$PURSE_SIG`` is the Crockford base32-encoded EdDSA - signature of purpose TALER_SIGNATURE_WALLET_PURSE_DELETE. - - **Response:** - - :http:statuscode:`204 No Content`: - The operation succeeded, the exchange confirms that the purse - was deleted. - :http:statuscode:`403 Forbidden`: - The signature is invalid. - This response comes with a standard `ErrorDetail` response. - :http:statuscode:`404 Not Found`: - The purse is not known. Might have already been deleted previously. - :http:statuscode:`409 Conflict`: - It is too late to delete the purse, its fate (merge or expiration) - was already decided. - - -.. http:post:: /purses/$PURSE_PUB/merge - - Merge purse with account, adding the value of the purse into - the account. Endpoint to be used by the receiver of a PUSH payment. - - **Request:** - - The request body must be a `MergeRequest` object. - - **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:`402 Payment Required`: - The purse is not yet full and more money needs to be deposited - before the merge can be made. - :http:statuscode:`403 Forbidden`: - The signature of the merge request or the reserve was invalid. - This response comes with a standard `ErrorDetail` response. - :http:statuscode:`404 Not found`: - The merge operation failed as we could not find the purse - or the partner exchange. - This response comes with a standard `ErrorDetail` response. - :http:statuscode:`409 Conflict`: - The purse was already merged into a different reserve. - The response will include a `MergeConflict` object. - :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:`451 Unavailable For Legal Reasons`: - This account has not yet passed the KYC checks. - The client must pass KYC checks before proceeding with the merge. - The response will be an `LegitimizationNeededResponse` object. - - **Details:** - - .. ts:def:: MergeRequest - - interface MergeRequest { - - // Normalized payto URI of the account the purse is to be merged into. - // Must be of the form: 'payto://taler/$EXCHANGE_URL/$RESERVE_PUB'. - payto_uri: string; - - // EdDSA signature of the account/reserve affirming the merge - // over a `TALER_AccountMergeSignaturePS`. - // Must be of purpose ``TALER_SIGNATURE_ACCOUNT_MERGE`` - reserve_sig: EddsaSignature; - - // EdDSA signature of the merge private key affirming the merge - // over a `TALER_PurseMergeSignaturePS`. - // Must be of purpose ``TALER_SIGNATURE_PURSE_MERGE``. - merge_sig: EddsaSignature; - - // Client-side timestamp of when the merge request was made. - merge_timestamp: Timestamp; - - } - - .. ts:def:: MergeSuccess - - interface MergeSuccess { - - // Amount merged (excluding deposit fees). - merge_amount: Amount; - - // Time at which the merge came into effect. - // Maximum of the "payment_timestamp" and the - // "merge_timestamp". - exchange_timestamp: Timestamp; - - // EdDSA signature of the exchange affirming the merge of - // purpose ``TALER_SIGNATURE_PURSE_MERGE_SUCCESS`` - // over `TALER_PurseMergeSuccessSignaturePS`. - // Signs over the above and the account public key. - exchange_sig: EddsaSignature; - - // public key used to create the signature. - exchange_pub: EddsaPublicKey; - - } - - .. ts:def:: MergeConflict - - interface MergeConflict { - - // Client-side timestamp of when the merge request was made. - merge_timestamp: Timestamp; - - // EdDSA signature of the purse private key affirming the merge - // over a `TALER_PurseMergeSignaturePS`. - // Must be of purpose ``TALER_SIGNATURE_PURSE_MERGE``. - merge_sig: EddsaSignature; - - // Base URL of the exchange receiving the payment, only present - // if the exchange hosting the reserve is not this exchange. - partner_url?: string; - - // Public key of the reserve that the purse was merged into. - reserve_pub: EddsaPublicKey; - } - - - -.. http:post:: /reserves/$RESERVE_PUB/purse - - Create purse for an account. First step of a PULL payment. - - **Request:** - - The request body must be a `ReservePurseRequest` object. - - **Response:** - - :http:statuscode:`200 OK`: - The operation succeeded, the exchange confirms that the - purse was allocated. - The response will include a `PurseCreateSuccessResponse` object. - :http:statuscode:`402 Payment Required`: - The account needs to contain more funding to create more purses. - This response comes with a standard `ErrorDetail` response. - :http:statuscode:`403 Forbidden`: - Account or contract signature is invalid. - This response comes with a standard `ErrorDetail` response. - :http:statuscode:`404 Not found`: - The purse creation operation failed as we could not find the reserve. - This response comes with a standard `ErrorDetail` response. - :http:statuscode:`409 Conflict`: - The purse creation failed because a purse with - the same public key but different meta data was - created previously. Which specific conflict it is - can be decided by looking at the error code - (``TALER_EC_EXCHANGE_PURSE_CREATE_CONFLICTING_META_DATA`` or - ``TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA`` or - ``TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA``). - The specific fields of the response depend on the error code - and include the signatures (and what was signed over) proving the - conflict. - The response will be a `PurseConflict` response - (but not a `DepositDoubleSpendError`). - :http:statuscode:`451 Unavailable For Legal Reasons`: - This account has not yet passed the KYC checks. - The client must pass KYC checks before proceeding with the merge. - The response will be an `LegitimizationNeededResponse` object. - - **Details:** - - .. ts:def:: ReservePurseRequest - - interface ReservePurseRequest { - - // Minimum amount that must be credited to the reserve, that is - // the total value of the purse minus the deposit fees. - // If the deposit fees are lower, the contribution to the - // reserve can be higher! - purse_value: Amount; - - // Minimum age required for all coins deposited into the purse. - min_age: Integer; - - // Purse fee the reserve owner is willing to pay - // for the purse creation. Optional, if not present - // the purse is to be created from the purse quota - // of the reserve. - purse_fee: Amount; - - // Optional encrypted contract, in case the buyer is - // proposing the contract and thus establishing the - // purse with the payment. - econtract?: EncryptedContract; - - // EdDSA public key used to approve merges of this purse. - merge_pub: EddsaPublicKey; - - // EdDSA signature of the purse private key affirming the merge - // over a `TALER_PurseMergeSignaturePS`. - // Must be of purpose ``TALER_SIGNATURE_PURSE_MERGE``. - merge_sig: EddsaSignature; - - // EdDSA signature of the account/reserve affirming the merge. - // Must be of purpose ``TALER_SIGNATURE_WALLET_ACCOUNT_MERGE`` - reserve_sig: EddsaSignature; - - // Purse public key. - purse_pub: EddsaPublicKey; - - // EdDSA signature of the purse over - // `TALER_PurseRequestSignaturePS` of - // purpose ``TALER_SIGNATURE_PURSE_REQUEST`` - // confirming that the - // above details hold for this purse. - purse_sig: EddsaSignature; - - // SHA-512 hash of the contact of the purse. - h_contract_terms: HashCode; - - // Client-side timestamp of when the merge request was made. - merge_timestamp: Timestamp; - - // Indicative time by which the purse should expire - // if it has not been paid. - purse_expiration: Timestamp; - - } - -.. http:get:: /contracts/$CONTRACT_PUB - - Obtain P2P contract associated with the given contract public key. - - **Response:** - - :http:statuscode:`200 Ok`: - The contract is being returned using a `ExchangeContractResponse`. - :http:statuscode:`404 Not found`: - The contract is unknown. - This response comes with a standard `ErrorDetail` response. - - **Details:** - - .. ts:def:: ExchangeContractResponse - - interface ExchangeContractResponse { - - // Public key of the purse into which payments must be - // made for this contract. - purse_pub: PursePublicKey; - - // Signature over the contract - econtract_sig: PurseContractSignature; - - // Encrypted contract. - econtract: EncryptedContract; - } - - -.. http:post:: /purses/$PURSE_PUB/deposit - - Deposit money into a purse. Used by the buyer for a PULL payment. - - **Request:** - - The request body must be a `PurseDeposits` 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 `PurseDepositSuccessResponse` object. - :http:statuscode:`403 Forbidden`: - A coin or denomination signature is invalid. - This response comes with a standard `ErrorDetail` response. - :http:statuscode:`404 Not found`: - The purse is unknown. - This response comes with a standard `ErrorDetail` response. - :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_GENERIC_INSUFFICIENT_FUNDS`` or - ``TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY`` or - ``TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA``). - This response comes with a standard `PurseConflict` response - (alas some cases are impossible). - :http:statuscode:`410 Gone`: - The purse has expired. - - - **Details:** - - .. ts:def:: PurseDeposits - - interface PurseDeposits { - - // Array of coins to deposit into the purse. - deposits: PurseDeposit[]; - } - - .. ts:def:: PurseDeposit - - interface PurseDeposit { - - // Amount to be deposited, can be a fraction of the - // coin's total value. - amount: 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: DenominationSignature; - - // Age commitment for the coin, if the denomination is age-restricted. - age_commitment?: AgeCommitment; - - // Attestation for the minimum age, if the denomination is age-restricted. - attest?: Attestation; - - // Signature over `TALER_PurseDepositSignaturePS` - // of purpose ``TALER_SIGNATURE_WALLET_PURSE_DEPOSIT`` - // made by the customer with the - // `coin's private key <coin-priv>`. - coin_sig: EddsaSignature; - - // Public key of the coin being deposited into the purse. - coin_pub: EddsaPublicKey; - - } - - .. ts:def:: PurseDepositSuccessResponse - - interface PurseDepositSuccessResponse { - - // Total amount paid into the purse. - total_deposited: Amount; - - // Total amount expected in the purse. - purse_value_after_fees: Amount; - - // Time at which the deposit came into effect. - exchange_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; - - // SHA-512 hash of the contact of the purse. - h_contract_terms: HashCode; - - // EdDSA signature of the exchange affirming the payment, - // of purpose ``TALER_SIGNATURE_PURSE_DEPOSIT_CONFIRMED`` - // over a `TALER_PurseDepositConfirmedSignaturePS`. - // Signs over the above and the purse public key and - // the hash of the contract terms. - exchange_sig: EddsaSignature; - - // public key used to create the signature. - exchange_pub: EddsaPublicKey; - - } - - .. ts:def:: AgeCommitment - - // AgeCommitment is an array of public keys, one for each age group of the - // age-restricted denomination. - type AgeCommitment = Edx25519PublicKey[]; - - .. ts:def:: Attestation - - // An attestation for a minimum age is an Edx25519 signature of the age - // with purpose ``TALER_SIGNATURE_WALLET_AGE_ATTESTATION``. - type Attestation = string; - -.. _exchange_wads: - - ----- -Wads ----- - - .. note:: - - This is a draft API that is not yet implemented. - - -These endpoints are used to manage exchange-to-exchange payments in support of -wallet-to-wallet payments. Only another exchange should access this endpoint. - - -.. 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; - - // Indicative time by which the wad was given to the - // bank to execute the wire transfer. - wad_execution_time: Timestamp; - - // Transfers aggregated in the wad. - items: WadItem[]; - - // EdDSA signature of the exchange affirming the wad - // data is correct, must be over `TALER_WadDataSignaturePS` - // and of purpose ``TALER_SIGNATURE_WAD_DATA``. - exchange_sig: EddsaSignature; - - // public key used to create the signature. - exchange_pub: EddsaPublicKey; - } - - Objects in the wad item list have the following format: - - .. ts:def:: WadItem - - interface WadItem { - - // Amount in the purse. - amount: Amount; - - // Normalized payto URI of the account the purse is to be merged into. - // Must be of the form: 'payto://taler/EXCHANGE_URL/RESERVE_PUB'. - payto_uri: string; - - // Purse public key. - purse_pub: EddsaPublicKey; - - // Hash of the contract. - h_contract: HashCode; - - // Indicative time by which the purse should expire - // if it has not been paid. - purse_expiration: Timestamp; - - // Client-side timestamp of when the merge request was made. - merge_timestamp: Timestamp; - - // Signature created with the reserve's private key. - // Must be of purpose ``TALER_SIGNATURE_ACCOUNT_MERGE`` - // and over `TALER_AccountMergeSignaturePS`. - reserve_sig: EddsaSignature; - - // Signature created with the purse's private key. - // Must be of purpose ``TALER_SIGNATURE_PURSE_MERGE`` - // and over `TALER_PurseMergeSignaturePS`. - 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; - } - - ------------------- -KYC status updates ------------------- - -This section describes endpoints used to set up, complete and -inquire about KYC operations performed by an exchange for -regulatory compliance. - -.. http:post:: /kyc-wallet - - The ``/kyc-wallet`` POST endpoint allows a wallet to notify an exchange if - it will cross a balance threshold. Here, the ``balance`` specified should be - the threshold (from the ``wallet_balance_limit_without_kyc`` array) that the - wallet would cross, and *not* the *exact* balance of the wallet. The exchange - will respond with a wire target UUID. The wallet can then use this UUID to - being the KYC process at ``/kyc-check/``. The wallet must only proceed to - obtain funds exceeding the threshold after the KYC process has concluded. - While wallets could be "hacked" to bypass this measure (we cannot - cryptographically enforce this), such modifications are a terms of service - violation which may have legal consequences for the user. - - Setup KYC identification for a wallet. Returns the KYC UUID. - This endpoint is used by compliant Taler wallets when they - are about to hit the balance threshold and thus need to have - the customer provide their personal details to the exchange. - The wallet is identified by its long-lived reserve public key - (which is used for P2P payments, not for withdrawals). - - **Request:** - - The request body must be a `WalletKycRequest` object. - - **Response:** - - :http:statuscode:`200 Ok`: - The balance - is below the threshold that requires KYC, or this - wallet already satisfied the KYC check for the - given balance. The `WalletKycCheckResponse` body - give details about the next applicable KYC check - requirements. - :http:statuscode:`204 No Content`: - KYC is disabled at this exchange. - :http:statuscode:`403 Forbidden`: - The provided signature is invalid. - This response comes with a standard `ErrorDetail` response. - :http:statuscode:`451 Unavailable for Legal Reasons`: - The wallet must undergo a KYC check. A KYC ID was created. - The response will be a `LegitimizationNeededResponse` object. - - **Details:** - - .. ts:def:: WalletKycRequest - - interface WalletKycRequest { - - // Balance threshold (not necessarily exact balance) - // to be crossed by the wallet that (may) trigger - // additional KYC requirements. - balance: Amount; - - // EdDSA signature of the wallet affirming the - // request, must be of purpose - // ``TALER_SIGNATURE_WALLET_ACCOUNT_SETUP`` - reserve_sig: EddsaSignature; - - // long-term wallet reserve-account - // public key used to create the signature. - reserve_pub: EddsaPublicKey; - } - - .. ts:def:: WalletKycCheckResponse - - interface WalletKycCheckResponse { - - // Next balance limit above which a KYC check - // may be required. Optional, not given if no - // threshold exists (assume infinity). - next_threshold?: Amount; - - // When does the current set of AML/KYC rules - // expire and the wallet needs to check again - // for updated thresholds. - expiration_time: Timestamp; - - } - - -.. http:get:: /kyc-check/$H_NORMALIZED_PAYTO - - Checks the KYC status of a particular payment target. That information - allows the customer to choose the next KYC measure to satisfy, if any. - This endpoint is typically used by wallets or merchants that - have been told that a transaction is not happening because it triggered - some KYC/AML measure and now want to check how the KYC/AML - requirement could be fulfilled (or whether it already has been - statisfied and the operation can now proceed). Long-polling may be used - to instantly observe a change in the KYC requirement status. - - Given a valid pair of a normalized payto hash and account owner signature, the - ``/kyc-check/`` endpoint returns either just the KYC status or redirects the - client (202) to the next required stage of the KYC process. The redirection - must be for an HTTP(S) endpoint to be triggered via a simple HTTP GET. It - must always be the same endpoint for the same client, as the wallet/merchant - backend are not required to check for changes to this endpoint. Clients - that received a 202 status code may repeat the request and use long-polling - to detect a change of the HTTP status. - - This endpoint exists in this specific form only since protocol **v21**. - - **Request:** - - *Account-Owner-Signature*: - The client must provide Base-32 encoded EdDSA signature with - ``$ACCOUNT_PRIV``, affirming the desire to obtain KYC data. Note that - this is merely a simple authentication mechanism, the details of the - request are not protected by the signature. The ``$ACCOUNT_PRIV`` is - either the (wallet long-term) reserve private key or the merchant instance - private key. - - *Account-Owner-Pub*: - The client should provide the Base-32 encoded EdDSA public key of - ``$ACCOUNT_PUB``, enabling the exchange to easily locate the correct - public key used for the signature in case multiple wire transfers - were made to the account in the past. - - :query lpt=TARGET: *Optional*. - Specifies what status change we are long-polling for. - Use 1 to wait for the KYC auth transfer (access token available), - 2 to wait for an AML investigation to be done, - and 3 to wait for the KYC status to be OK. - @since protocol **v21**. - :query min_rule=NUMBER: *Optional*. - Specifies the ``rule_num`` of the previous ``kyc-check`` - already known to the client. When long-polling, the server - should always respond when it has a more recent rule. - @since protocol **v26**. - :query timeout_ms=NUMBER: *Optional.* If specified, the exchange will - wait up to ``timeout_ms`` milliseconds if the requirement continues - to be mandatory provisioning of KYC data by the client. - Ignored if the HTTP status code is already ``200 Ok``. Note that - clients cannot long-poll for AML staff actions, so status information - about an account being under AML review needs to be requested - periodically. - - **Response:** - - :http:statuscode:`200 Ok`: - No mandatory KYC actions are required by the client at this time. - The client *may* still visit the KYC URL to initiate voluntary checks. - The response will be an `AccountKycStatus` object which specifies - restrictions that currently apply to the account. If the - client attempts to exceed *soft* limits, the status may change - to a ``202 Accepted``. Hard limits cannot be lifted by passing KYC checks. - :http:statuscode:`202 Accepted`: - The account holder performed an operation that would have crossed - limits (hard or soft) and must be redirected to the provided location to perform - the required KYC checks to satisfy the legal requirements. Afterwards, the - ``/kyc-check/`` request should be repeated to check whether the - user has completed the process. - The response will be an `AccountKycStatus` object. - :http:statuscode:`204 No content`: - The exchange is not configured to perform KYC and thus - the legal requirements are already satisfied. - :http:statuscode:`403 Forbidden`: - The provided signature is not acceptable for the given account. The response will be an `KycCheckAccountExpectedResponse` object which specifies the expected account public key. - :http:statuscode:`404 Not found`: - The account identified by the normalized payto hash is unknown. - :http:statuscode:`409 Conflict`: - The bank account is not (yet) associated with a public key. The account owner must do a wire transfer to the exchange with the account public key in the wire transfer subject to enable authentication. - - **Details:** - - .. ts:def:: KycCheckAccountExpectedResponse - - interface KycCheckAccountExpectedResponse { - - // Public key for which the signature must be - // valid to authorize this request. - expected_account_pub: EddsaPublicKey; - } - - .. ts:def:: AccountKycStatus - - interface AccountKycStatus { - - // Current AML state for the target account. True if - // operations are not happening due to staff processing - // paperwork *or* due to legal requirements (so the - // client cannot do anything but wait). - // - // Note that not every AML staff action may be legally - // exposed to the client, so this is merely a hint that - // a client should be told that AML staff is currently - // reviewing the account. AML staff *may* review - // accounts without this flag being set! - aml_review: boolean; - - // Monotonically increasing number identifying the decision. - // 0 if no decision was taken for this account. Useful for - // long-polling via ``min_rule`` to long-poll for any change - // to the rules or limits. - rule_gen: Integer; - - // Access token needed to construct the ``/kyc-spa/`` - // URL that the user should open in a browser to - // proceed with the KYC process (optional if the status - // type is ``200 Ok``, mandatory if the HTTP status - // is ``202 Accepted``). - access_token: AccountAccessToken; - - // Array with limitations that currently apply to this - // account and that may be increased or lifted if the - // KYC check is passed. - // Note that additional limits *may* exist and not be - // communicated to the client. If such limits are - // reached, this *may* be indicated by the account - // going into ``aml_review`` state. However, it is - // also possible that the exchange may legally have - // to deny operations without being allowed to provide - // any justification. - // The limits should be used by the client to - // possibly structure their operations (e.g. withdraw - // what is possible below the limit, ask the user to - // pass KYC checks or withdraw the rest after the time - // limit is passed, warn the user to not withdraw too - // much or even prevent the user from generating a - // request that would cause it to exceed hard limits). - limits?: AccountLimit[]; - - } - -.. http:get:: /kyc-spa/$ACCESS_TOKEN -.. http:get:: /kyc-spa/$FILENAME - - A set of ``/kyc-spa/$ACCESS_TOKEN`` GET endpoints is created per account - hash that serves the KYC SPA. This is where the ``/kyc-check/`` endpoint - will in principle redirect clients. The KYC SPA will use the - ``$ACCESS_TOKEN`` of its URL to initialize itself via the - ``/kyc-info/$ACCESS_TOKEN`` endpoint family. The KYC SPA may download - additional resources via ``/kyc-spa/$FILENAME``. The filenames must not - match base32-encoded 256-bit values. - - This endpoint was introduced in protocol **v20**. - - -.. http:get:: /kyc-info/$ACCESS_TOKEN - - The ``/kyc-info/$ACCESS_TOKEN`` endpoints are created per client - account hash (but access controlled via a unique target token) - to return information about the state of the KYC or AML process - to the KYC SPA. The SPA uses this information to show the user an - appropriate dialog. The SPA should also long-poll this endpoint for changes - to the AML/KYC state. Note that this is a client-facing endpoint, so it will - only provide a restricted amount of information to the customer (as some - laws may forbid us to inform particular customers about their true status). - The endpoint will typically inform the SPA about possible choices to - proceed, such as directly uploading files, contacting AML staff, or - proceeding with a particular KYC process at an external provider (such as - Challenger). If the user chooses to initate a KYC process at an external - provider, the SPA must request the respective process to be set-up by the - exchange via the ``/kyc-start/`` endpoint. - - This endpoint was introduced in protocol **v20**. - - **Request:** - - *If-None-Match*: - The client MAY provide an ``If-None-Match`` header with an ETag. - - :query timeout_ms=MILLISECONDS: - *Optional.* If specified, the exchange will wait up to MILLISECONDS for - a change to a more recent legitimization measure before returning a 304 - Not Modified status. - - **Response:** - - *Etag*: Will be set to the serial ID of the measure. Used for long-polling (only for 200 OK responses). - - :http:statuscode:`200 OK`: - The body is a `KycProcessClientInformation`. - :http:statuscode:`202 Accepted`: - The exchange is currently processing the KYC status. The request should be - repeated later again. - :http:statuscode:`204 No Content`: - There are no open KYC requirements or possible voluntary checks - the client might perform. - :http:statuscode:`304 Not Modified`: - The KYC requirements did not change. - - - **Details:** - - .. ts:def:: KycProcessClientInformation - - interface KycProcessClientInformation { - - // Array of requirements. - requirements: KycRequirementInformation[]; - - // True if the client is expected to eventually satisfy all requirements. - // Default (if missing) is false. - is_and_combinator?: boolean - - // List of available voluntary checks the client could undertake. - // @since protocol **vATTEST**. - voluntary_measures?: KycRequirementInformation[]; - } - - .. ts:def:: KycRequirementInformation - - interface KycRequirementInformation { - - // Which form should be used? Common values include "INFO" - // (to just show the descriptions but allow no action), - // "LINK" (to enable the user to obtain a link via - // ``/kyc-start/``) or any build-in form name supported - // by the SPA. - form: string; - - // Object with arbitrary additional context, completely depends on - // the specific form. - context?: Object; - - // English description of the requirement. - description: string; - - // Map from IETF BCP 47 language tags to localized - // description texts. - description_i18n ?: { [lang_tag: string]: string }; - - // ID of the requirement, useful to construct the - // ``/kyc-upload/$ID`` or ``/kyc-start/$ID`` endpoint URLs. - // Present if and only if "form" is not "INFO". The - // ``$ID`` value may itself contain ``/`` or ``?`` and - // basically encode any URL path (and optional arguments). - id?: string; - - } - -.. http:post:: /kyc-upload/$ID - - The ``/kyc-upload/$ID`` POST endpoint allows the SPA to upload - client-provided evidence. The ``$ID`` will be provided as part of the - ``/kyc-info`` body. This is for checks of type ``FORM``. In practice, - ``$ID`` will encode the ``$ACCESS_TOKEN``, legitimization measure serial ID - (to disambiguate) and the index of the selected measure (but these details - should be irrelevant for the client). - - This endpoint was introduced in protocol **v20**. - - **Request:** - - JSON body with data depending on the form being submitted. - Details will thus completely depend on the form, but it - MUST include a form ID and be generally of type - `CustomerKycAttributes`. - - **Response:** - - :http:statuscode:`204 No Content`: - The information was successfully uploaded. The SPA should fetch - an updated ``/kyc-info/``. - :http:statuscode:`404 Not Found`: - The ``$ID`` is unknown to the exchange. - :http:statuscode:`409 Conflict`: - The upload conflicts with a previous upload. - :http:statuscode:`413 Request Entity Too Large`: - The body is too large. - -.. http:post:: /kyc-start/$ID - - The ``/kyc-start/$ID`` POST endpoint allows the SPA to set up a new external - KYC process. It will return the URL that the client must GET to begin the - KYC process. The SPA should probably open this URL in a new window or tab. - The ``$ID`` will be provided as part of the ``/kyc-info`` body. In - practice, ``$ID`` will encode the ``$ACCESS_TOKEN``, legitimization measure - serial ID (to disambiguate) and the index of the selected measure (but these - details should be irrelevant for the client). - - **Request:** - - Use empty JSON body for now. - - **Response:** - - :http:statuscode:`200 Ok`: - The KYC process was successfully initiated. The URL is in a - `KycProcessStartInformation` object. - :http:statuscode:`404 Not Found`: - The ``$ID`` is unknown to the exchange. - - **Details:** - - .. ts:def:: KycProcessStartInformation - - interface KycProcessStartInformation { - - // URL to open. - redirect_url: string; - } - - .. note:: - - As this endpoint is involved in every KYC check at the beginning, this - is also the place where we could integrate the payment process for the KYC fee - in the future (since protocol **vATTEST**). - -.. http:get:: /kyc-proof/$PROVIDER_NAME?state=$H_NORMALIZED_PAYTO - - Upon completion of the process at the external KYC provider, the provider - must redirect the client (browser) to trigger a GET request to a new - ``/kyc-proof/$H_NORMALIZED_PAYTO/$PROVIDER_NAME`` endpoint. Once this endpoint is - triggered, the exchange will pass the received arguments to the respective - logic plugin. The logic plugin will then (asynchronously) update the KYC - status of the user. The logic plugin should redirect the user to the KYC - SPA. This endpoint deliberately does not use the ``$ACCESS_TOKEN`` as the - external KYC provider should not learn that token. - - This endpoint is thus accessed from the user's browser at the *end* of a - KYC process, possibly providing the exchange with additional - credentials to obtain the results of the KYC process. - Specifically, the URL arguments should provide - information to the exchange that allows it to verify that the - user has completed the KYC process. The details depend on - the logic, which is selected by the "$PROVIDER_NAME". - - While this is a GET (and thus safe, and idempotent), the operation - may actually trigger significant changes in the exchange's state. - In particular, it may update the KYC status of a particular - payment target. - - **Request:** - - Details on the request depend on the specific KYC logic - that was used. - - If the KYC plugin logic is OAuth 2.0, the query parameters are: - - :query code=CODE: - OAuth 2.0 code argument. - :query state=STATE: - OAuth 2.0 state argument with the H_NORMALIZED_PAYTO. - - .. note:: - - Depending on the OAuth variant used, additional - query parameters may need to be passed here. - - **Response:** - - Given that the response is returned to a user using a browser and **not** to - a Taler wallet, the response format is in human-readable HTML and not in - machine-readable JSON. - - :http:statuscode:`302 Found`: - The KYC operation succeeded and the - payment target is now authorized to transact. - The browser is redirected to a human-readable - page configured by the exchange operator. - :http:statuscode:`401 Unauthorized`: - The provided authorization token is invalid. - :http:statuscode:`404 Not found`: - The payment target is unknown. - :http:statuscode:`502 Bad Gateway`: - The exchange received an invalid reply from the - legitimization service. - :http:statuscode:`504 Gateway Timeout`: - The exchange did not receive a reply from the legitimization - service within a reasonable time period. - - -.. http:get:: /kyc-webhook/$PROVIDER_NAME/* -.. http:post:: /kyc-webhook/$PROVIDER_NAME/* -.. http:get:: /kyc-webhook/$LOGIC/* -.. http:post:: /kyc-webhook/$LOGIC/* - - All of the above endpoints can be used to update KYC status of a particular - payment target. They provide information to the KYC logic of the exchange - that allows it to verify that the user has completed the KYC process. May - be a GET or a POST request, depending on the specific "$LOGIC" and/or the - "$PROVIDER_NAME". - - **Request:** - - Details on the request depend on the specific KYC logic - that was used. - - **Response:** - - :http:statuscode:`204 No content`: - The operation succeeded. - :http:statuscode:`404 Not found`: - The specified logic is unknown. - - --------------- -AML operations --------------- - -This API is only for designated AML officers. It is used -to allow exchange staff to monitor suspicious transactions -and freeze or unfreeze accounts suspected of money laundering. - - -.. http:get:: /aml/$OFFICER_PUB/measures - - To enable the AML staff SPA to give AML staff a choice of possible measures, a - new endpoint ``/aml/$OFFICER_PUB/measures`` is added that allows the AML SPA - to dynamically GET the list of available measures. It returns a list of known - KYC checks (by name) with their descriptions and a list of AML programs with - information about the required context. - - This endpoint was introduced in protocol **v20**. - - **Request:** - - *Taler-AML-Officer-Signature*: - The client must provide Base-32 encoded EdDSA signature with - ``$OFFICER_PRIV``, affirming the desire to obtain AML data. Note that - this is merely a simple authentication mechanism, the details of the - request are not protected by the signature. - - **Response:** - - :http:statuscode:`200 Ok`: - Information about possible measures is returned in a - `AvailableMeasureSummary` object. - - **Details:** - - .. ts:def:: AvailableMeasureSummary - - interface AvailableMeasureSummary { - - // Available original measures that can be - // triggered directly by default rules. - roots: { "$measure_name" : MeasureInformation; }; - - // Available AML programs. - programs: { "$prog_name" : AmlProgramRequirement; }; - - // Available KYC checks. - checks: { "$check_name" : KycCheckInformation; }; - - // Default KYC rules. This is the set of KYC rules that - // applies by default to new "accounts". Note that some - // rules only apply to wallets, while others only apply to - // bank accounts. The returned array is the union of all - // possible rules, applications should consider the - // ``operation_type`` to filter for rules that actually - // apply to a specific situation. - // @since protocol **v28**. - default_rules: KycRule[]; - - } - - .. ts:def:: MeasureInformation - - interface MeasureInformation { - - // Name of a KYC check. - check_name: string; - - // Name of an AML program. - // Optional @since protocol **v30**. - prog_name?: string; - - // Context for the check. Optional. - context?: Object; - - // Operation that this measure relates to. - // NULL if unknown. Useful as a hint to the - // user if there are many (voluntary) measures - // and some related to unlocking certain operations. - // (and due to zero-amount thresholds, no measure - // was actually specifically triggered). - // - // Must be one of "WITHDRAW", "DEPOSIT", - // (p2p) "MERGE", (wallet) "BALANCE", - // (reserve) "CLOSE", "AGGREGATE", - // "TRANSACTION" or "REFUND". - // @since protocol **v21**. - operation_type?: string; - - // Can this measure be undertaken voluntarily? - // Optional, default is false. - // @since protocol **vATTEST**. - voluntary?: boolean; - - } - - .. ts:def:: AmlProgramRequirement - - interface AmlProgramRequirement { - - // Description of what the AML program does. - description: string; - - // List of required field names in the context to run this - // AML program. SPA must check that the AML staff is providing - // adequate CONTEXT when defining a measure using this program. - context: string[]; - - // List of required attribute names in the - // input of this AML program. These attributes - // are the minimum that the check must produce - // (it may produce more). - inputs: string[]; - - } - - .. ts:def:: KycCheckInformation - - interface KycCheckInformation { - - // Description of the KYC check. Should be shown - // to the AML staff but will also be shown to the - // client when they initiate the check in the KYC SPA. - description: string; - - // Map from IETF BCP 47 language tags to localized - // description texts. - description_i18n ?: { [lang_tag: string]: string}; - - // Names of the fields that the CONTEXT must provide - // as inputs to this check. - // SPA must check that the AML staff is providing - // adequate CONTEXT when defining a measure using - // this check. - requires: string[]; - - // Names of the attributes the check will output. - // SPA must check that the outputs match the - // required inputs when combining a KYC check - // with an AML program into a measure. - outputs: string[]; - - // Name of a root measure taken when this check fails. - fallback: string; - } - -.. http:get:: /aml/$OFFICER_PUB/kyc-statistics/$NAMES - - Returns the number of KYC events matching the space-separated (!) - list of given event types ``$NAMES`` in - the specified time range. Note that this query can be slow as the - statistics are computed on-demand. (This is OK as such requests should be - rare.) - - This endpoint was introduced in protocol **v20**. It was - modified in **v30** revision 1 to accept a space-separated - list of names instead of a single name and to return an array - of results. - - **Request:** - - *Taler-AML-Officer-Signature*: - The client must provide Base-32 encoded EdDSA signature with - ``$OFFICER_PRIV``, affirming the desire to obtain AML data. Note that this - is merely a simple authentication mechanism, the details of the request are - not protected by the signature. - - :query start_date=TIMESTAMP: - *Optional*. Specifies the date when to - start looking (inclusive). If not given, the start time of the - exchange operation is used. The TIMESTAMP is given - in seconds since the UNIX epoch. - :query end_date=TIMESTAMP: - *Optional*. Specifies the date when to - stop looking (exclusive). If not given, the current date is used. The TIMESTAMP is given - in seconds since the UNIX epoch. - - **Response:** - - :http:statuscode:`200 OK`: - The responds will be an `AmlStatisticsResponse` message. - :http:statuscode:`204 No content`: - All the event counters are zero. - - **Details:** - - .. ts:def:: AmlStatisticsResponse - - interface AmlStatisticsResponse { - // Statistics that were found. - statistics: EventCounter[]; - } - - - .. ts:def:: EventCounter - - interface EventCounter { - // Name of the statistic that is being returned. - name: string; - - // Number of events of the specified type in - // the given range. - counter: Integer; - - } - - -.. http:get:: /aml/$OFFICER_PUB/decisions - - **Request:** - - *Taler-AML-Officer-Signature*: - The client must provide Base-32 encoded EdDSA signature with - ``$OFFICER_PRIV``, affirming the desire to obtain AML data. Note that - this is merely a simple authentication mechanism, the details of the - request are not protected by the signature. - - This endpoint was introduced in this form in protocol **v20**. - - :query limit: - *Optional*. takes value of the form ``N (-N)``, so that at - most ``N`` values strictly older (younger) than ``start`` are returned. - Defaults to ``-20`` to return the last 20 entries (before ``start``). - :query offset: - *Optional*. Row number threshold, see ``delta`` for its - interpretation. Defaults to ``INT64_MAX``, namely the biggest row id - possible in the database. - :query h_payto: - *Optional*. Account selector using the *normalized* payto URI. - All matching accounts are returned if this - filter is absent, otherwise only decisions for this account. - :query active: - *Optional*. If set to yes, only return active decisions, if no only - decisions that have been superseded. Do not give (or use "all") to - see all decisions regardless of activity status. - :query investigation: - *Optional*. If set to yes, only return accounts that are under - AML investigation, if no only accounts that are not under investigation. - Do not give (or use "all") to see all accounts regardless of - investigation status. - - **Response:** - - :http:statuscode:`200 OK`: - The response will be an `AmlDecisionsResponse` message. - :http:statuscode:`204 No content`: - There are no matching AML records. - :http:statuscode:`403 Forbidden`: - The signature is invalid. - :http:statuscode:`404 Not found`: - The designated AML account is not known. - :http:statuscode:`409 Conflict`: - The designated AML account is not enabled. - - **Details:** - - .. ts:def:: AmlDecisionsResponse - - interface AmlDecisionsResponse { - - // Array of AML decisions matching the query. - records: AmlDecision[]; - } - - .. ts:def:: AmlDecision - - interface AmlDecision { - - // Which payto-address is this record about. - // Identifies a GNU Taler wallet or an affected bank account. - h_payto: NormalizedPaytoHash; - - // Full payto URL of the account that the decision is - // about. Since protocol **v30** (rev 1). - full_payto: string; - - // True if the underlying payto://-URI is for a wallet - // Since protocol **v25**. - is_wallet: boolean; + interface CoinHistoryResponse { + // Current balance of the coin. + balance: Amount; - // Row ID of the record. Used to filter by offset. - rowid: Integer; + // Hash of the coin's denomination. + h_denom_pub: HashCode; - // Justification for the decision. NULL if none - // is available. - justification?: string; + // Transaction history for the coin. + history: CoinSpendHistoryItem[]; + } - // When was the decision made? - decision_time: Timestamp; +.. ts:def:: CoinSpendHistoryItem - // Free-form properties about the account. - // Can be used to store properties such as PEP, - // risk category, type of business, hits on - // sanctions lists, etc. - // - // Passing a properties object overrides all - // of the current properties for the AML account. - properties?: AccountProperties; - - // What are the new rules? - limits: LegitimizationRuleSet; - - // True if the account is under investigation by AML staff - // after this decision. - to_investigate: boolean; - - // True if this is the active decision for the - // account. - is_active: boolean; - - } - - .. ts:def:: AccountProperties - - // All fields in this object are optional. The actual - // properties collected depend fully on the discretion - // of the exchange operator; - // however, some common fields are standardized - // and thus described here. - interface AccountProperties { - - // True if this is a politically exposed account. - // Rules for classifying accounts as politically - // exposed are country-dependent. - pep?: boolean; - - // True if this is a sanctioned account. - // Rules for classifying accounts as sanctioned - // are country-dependent. - sanctioned?: boolean; - - // True if this is a high-risk account. - // Rules for classifying accounts as at-risk - // are exchange operator-dependent. - high_risk?: boolean; - - // Business domain of the account owner. - // The list of possible business domains is - // operator- or country-dependent. - business_domain?: string; + // Union discriminated by the "type" field. + type CoinSpendHistoryItem = + | CoinDepositTransaction + | CoinMeltTransaction + | CoinRefundTransaction + | CoinRecoupWithdrawTransaction + | CoinRecoupRefreshTransaction + | CoinRecoupRefreshReceiverTransaction + | CoinPurseDepositTransaction + | CoinPurseRefundTransaction + | CoinReserveOpenDepositTransaction; - // Is the client's account currently frozen? - is_frozen?: boolean; - - // Was the client's account reported to the authorities? - was_reported?: boolean; +.. ts:def:: CoinDepositTransaction - } + interface CoinDepositTransaction { + type: "DEPOSIT"; - .. ts:def:: LegitimizationRuleSet + // Offset of this entry in the reserve history. + // Useful to request incremental histories via + // the "start" query parameter. + history_offset: Integer; - interface LegitimizationRuleSet { + // The total amount of the coin's value absorbed (or restored in the + // case of a refund) by this transaction. + // The amount given includes + // the deposit fee. The current coin value can thus be computed by + // subtracting this amount. + amount: Amount; - // When does this set of rules expire and - // we automatically transition to the successor - // measure? - expiration_time: Timestamp; - - // Name of the measure to apply when the expiration time is - // reached. If not set, we refer to the default - // set of rules (and the default account state). - successor_measure?: string; - - // Legitimization rules that are to be applied - // to this account. - rules: KycRule[]; - - // Custom measures that KYC rules and the - // ``successor_measure`` may refer to. - custom_measures: { "$measure_name" : MeasureInformation; }; - - } - - .. ts:def:: KycRule - - interface KycRule { - - // Type of operation to which the rule applies. - // - // Must be one of "WITHDRAW", "DEPOSIT", - // (p2p) "MERGE", (wallet) "BALANCE", - // (reserve) "CLOSE", "AGGREGATE", - // "TRANSACTION" or "REFUND". - operation_type: string; - - // Name of the configuration section this rule - // originates from. Not available for all rules. - // Primarily informational, but also useful to - // explicitly manipulate rules by-name in AML programs. - rule_name?: string; - - // The measures will be taken if the given - // threshold is crossed over the given timeframe. - threshold: Amount; - - // Over which duration should the ``threshold`` be - // computed. All amounts of the respective - // ``operation_type`` will be added up for this - // duration and the sum compared to the ``threshold``. - timeframe: RelativeTime; + // Deposit fee. + deposit_fee: Amount; - // Array of names of measures to apply. - // Names listed can be original measures or - // custom measures from the `AmlOutcome`. - // A special measure "verboten" is used if the - // threshold may never be crossed. - measures: string[]; + // Public key of the merchant. + merchant_pub: EddsaPublicKey; - // If multiple rules apply to the same account - // at the same time, the number with the highest - // rule determines which set of measures will - // be activated and thus become visible for the - // user. - display_priority: Integer; + // Date when the operation was made. + timestamp: Timestamp; - // True if the rule (specifically, operation_type, - // threshold, timeframe) and the general nature of - // the measures (verboten or approval required) - // should be exposed to the client. - // Defaults to "false" if not set. - exposed?: boolean; + // Date until which the merchant can issue a refund to the customer via the + // exchange, possibly zero if refunds are not allowed. + refund_deadline?: Timestamp; - // True if all the measures will eventually need to - // be satisfied, false if any of the measures should - // do. Primarily used by the SPA to indicate how - // the measures apply when showing them to the user; - // in the end, AML programs will decide after each - // measure what to do next. - // Default (if missing) is false. - is_and_combinator?: boolean; + // Hash over the proposal data of the contract that + // is being paid. + h_contract_terms: HashCode; - } + // Hash of the bank account from where we received the funds. + h_wire: HashCode; + // Hash of the public denomination key used to sign the coin. + // Needed because 'coin_sig' signs over this, and + // that is important to fix the coin's denomination. + h_denom_pub: HashCode; -.. http:get:: /aml/$OFFICER_PUB/legitimizations + // Hash over the deposit policy extension. Optional. + h_policy?: HashCode; - Enables AML staff to see which legitimizations are pending - or have been completed. - returns a list of active or finished legitimization - measures (by account). + // Hash over auxiliary wallet data provided by the wallet + // to complete the contract. Optional. + wallet_data_hash?: HashCode; - This endpoint was introduced in protocol **v23**. - - **Request:** - - *Taler-AML-Officer-Signature*: - The client must provide Base-32 encoded EdDSA signature with - ``$OFFICER_PRIV``, affirming the desire to obtain AML data. Note that - this is merely a simple authentication mechanism, the details of the - request are not protected by the signature. - - :query limit: - *Optional*. takes value of the form ``N (-N)``, so that at - most ``N`` values strictly older (younger) than ``start`` are returned. - Defaults to ``-20`` to return the last 20 entries (before ``start``). - :query offset: - *Optional*. Row number threshold, see ``limit`` for its - interpretation. Defaults to ``INT64_MAX``, namely the biggest row id - possible in the database. - :query h_payto: - *Optional*. Account selector using the *normalized* payto URI. - All matching accounts are returned if this - filter is absent, otherwise only decisions for this account. - :query active: - *Optional*. If set to yes, only return active decisions, if no only - decisions that have been superseded. Do not give (or use "all") to - see all decisions regardless of activity status. - - **Response:** - - :http:statuscode:`200 Ok`: - Information about possible measures is returned in a - `LegitimizationMeasuresList` object. - - **Details:** - - .. ts:def:: LegitimizationMeasuresList - - interface LegitimizationMeasuresList { - - // Legitimization measures. - measures: LegitimizationMeasureDetails[]; - - } - - .. ts:def:: LegitimizationMeasureDetails - - interface LegitimizationMeasureDetails { - - // Hash of the normalized payto:// URI of the account the - // measure applies to. - h_payto: HashCode; - - // Row of the measure in the exchange database. - rowid: Integer; - - // When was the measure started? - start_time: Timestamp; - - // The the actual measures. - measures: LegitimizationMeasures; - - // Was this measure finished by the customer? - is_finished: boolean; - - } + // Hash over the age commitment of the coin. Optional. + h_age_commitment?: HashCode; + // Signature over `TALER_DepositRequestPS`, made by the customer with the + // `coin's private key <coin-priv>`. + coin_sig: EddsaSignature; -.. http:get:: /aml/$OFFICER_PUB/accounts + } - **Request:** +.. ts:def:: CoinMeltTransaction - *Accept*: - The client may specify the desired MIME-type for the result. - Supported are the usual "application/json", but also - "text/csv" (RFC 4180) - and "application/vnd.ms-excel" (XML of Excel 2003). + interface CoinMeltTransaction { + type: "MELT"; - *Taler-AML-Officer-Signature*: - The client must provide Base-32 encoded EdDSA signature with - ``$OFFICER_PRIV``, affirming the desire to obtain AML data. Note that - this is merely a simple authentication mechanism, the details of the - request are not protected by the signature. + // Offset of this entry in the reserve history. + // Useful to request incremental histories via + // the "start" query parameter. + history_offset: Integer; - This endpoint was introduced in this form in protocol **v31**. + // 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 + // subtracting the amounts. + amount: Amount; - :query limit: - *Optional*. takes value of the form ``N (-N)``, so that at - most ``N`` values strictly older (younger) than ``start`` are returned. - Defaults to ``-20`` to return the last 20 entries (before ``start``). - :query offset: - *Optional*. Row number threshold, see ``delta`` for its - interpretation. Defaults to ``INT64_MAX``, namely the biggest row id - possible in the database. - :query investigation: - *Optional*. If set to yes, only return accounts that are under - AML investigation, if no only accounts that are not under investigation. - Do not give (or use "all") to see all accounts regardless of - investigation status. - :query open: - *Optional*. If set to yes, only return accounts that are open, - which means they crossed some initial limit and now have custom rules. - Do not give (or use "all") to see all accounts regardless of - open status. - :query high_risk: - *Optional*. If set to yes, only return accounts that were flagged - as high risk. - Do not give (or use "all") to see all accounts regardless of - risk status. + // Melt fee. + melt_fee: Amount; - **Response:** + // Commitment from the melt operation, see `TALER_RefreshCommitmentP` + rc: HashCode; - :http:statuscode:`200 OK`: - The response will be an `AmlAccountsResponse` message (if the - client's header "Accept" was "application/json"). Other encodings - may differ in the exact returned data and format. - :http:statuscode:`204 No content`: - There are no accounts at all (within the specified constraints). - :http:statuscode:`403 Forbidden`: - The signature is invalid. - :http:statuscode:`404 Not found`: - The designated AML account is not known. - :http:statuscode:`409 Conflict`: - The designated AML account is not enabled. + // Hash of the public denomination key used to sign the old coin. + // Needed because 'coin_sig' signs over this, and + // that is important to fix the coin's denomination. + old_denom_pub_h: HashCode; - **Details:** + // Hash over the age commitment of the coin. Optional. + old_age_commitment_h?: AgeCommitmentHash; - .. ts:def:: AmlAccountsResponse + // @since **v32** + // This value is opaque to the exchange. It was provided by the client + // as part of the original refresh request, and was therefore verified + // with the confirm_sig below. + // If the reveal step was not performed yet by the old coin owner, + // they can use this value and the old coin's private key to derive + // all indivual seeds for the n*κ coin candidates for the original + // refresh request and replay it + refresh_seed: HashCode; - interface AmlAccountsResponse { + // @since **v32** + // The kappa*n list of transfer public keys that were provided by the + // old coin owner during the melt request. + transfer_pubs: EddsaPublicKey[kappa][]; - // Array of customer accounts matching the query. - accounts: CustomerAccountSummary[]; - } + // @since **v32** + // The n denomination public keys for the fresh coins + // that the coin owner had requested. + denoms_h: HashCode[]; - .. ts:def:: CustomerAccountSummary + // @since **v32** + // The ``noreveal_index`` value that was returned by the exchange as response + // to the melt request. + noreveal_index: Integer; - interface CustomerAccountSummary { + // @since **v32** + // If the reveal step was successfully peformed by the coin owner, + // this field contains the blind coin signatures that were returned + // by the exchange for the chosen batch of coins. + ev_sigs?: BlindedDenominationSignature[]; - // Which payto-address is this record about. - // Identifies a GNU Taler wallet or an affected bank account. - h_payto: NormalizedPaytoHash; + // Master seed for the Clause-Schnorr R-value + // Present if one of the fresh coin's + // denominations is of type Clause-Schnorr. + blinding_seed?: BlindingMasterSeed; - // Full payto URL of the account that the decision is - // about. - full_payto: string; + // Signature by the coin over a + // `TALER_RefreshMeltCoinAffirmationPS` of + // purpose ``TALER_SIGNATURE_WALLET_COIN_MELT``. + confirm_sig: EddsaSignature; - // True if the account was assessed as being high risk. - high_risk: boolean; - - // Latest comments about the account (if any). - comments?: string; - - // Row of the account in the exchange tables. Useful to filter - // by offset. - rowid: Integer; - - // When was the account opened? "never" if it was never opened. - open_time: Timestamp; - - // When was the account opened? "never" if it was never closed. - close_time: Timestamp; + } - // True if the account is under investigation by AML staff - // after this decision. - to_investigate: boolean; +.. ts:def:: CoinRefundTransaction - } - - .. ts:def:: AccountProperties - - // All fields in this object are optional. The actual - // properties collected depend fully on the discretion - // of the exchange operator; - // however, some common fields are standardized - // and thus described here. - interface AccountProperties { - - // True if this is a politically exposed account. - // Rules for classifying accounts as politically - // exposed are country-dependent. - pep?: boolean; + interface CoinRefundTransaction { + type: "REFUND"; - // True if this is a sanctioned account. - // Rules for classifying accounts as sanctioned - // are country-dependent. - sanctioned?: boolean; + // Offset of this entry in the reserve history. + // Useful to request incremental histories via + // the "start" query parameter. + history_offset: Integer; - // True if this is a high-risk account. - // Rules for classifying accounts as at-risk - // are exchange operator-dependent. - high_risk?: boolean; + // The total amount of the coin's value restored + // by this transaction. + // The amount given excludes the transaction fee. + // The current coin value can thus be computed by + // adding the amounts to the coin's denomination value. + amount: Amount; - // Business domain of the account owner. - // The list of possible business domains is - // operator- or country-dependent. - business_domain?: string; + // Refund fee. + refund_fee: Amount; - // Is the client's account currently frozen? - is_frozen?: boolean; + // Hash over the proposal data of the contract that + // is being refunded. + h_contract_terms: HashCode; - // Was the client's account reported to the authorities? - was_reported?: boolean; + // Public key of the merchant. + merchant_pub: EddsaPublicKey; - } + // Refund transaction ID. + rtransaction_id: Integer; + // `EdDSA Signature <eddsa-sig>` authorizing the REFUND over a + // `TALER_MerchantRefundConfirmationPS` with + // purpose ``TALER_SIGNATURE_MERCHANT_REFUND_OK``. Made with + // the `public key of the merchant <merchant-pub>`. + merchant_sig: EddsaSignature; + } -.. http:get:: /aml/$OFFICER_PUB/attributes/$H_NORMALIZED_PAYTO - Obtain attributes obtained as part of AML/KYC processes for a - given account. +.. note:: - This endpoint was introduced in protocol **v20**. + The `CoinRecoupWithdrawTransaction` interface defintion is WIP. + It will be fully specified and implemented with **vRECOUP**. - **Request:** +.. ts:def:: CoinRecoupWithdrawTransaction - *Accept*: - The client may specify the desired MIME-type for the result. - Supported are the usual "application/json", but also - "application/pdf". + // This represents a transaction of a call to /recoup-withdraw + // where the coin's residual value has been credited to the + // original reserve, from which this coin was withdrawn. + interface CoinRecoupWithdrawTransaction { + type: "RECOUP-WITHDRAW"; - *Taler-AML-Officer-Signature*: - The client must provide Base-32 encoded EdDSA signature with - ``$OFFICER_PRIV``, affirming the desire to obtain AML data. Note that - this is merely a simple authentication mechanism, the details of the - request are not protected by the signature. + // Offset of this entry in the reserve history. + // Useful to request incremental histories via + // the "start" query parameter. + history_offset: Integer; - :query limit: - *Optional*. takes value of the form ``N (-N)``, so that at - most ``N`` values strictly older (younger) than ``start`` are returned. - Defaults to ``-20`` to return the last 20 entries (before ``start``). - :query offset: - *Optional*. Row number threshold, see ``delta`` for its - interpretation. Defaults to ``INT64_MAX``, namely the biggest row id - possible in the database. + // The total amount of the coin's value absorbed + // by this transaction. + // The current coin value can thus be computed by + // subtracting the amount from + // the coin's denomination value. + amount: Amount; - **Response:** + // Signature by the exchange over a + // `TALER_RecoupConfirmationPS`, must be + // of purpose ``TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP``. + exchange_sig: EddsaSignature; - :http:statuscode:`200 OK`: - The responds will be an `KycAttributes` message. - :http:statuscode:`204 No content`: - There are no matching KYC attributes. - :http:statuscode:`403 Forbidden`: - The signature is invalid. - :http:statuscode:`404 Not found`: - The designated AML account is not known. - :http:statuscode:`409 Conflict`: - The designated AML account is not enabled. - :http:statuscode:`501 Not implemented`: - The requested functionality is not implemented. - Usually returned if the PDF generator is not available - at this backend and the requested format was application/pdf. + // Public key of the private key used to create 'exchange_sig'. + exchange_pub: EddsaPublicKey; - .. ts:def:: KycAttributes + // Signature by the coin over a + // `TALER_RecoupRequestPS` with purpose + // ``TALER_SIGNATURE_WALLET_COIN_RECOUP``. + coin_sig: EddsaSignature; - interface KycAttributes { + // Hash of the public denomination key used to sign the coin. + // Needed because 'coin_sig' signs over this, and + // that is important to fix the coin's denomination. + h_denom_pub: HashCode; - // Matching KYC attribute history of the account. - details: KycAttributeCollectionEvent[]; + // Coin blinding key that was used in the original withdraw request. + coin_blind: DenominationBlindingKeyP; - } + // The hash of the withdraw commitment of the original withdraw + // request that this coin was part of + h_commitment: HashCode; - .. ts:def:: KycAttributeCollectionEvent + // Coin's index in the original withdraw request, starting at 0 + coin_index: Integer; - interface KycAttributeCollectionEvent { + // Reserve receiving the recoup. + reserve_pub: EddsaPublicKey; - // Row ID of the record. Used to filter by offset. - rowid: Integer; + // Date when the operation was made. + timestamp: Timestamp; - // True if the attributes were filed by an AML officer, - // otherwise they were provided directly by the customer. - by_aml_officer: boolean; + } - // The collected KYC data. NULL if the attribute data could not - // be decrypted (internal error of the exchange, likely the - // attribute key was changed). - attributes?: CustomerKycAttributes; - // Time when the KYC data was collected - collection_time: Timestamp; +.. note:: - } + The `CoinRecoupRefreshTransaction` interface defintion is WIP. + It will be fully specified and implemented with **vRECOUP**. - .. ts:def:: CustomerKycAttributes +.. ts:def:: CoinRecoupRefreshTransaction - interface CustomerKycAttributes { + // This represents a transaction of a call to /recoup-refresh + // where this coin was _part_ of the batch of coins whose + // residual values were credited to the original coin, from + // which also this coin was refresh from. + interface CoinRecoupRefreshTransaction { + type: "RECOUP-REFRESH"; - // ID of the Form that was used to submit the attributes and/or - // that should be used to *render* the attributes. - // Mandatory since **v31**. - FORM_ID: string; + // Offset of this entry in the reserve history. + // Useful to request incremental histories via + // the "start" query parameter. + history_offset: Integer; - // Version of the form completed by the user. - FORM_VERSION?: Integer; + // The total amount of the coin's value absorbed + // by this transaction. + // The current coin value can thus be computed by + // subtracting this amounts from + // the coin's denomination value. + amount: Amount; - // High entropy value used in forms where hash is going to be stored in - // plain text. - FORM?_SALT: string; + // Signature by the exchange over a + // `TALER_RecoupRefreshConfirmationPS` + // of purpose ``TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_REFRESH``. + exchange_sig: EddsaSignature; - // Attributes can have basically arbitrary additional - // Key-value pairs. See the - // gnu-taler-form-attributes registry in GANA - // for possible keys! + // Public key used to sign 'exchange_sig'. + exchange_pub: EddsaPublicKey; - } + // The original coin, from which this coin was derived from + // in a call to /refresh, and which was then credited with + // the residual value of this coin in a call to /recoup-refresh. + old_coin_pub: EddsaPublicKey; + // Signature by the coin over a `TALER_RecoupRequestPS` + // with purpose ``TALER_SIGNATURE_WALLET_COIN_RECOUP``. + coin_sig: EddsaSignature; -.. http:post:: /aml/$OFFICER_PUB/decision + // Hash of the public denomination key used to sign the coin. + // Needed because 'coin_sig' signs over this, and + // that is important to fix the coin's denomination. + h_denom_pub: HashCode; - Make an AML decision. Triggers the respective action and - records the justification. + // Coin blinding key that was used in the original refresh request. + coin_blind: DenominationBlindingKeyP; - **Request:** + // The hash of the refresh commitment of the original refresh + // request that this coin was derived from. + h_commitment: HashCode; - The request must be an `AmlDecisionRequest` message. + // Coin's index in the original refresh request, starting at 0 + coin_index: Integer; - **Response** + // Blinding factor of the revoked new coin. + new_coin_blinding_secret: DenominationBlindingKeySecret; - :http:statuscode:`204 No content`: - The AML decision has been executed and recorded successfully. - :http:statuscode:`403 Forbidden`: - The signature is invalid (or the AML officer not known). - :http:statuscode:`404 Not found`: - The normalized payto-address the decision was made for is unknown to the exchange. - :http:statuscode:`409 Conflict`: - The designated AML account is not enabled or a more recent - decision was already submitted. + // Blinded public key of the revoked new coin. + new_coin_ev: DenominationBlindingKeySecret; - **Details:** + // Date when the operation was made. + timestamp: Timestamp; - .. ts:def:: AmlDecisionRequest + } - interface AmlDecisionRequest { - // Human-readable justification for the decision. - justification: string; - - // Hash of normalized payto-address of the account the decision is about. - // Identifies a GNU Taler wallet or an affected bank account. - h_payto: NormalizedPaytoHash; +.. note:: - // Full payto address of the account the decision is about. - // Optional. Must be given if the account is not yet - // known to the exchange. If given, must match ``h_payto`` - // (when normalized and then hashed). - // @since protocol **v21**. - payto_uri?: string; + The `CoinRecoupRefreshReceiverTransaction` interface defintion is WIP. + It will be fully specified and implemented with **vRECOUP**. - // What are the new rules? - // New since protocol **v20**. - new_rules: LegitimizationRuleSet; +.. ts:def:: CoinRecoupRefreshReceiverTransaction - // What are the new account properties? - // New since protocol **v20**. - properties: AccountProperties; + // This represents a transaction of a call to /recoup-refresh + // where this coin was the _receiver_ of the residual values + // from coins, that originated from a call to /refresh of this coin. + interface CoinRecoupRefreshReceiverTransaction { + type: "RECOUP-REFRESH-RECEIVER"; - // Array of AML/KYC events to trigger for statistics. - // Note that this information is not covered by the signature - // (which is OK as events are just for statistics). - // New since protocol **v24**. - events?: string[]; + // Offset of this entry in the reserve history. + // Useful to request incremental histories via + // the "start" query parameter. + history_offset: Integer; - // Space-separated list of measures to trigger - // immediately on the account. - // Prefixed with a "+" to indicate that the - // measures should be ANDed. - // Should typically be used to give the user some - // information or request additional information. - // - // At most one measure with a SKIP check may be specified. - // - // @since protocol **v21**. - new_measures?: string; + // The total amount of the coin's value restored + // by this transaction. + // The current coin value can thus be computed by + // adding the amount to the coin's denomination value. + amount: Amount; - // True if the account should remain under investigation by AML staff. - // @since protocol **v20**. - keep_investigating: boolean; + // Date when the operation was made. + timestamp: Timestamp; - // Signature by the AML officer over a `TALER_AmlDecisionPS`. - // Must have purpose ``TALER_SIGNATURE_MASTER_AML_KEY``. - officer_sig: EddsaSignature; + // Signature by the exchange over a + // `TALER_RecoupRefreshConfirmationPS` + // of purpose ``TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_REFRESH``. + exchange_sig: EddsaSignature; - // When was the decision made? - decision_time: Timestamp; + // Public key of the private key used to create 'exchange_sig'. + exchange_pub: EddsaPublicKey; - // KYC attributes uploaded by the AML officer - // The object *must* contain high-entropy salt, - // as the hash of the attributes will be - // stored in plain text. - attributes?: CustomerKycAttributes; + } - // Expiration timestamp of the attributes. - // Mandatory if attributes are present. - attributes_expiration?: Timestamp; +.. ts:def:: CoinPurseDepositTransaction - } + 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; -.. http:get:: /aml/$OFFICER_PUB/transfers-credit -.. http:get:: /aml/$OFFICER_PUB/transfers-debit -.. http:get:: /aml/$OFFICER_PUB/transfers-kycauth + // The total amount of the coin's value absorbed + // by this transaction. + // Note that this means the amount given includes + // the deposit fee. The current coin value can thus be computed by + // subtracting the amount from + // the coin's denomination value. + amount: Amount; - Obtain exchange's bank account wire transfer history data. Shows effective - incoming wire transfers (credit), incoming wire transfers for KYC - authorization (kycauth), or effective outgoing (aggregated, closed) - wire transfers (debit). Note that bounced incoming - wire transfers and drain outgoing wire transfers requests are excluded (as - they are not relevant for AML processes and would just distract). The - wire transfer subject is also not given as in all cases it will just - be some cryptographic data that is meaningless for humans. + // Base URL of the exchange the purse lives at. + exchange_base_url: string; - The first two endpoints were introduced in protocol **v25**. - ``transfers-kycauth`` was added in protocol **v29**. + // The hash of the age-commitment for the coin. Only present + // if the denomination has support for age restriction. + h_age_commitment?: AgeCommitmentHash; - **Request:** + // Deposit fee. + deposit_fee: Amount; - *Taler-AML-Officer-Signature*: - The client must provide Base-32 encoded EdDSA signature with - ``$OFFICER_PRIV``, affirming the desire to obtain AML data. Note that - this is merely a simple authentication mechanism, the details of the - request are not protected by the signature. + // Public key of the purse. + purse_pub: EddsaPublicKey; - :query threshold: - *Optional*. minimum amount ("CURRENCY:VAL.FRAC") to return. All amounts - below the given threshold will be filtered. - :query limit: - *Optional*. takes value of the form ``N (-N)``, so that at - most ``N`` values strictly older (younger) than ``start`` are returned. - Defaults to ``-20`` to return the last 20 entries (before ``start``). - :query offset: - *Optional*. Row number threshold, see ``delta`` for its - interpretation. Defaults to ``INT64_MAX``, namely the biggest row id - possible in the database. - :query h_payto: - *Optional*. Account selector using the *normalized* payto URI. - Information for all accounts is returned if this - filter is absent, otherwise only transactions for this account. - @since protocol **v29**. - - **Response:** + // True if the deposit was refunded for any reason. + refunded: boolean; - :http:statuscode:`200 OK`: - The responds will be an `ExchangeTransferList` message. - :http:statuscode:`204 No content`: - There are no matching transactions. - :http:statuscode:`403 Forbidden`: - The signature is invalid. - :http:statuscode:`404 Not found`: - The designated AML account is not known. - :http:statuscode:`409 Conflict`: - The designated AML account is not enabled. + // Signature by the coin over a + // `TALER_PurseDepositSignaturePS` of + // purpose ``TALER_SIGNATURE_PURSE_DEPOSIT``. + coin_sig: EddsaSignature; - .. ts:def:: ExchangeTransferList + // Hash of the public denomination key used to sign the coin. + // Needed because 'coin_sig' signs over this, and + // that is important to fix the coin's denomination. + h_denom_pub: HashCode; - interface ExchangeTransferList { + } - // Matching transaction of the exchange - transfers: ExchangeTransferListEntry[]; +.. ts:def:: CoinPurseRefundTransaction - } + interface CoinPurseRefundTransaction { + type: "PURSE-REFUND"; - .. ts:def:: ExchangeTransferListEntry + // Offset of this entry in the reserve history. + // Useful to request incremental histories via + // the "start" query parameter. + history_offset: Integer; - interface ExchangeTransferListEntry { + // The total amount of the coin's value restored + // by this transaction. + // The amount given excludes the refund fee. + // The current coin value can thus be computed by + // adding the amount to the coin's denomination value. + amount: Amount; - // Row ID of the record. Used to filter by offset. - rowid: Integer; + // Refund fee (of the coin's denomination). The deposit + // fee will be waived. + refund_fee: Amount; - // payto://-URI of the other account. - payto_uri: string; + // Signature by the exchange over a + // ``TALER_CoinPurseRefundConfirmationPS`` + // of purpose ``TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_REFUND``. + exchange_sig: EddsaSignature; - // The amount involved. - amount: Amount; + // Public key used to sign 'exchange_sig'. + exchange_pub: EddsaPublicKey; - // Time when the transfer was made - execution_time: Timestamp; + // Public key of the purse that expired. + purse_pub: EddsaPublicKey; - } + } +.. ts:def:: CoinReserveOpenDepositTransaction + 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; ---------------- -Reserve control ---------------- + // The total amount of the coin's value absorbed + // by this transaction. + // Note that this means the amount given includes + // the deposit fee. + coin_contribution: Amount; -This section describes the reserve control API which can be used to (1) -prevent a reserve from expiring, to (2) pay an annual fee to allow a number of -purses to be created for the respective reserve without paying a purse fee -each time, to (3) obtain KYC information associated with a reserve to prove -the identity of the person sending an invoice to the payer, and to (4) close a -reserve before it would naturally expire and possibly (5) wire the funds to a -designated account. + // Signature of the reserve open operation being paid for. + reserve_sig: EddsaSignature; - .. note:: + // Signature by the coin over a + // `TALER_ReserveOpenDepositSignaturePS` of + // purpose ``TALER_SIGNATURE_RESERVE_OPEN_DEPOSIT``. + coin_sig: EddsaSignature; - This section is about a proposed API. It is not implemented. See also DD 31. + } -.. http:post:: /reserves/$RESERVE_PUB/open - - Request keeping a reserve open for invoicing. - - **Request:** - - The request body must be a `ReserveOpenRequest` object. - - **Response:** - - :http:statuscode:`200 OK`: - The exchange responds with a `ReserveOpenResponse` object. - :http:statuscode:`402 Payment Required`: - The exchange responds with a `ReserveOpenFailure` object when - the payment offered is insufficient for the requested operation. - :http:statuscode:`403 Forbidden`: - The *TALER_SIGNATURE_WALLET_RESERVE_OPEN* signature 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. - :http:statuscode:`409 Conflict`: - The balance of the reserve or of a coin was insufficient. - Which case it is can be decided by looking at the error code - (``TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS`` or - ``TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY`` or - ``TALER_EC_EXCHANGE_OPEN_INSUFFICIENT_FUNDS``). - The specific fields of the response depend on the error code - and include the signatures (and what was signed over) proving the - conflict. - The response is `WithdrawError` object or a `DepositDoubleSpendError` - depending on the error type. - :http:statuscode:`451 Unavailable For Legal Reasons`: - This account has not yet passed the KYC checks. - The client must pass KYC checks before the reserve can be opened. - The response will be an `LegitimizationNeededResponse` object. - **Details:** +----------------------- +Tracking wire transfers +----------------------- - .. ts:def:: ReserveOpenRequest +This API is used by merchants that need to find out which wire +transfers (from the exchange to the merchant) correspond to which deposit +operations. Typically, a merchant will receive a wire transfer with a +**wire transfer identifier** and want to know the set of deposit +operations that correspond to this wire transfer. This is the +preferred query that merchants should make for each wire transfer they +receive. If a merchant needs to investigate a specific deposit +operation (i.e. because it seems that it was not paid), then the +merchant can also request the wire transfer identifier for a deposit +operation. - interface ReserveOpenRequest { - // Signature of purpose - // ``TALER_SIGNATURE_WALLET_RESERVE_OPEN`` over - // a `TALER_ReserveOpenPS`. - reserve_sig: EddsaSignature; +Sufficient information is returned to verify that the coin signatures +are correct. This also allows governments to use this API when doing +a tax audit on merchants. - // Array of payments made towards the cost of the - // operation. - payments: OpenPaymentDetail[]; +Naturally, the returned information may be sensitive for the merchant. +We do not require the merchant to sign the request, as the same requests +may also be performed by the government auditing a merchant. +However, wire transfer identifiers should have sufficient entropy to +ensure that obtaining a successful reply by brute-force is not practical. +Nevertheless, the merchant should protect the wire transfer identifiers +from his bank statements against unauthorized access, lest his income +situation is revealed to an adversary. (This is not a major issue, as +an adversary that has access to the line-items of bank statements can +typically also view the balance.) - // Amount to be paid from the reserve for this - // operation. - reserve_payment: Amount; - // 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 (with a status code of 400). - request_timestamp: Timestamp; +.. include:: exchange/get-transfers-WTID.rst - // Desired new expiration time for the reserve. - // If the reserve would expire before this time, - // the exchange will charge account fees (and - // possibly KYC fees) until the expiration time - // exceeds this timestamp. Note that the exchange - // will refuse requests (with a status code of 400) - // if the time is so far in the future that the - // fees are not yet known (see /keys). - reserve_expiration: Timestamp; +.. include:: exchange/get-deposits-H_WIRE-MERCHANT_PUB-H_CONTRACT_TERMS-COIN_PUB.rst - // Desired open purse limit. Can be used to pay the - // annual account fee more than once to get a larger - // purse limit. - purse_limit: Integer; - } +.. _exchange_w2w: - .. ts:def:: ReserveOpenResponse +-------------------------- +Wallet-to-wallet transfers +-------------------------- - interface ReserveOpenResponse { - // Transaction cost for extending the expiration time. - // Excludes KYC fees. - open_cost: Amount; +.. include:: exchange/get-purses-PURSE_PUB-merge.rst - // Current expiration time for the reserve. - reserve_expiration: Timestamp; - } - .. ts:def:: ReserveOpenFailure - interface ReserveOpenFailure { - // Transaction cost that should have been paid - // to extending the reserve as requested. - // Excludes KYC fees. - open_cost: Amount; +.. include:: exchange/post-purses-PURSE_PUB-create.rst - // Remaining expiration time for the reserve. - reserve_expiration: Timestamp; - } - .. ts:def:: OpenPaymentDetail +.. include:: exchange/delete-purses-PURSE_PUB.rst - interface OpenPaymentDetail { - // Contribution of this coin to the overall amount. - // Can be a fraciton of the coin's total value. - amount: Amount; +.. include:: exchange/post-purses-PURSE_PUB-merge.rst - // 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: DenominationSignature; - // Age commitment for the coin, if the denomination is age-restricted. - age_commitment?: AgeCommitment; - // Signature over `TALER_ReserveOpenDepositSignaturePS` - // of purpose ``TALER_SIGNATURE_WALLET_RESERVE_OPEN_DEPOSIT`` - // made by the customer with the - // `coin's private key <coin-priv>`. - coin_sig: EddsaSignature; +.. include:: exchange/post-reserves-RESERVE_PUB-purse.rst - // Public key of the coin being used to pay for - // opening the reserve. - coin_pub: EddsaPublicKey; +.. include:: exchange/get-contracts-CONTRACT_PUB.rst - } +.. include:: exchange/post-purses-PURSE_PUB-deposit.rst -.. http:get:: /reserves-attest/$RESERVE_PUB +.. _exchange_wads: - Request list of available KYC attributes about the owner of a reserve. - Used as a preliminary step to find out which subsets of attributes the - exchange could provide signatures over. - **Response:** +---- +Wads +---- - :http:statuscode:`200 OK`: - The exchange responds with a `ReserveKycAttributes` object. - :http:statuscode:`404 Not found`: - The reserve key does not belong to a reserve known to the exchange. - :http:statuscode:`409 Conflict`: - The exchange does not have the requested KYC information. + .. note:: - **Details:** + This is a draft API that is not yet implemented. - .. ts:def:: ReserveKycAttributes - interface ReserveKycAttributes { - // Array of KYC attributes available. The attribute names - // listed are expected to be from the respective GANA - // registry. - details: string[]; - } +These endpoints are used to manage exchange-to-exchange payments in support of +wallet-to-wallet payments. Only another exchange should access this endpoint. -.. http:post:: /reserves-attest/$RESERVE_PUB +.. include:: exchange/get-wads-WAD_ID.rst - Request signed KYC information about the owner of a reserve. - This can be used by a reserve owner to include a proof - of their identity in invoices. - **Request:** +------------------ +KYC status updates +------------------ - The request body must be a `ReserveAttestRequest` object. +This section describes endpoints used to set up, complete and +inquire about KYC operations performed by an exchange for +regulatory compliance. - **Response:** +.. include:: exchange/post-kyc-wallet.rst - :http:statuscode:`200 OK`: - The exchange responds with a `ReserveAttestResponse` object. - :http:statuscode:`403 Forbidden`: - The *TALER_SIGNATURE_WALLET_KYC_DETAILS* signature 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. - :http:statuscode:`409 Conflict`: - The exchange does not have the requested KYC information. - **Details:** +.. include:: exchange/get-kyc-check-H_NORMALIZED_PAYTO.rst - .. ts:def:: ReserveAttestRequest +.. include:: exchange/get-kyc-spa-ACCESS_TOKEN.rst - interface ReserveAttestRequest { - // Signature of purpose - // ``TALER_SIGNATURE_WALLET_ATTEST_DETAILS`` over - // a `TALER_WalletReserveAttestRequestSignaturePS`. - reserve_sig: EddsaSignature; - // Client's time for the request. - request_timestamp: Timestamp; +.. include:: exchange/get-kyc-info-ACCESS_TOKEN.rst - // Array of KYC attributes requested. - details: string[]; - } +.. include:: exchange/post-kyc-upload-ID.rst - .. ts:def:: ReserveAttestResponse +.. include:: exchange/post-kyc-start-ID.rst - interface ReserveAttestResponse { - // Signature of purpose - // ``TALER_SIGNATURE_EXCHANGE_RESERVE_ATTEST_DETAILS`` over - // a `TALER_ExchangeAttestPS`. - exchange_sig: EddsaSignature; +.. include:: exchange/get-kyc-proof-PROVIDER_NAME.rst - // Exchange public key used to create the - // signature. - exchange_pub: EddsaPublicKey; - // Time when the exchange created the signature. - exchange_timestamp: Timestamp; +.. include:: exchange/get-kyc-webhook-PROVIDER_NAME-star.rst - // Expiration time for the provided information. - expiration_time: Timestamp; - // KYC details (key-value pairs) as requested. - // The keys will match the elements of the - // ``details`` array from the request. - attributes: CustomerKycAttributes; - } +-------------- +AML operations +-------------- +This API is only for designated AML officers. It is used +to allow exchange staff to monitor suspicious transactions +and freeze or unfreeze accounts suspected of money laundering. -.. http:post:: /reserves/$RESERVE_PUB/close - Force immediate closure of a reserve. Does not actually - delete the reserve or the KYC data, but merely forces - the reserve's current balance to be wired back to the - account where it originated from, or another account of - the user's choosing if they performed the required KYC - check and designated another target account. +.. include:: exchange/get-aml-OFFICER_PUB-measures.rst - **Request:** +.. include:: exchange/get-aml-OFFICER_PUB-kyc-statistics-NAMES.rst - The request body must be a `ReserveCloseRequest` object. - **Response:** +.. include:: exchange/get-aml-OFFICER_PUB-decisions.rst - :http:statuscode:`200 OK`: - The exchange responds with a `ReserveCloseResponse` object. - :http:statuscode:`403 Forbidden`: - The *TALER_SIGNATURE_WALLET_RESERVE_CLOSE* signature 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. - :http:statuscode:`409 Conflict`: - No target account was given, and the exchange does not know an - origin account for this reserve. - :http:statuscode:`451 Unavailable For Legal Reasons`: - This account has not yet passed the KYC checks, hence wiring - funds to a non-origin account is not allowed. - The client must pass KYC checks before the reserve can be opened. - The response will be an `LegitimizationNeededResponse` object. - **Details:** +.. include:: exchange/get-aml-OFFICER_PUB-legitimizations.rst - .. ts:def:: ReserveCloseRequest - interface ReserveCloseRequest { - // Signature of purpose - // ``TALER_SIGNATURE_WALLET_RESERVE_CLOSE`` over - // a `TALER_ReserveCloseRequestSignaturePS`. - reserve_sig: EddsaSignature; +.. include:: exchange/get-aml-OFFICER_PUB-accounts.rst - // 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 (with a status code of 400). - request_timestamp: Timestamp; - // Full payto://-URI of the account the reserve balance is to be - // wired to. Must be of the form: 'payto://$METHOD' for a - // wire method supported by this exchange (if the - // method is not supported, this is a bad request (400)). - // If not given, the reserve's origin account - // will be used. If no origin account is known for the - // reserve and not given, this is a conflict (409). - payto_uri?: string; - } +.. include:: exchange/get-aml-OFFICER_PUB-attributes-H_NORMALIZED_PAYTO.rst - .. ts:def:: ReserveCloseResponse - interface ReserveCloseResponse { +.. include:: exchange/post-aml-OFFICER_PUB-decision.rst - // Actual amount that will be wired (excludes closing fee). - wire_amount: Amount; - } +.. include:: exchange/get-aml-OFFICER_PUB-transfers-credit.rst -.. _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! +--------------- +Reserve control +--------------- - **Request:** +This section describes the reserve control API which can be used to (1) +prevent a reserve from expiring, to (2) pay an annual fee to allow a number of +purses to be created for the respective reserve without paying a purse fee +each time, to (3) obtain KYC information associated with a reserve to prove +the identity of the person sending an invoice to the payer, and to (4) close a +reserve before it would naturally expire and possibly (5) wire the funds to a +designated account. - *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``. + .. note:: - :query force=BOOLEAN: *Optional.* If set to 'true' specified, the exchange - will delete the account even if there is a balance remaining. + This section is about a proposed API. It is not implemented. See also DD 31. - **Response:** +.. include:: exchange/post-reserves-RESERVE_PUB-open.rst - :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:** +.. include:: exchange/get-reserves-attest-RESERVE_PUB.rst - .. ts:def:: ReserveDeletedResponse - interface ReserveDeletedResponse { +.. include:: exchange/post-reserves-attest-RESERVE_PUB.rst - // 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; +.. include:: exchange/post-reserves-RESERVE_PUB-close.rst - // Hash of the full payto URI 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: FullPaytoHash; - // This is a signature over a - // struct ``TALER_AccountDeleteConfirmationPS`` with purpose - // ``TALER_SIGNATURE_EXCHANGE_RESERVE_DELETED``. - exchange_sig: EddsaSignature; +.. _delete-reserve: - } +.. include:: exchange/delete-reserves-RESERVE_PUB.rst diff --git a/core/exchange/delete-purses-PURSE_PUB.rst b/core/exchange/delete-purses-PURSE_PUB.rst @@ -0,0 +1,28 @@ +.. http:delete:: /purses/$PURSE_PUB + + Delete a purse that is unmerged and not yet expired. Refunds any money that + is already in the purse. + + **Request:** + + The request body must be empty, as recommended for HTTP delete in general. + + To authorize the request, the header must contain the following HTTP header: + + *Taler-Purse-Signature*: + $PURSE_SIG`` where ``$PURSE_SIG`` is the Crockford base32-encoded EdDSA + signature of purpose TALER_SIGNATURE_WALLET_PURSE_DELETE. + + **Response:** + + :http:statuscode:`204 No Content`: + The operation succeeded, the exchange confirms that the purse + was deleted. + :http:statuscode:`403 Forbidden`: + The signature is invalid. + This response comes with a standard `ErrorDetail` response. + :http:statuscode:`404 Not Found`: + The purse is not known. Might have already been deleted previously. + :http:statuscode:`409 Conflict`: + It is too late to delete the purse, its fate (merge or expiration) + was already decided. diff --git a/core/exchange/delete-reserves-RESERVE_PUB.rst b/core/exchange/delete-reserves-RESERVE_PUB.rst @@ -0,0 +1,58 @@ +.. 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 full payto URI 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: FullPaytoHash; + + // This is a signature over a + // struct ``TALER_AccountDeleteConfirmationPS`` with purpose + // ``TALER_SIGNATURE_EXCHANGE_RESERVE_DELETED``. + exchange_sig: EddsaSignature; + + } diff --git a/core/exchange/get-aml-OFFICER_PUB-accounts.rst b/core/exchange/get-aml-OFFICER_PUB-accounts.rst @@ -0,0 +1,137 @@ +.. http:get:: /aml/$OFFICER_PUB/accounts + + **Request:** + + *Accept*: + The client may specify the desired MIME-type for the result. + Supported are the usual "application/json", but also + "text/csv" (RFC 4180) + and "application/vnd.ms-excel" (XML of Excel 2003). + + *Taler-AML-Officer-Signature*: + The client must provide Base-32 encoded EdDSA signature with + ``$OFFICER_PRIV``, affirming the desire to obtain AML data. Note that + this is merely a simple authentication mechanism, the details of the + request are not protected by the signature. + + This endpoint was introduced in this form in protocol **v31**. + + :query limit: + *Optional*. takes value of the form ``N (-N)``, so that at + most ``N`` values strictly older (younger) than ``start`` are returned. + Defaults to ``-20`` to return the last 20 entries (before ``start``). + :query offset: + *Optional*. Row number threshold, see ``delta`` for its + interpretation. Defaults to ``INT64_MAX``, namely the biggest row id + possible in the database. + :query investigation: + *Optional*. If set to yes, only return accounts that are under + AML investigation, if no only accounts that are not under investigation. + Do not give (or use "all") to see all accounts regardless of + investigation status. + :query open: + *Optional*. If set to yes, only return accounts that are open, + which means they crossed some initial limit and now have custom rules. + Do not give (or use "all") to see all accounts regardless of + open status. + :query high_risk: + *Optional*. If set to yes, only return accounts that were flagged + as high risk. + Do not give (or use "all") to see all accounts regardless of + risk status. + + **Response:** + + :http:statuscode:`200 OK`: + The response will be an `AmlAccountsResponse` message (if the + client's header "Accept" was "application/json"). Other encodings + may differ in the exact returned data and format. + :http:statuscode:`204 No content`: + There are no accounts at all (within the specified constraints). + :http:statuscode:`403 Forbidden`: + The signature is invalid. + :http:statuscode:`404 Not found`: + The designated AML account is not known. + :http:statuscode:`409 Conflict`: + The designated AML account is not enabled. + + **Details:** + + .. ts:def:: AmlAccountsResponse + + interface AmlAccountsResponse { + + // Array of customer accounts matching the query. + accounts: CustomerAccountSummary[]; + } + + .. ts:def:: CustomerAccountSummary + + interface CustomerAccountSummary { + + // Which payto-address is this record about. + // Identifies a GNU Taler wallet or an affected bank account. + h_payto: NormalizedPaytoHash; + + // Full payto URL of the account that the decision is + // about. + full_payto: string; + + // True if the account was assessed as being high risk. + high_risk: boolean; + + // Latest comments about the account (if any). + comments?: string; + + // Row of the account in the exchange tables. Useful to filter + // by offset. + rowid: Integer; + + // When was the account opened? "never" if it was never opened. + open_time: Timestamp; + + // When was the account opened? "never" if it was never closed. + close_time: Timestamp; + + // True if the account is under investigation by AML staff + // after this decision. + to_investigate: boolean; + + } + + .. ts:def:: AccountProperties + + // All fields in this object are optional. The actual + // properties collected depend fully on the discretion + // of the exchange operator; + // however, some common fields are standardized + // and thus described here. + interface AccountProperties { + + // True if this is a politically exposed account. + // Rules for classifying accounts as politically + // exposed are country-dependent. + pep?: boolean; + + // True if this is a sanctioned account. + // Rules for classifying accounts as sanctioned + // are country-dependent. + sanctioned?: boolean; + + // True if this is a high-risk account. + // Rules for classifying accounts as at-risk + // are exchange operator-dependent. + high_risk?: boolean; + + // Business domain of the account owner. + // The list of possible business domains is + // operator- or country-dependent. + business_domain?: string; + + // Is the client's account currently frozen? + is_frozen?: boolean; + + // Was the client's account reported to the authorities? + was_reported?: boolean; + + } diff --git a/core/exchange/get-aml-OFFICER_PUB-attributes-H_NORMALIZED_PAYTO.rst b/core/exchange/get-aml-OFFICER_PUB-attributes-H_NORMALIZED_PAYTO.rst @@ -0,0 +1,98 @@ +.. http:get:: /aml/$OFFICER_PUB/attributes/$H_NORMALIZED_PAYTO + + Obtain attributes obtained as part of AML/KYC processes for a + given account. + + This endpoint was introduced in protocol **v20**. + + **Request:** + + *Accept*: + The client may specify the desired MIME-type for the result. + Supported are the usual "application/json", but also + "application/pdf". + + *Taler-AML-Officer-Signature*: + The client must provide Base-32 encoded EdDSA signature with + ``$OFFICER_PRIV``, affirming the desire to obtain AML data. Note that + this is merely a simple authentication mechanism, the details of the + request are not protected by the signature. + + :query limit: + *Optional*. takes value of the form ``N (-N)``, so that at + most ``N`` values strictly older (younger) than ``start`` are returned. + Defaults to ``-20`` to return the last 20 entries (before ``start``). + :query offset: + *Optional*. Row number threshold, see ``delta`` for its + interpretation. Defaults to ``INT64_MAX``, namely the biggest row id + possible in the database. + + **Response:** + + :http:statuscode:`200 OK`: + The responds will be an `KycAttributes` message. + :http:statuscode:`204 No content`: + There are no matching KYC attributes. + :http:statuscode:`403 Forbidden`: + The signature is invalid. + :http:statuscode:`404 Not found`: + The designated AML account is not known. + :http:statuscode:`409 Conflict`: + The designated AML account is not enabled. + :http:statuscode:`501 Not implemented`: + The requested functionality is not implemented. + Usually returned if the PDF generator is not available + at this backend and the requested format was application/pdf. + + .. ts:def:: KycAttributes + + interface KycAttributes { + + // Matching KYC attribute history of the account. + details: KycAttributeCollectionEvent[]; + + } + + .. ts:def:: KycAttributeCollectionEvent + + interface KycAttributeCollectionEvent { + + // Row ID of the record. Used to filter by offset. + rowid: Integer; + + // True if the attributes were filed by an AML officer, + // otherwise they were provided directly by the customer. + by_aml_officer: boolean; + + // The collected KYC data. NULL if the attribute data could not + // be decrypted (internal error of the exchange, likely the + // attribute key was changed). + attributes?: CustomerKycAttributes; + + // Time when the KYC data was collected + collection_time: Timestamp; + + } + + .. ts:def:: CustomerKycAttributes + + interface CustomerKycAttributes { + + // ID of the Form that was used to submit the attributes and/or + // that should be used to *render* the attributes. + // Mandatory since **v31**. + FORM_ID: string; + + // Version of the form completed by the user. + FORM_VERSION?: Integer; + + // High entropy value used in forms where hash is going to be stored in + // plain text. + FORM?_SALT: string; + + // Attributes can have basically arbitrary additional + // Key-value pairs. See the + // gnu-taler-form-attributes registry in GANA + // for possible keys! + + } diff --git a/core/exchange/get-aml-OFFICER_PUB-decisions.rst b/core/exchange/get-aml-OFFICER_PUB-decisions.rst @@ -0,0 +1,225 @@ +.. http:get:: /aml/$OFFICER_PUB/decisions + + **Request:** + + *Taler-AML-Officer-Signature*: + The client must provide Base-32 encoded EdDSA signature with + ``$OFFICER_PRIV``, affirming the desire to obtain AML data. Note that + this is merely a simple authentication mechanism, the details of the + request are not protected by the signature. + + This endpoint was introduced in this form in protocol **v20**. + + :query limit: + *Optional*. takes value of the form ``N (-N)``, so that at + most ``N`` values strictly older (younger) than ``start`` are returned. + Defaults to ``-20`` to return the last 20 entries (before ``start``). + :query offset: + *Optional*. Row number threshold, see ``delta`` for its + interpretation. Defaults to ``INT64_MAX``, namely the biggest row id + possible in the database. + :query h_payto: + *Optional*. Account selector using the *normalized* payto URI. + All matching accounts are returned if this + filter is absent, otherwise only decisions for this account. + :query active: + *Optional*. If set to yes, only return active decisions, if no only + decisions that have been superseded. Do not give (or use "all") to + see all decisions regardless of activity status. + :query investigation: + *Optional*. If set to yes, only return accounts that are under + AML investigation, if no only accounts that are not under investigation. + Do not give (or use "all") to see all accounts regardless of + investigation status. + + **Response:** + + :http:statuscode:`200 OK`: + The response will be an `AmlDecisionsResponse` message. + :http:statuscode:`204 No content`: + There are no matching AML records. + :http:statuscode:`403 Forbidden`: + The signature is invalid. + :http:statuscode:`404 Not found`: + The designated AML account is not known. + :http:statuscode:`409 Conflict`: + The designated AML account is not enabled. + + **Details:** + + .. ts:def:: AmlDecisionsResponse + + interface AmlDecisionsResponse { + + // Array of AML decisions matching the query. + records: AmlDecision[]; + } + + .. ts:def:: AmlDecision + + interface AmlDecision { + + // Which payto-address is this record about. + // Identifies a GNU Taler wallet or an affected bank account. + h_payto: NormalizedPaytoHash; + + // Full payto URL of the account that the decision is + // about. Since protocol **v30** (rev 1). + full_payto: string; + + // True if the underlying payto://-URI is for a wallet + // Since protocol **v25**. + is_wallet: boolean; + + // Row ID of the record. Used to filter by offset. + rowid: Integer; + + // Justification for the decision. NULL if none + // is available. + justification?: string; + + // When was the decision made? + decision_time: Timestamp; + + // Free-form properties about the account. + // Can be used to store properties such as PEP, + // risk category, type of business, hits on + // sanctions lists, etc. + // + // Passing a properties object overrides all + // of the current properties for the AML account. + properties?: AccountProperties; + + // What are the new rules? + limits: LegitimizationRuleSet; + + // True if the account is under investigation by AML staff + // after this decision. + to_investigate: boolean; + + // True if this is the active decision for the + // account. + is_active: boolean; + + } + + .. ts:def:: AccountProperties + + // All fields in this object are optional. The actual + // properties collected depend fully on the discretion + // of the exchange operator; + // however, some common fields are standardized + // and thus described here. + interface AccountProperties { + + // True if this is a politically exposed account. + // Rules for classifying accounts as politically + // exposed are country-dependent. + pep?: boolean; + + // True if this is a sanctioned account. + // Rules for classifying accounts as sanctioned + // are country-dependent. + sanctioned?: boolean; + + // True if this is a high-risk account. + // Rules for classifying accounts as at-risk + // are exchange operator-dependent. + high_risk?: boolean; + + // Business domain of the account owner. + // The list of possible business domains is + // operator- or country-dependent. + business_domain?: string; + + // Is the client's account currently frozen? + is_frozen?: boolean; + + // Was the client's account reported to the authorities? + was_reported?: boolean; + + } + + .. ts:def:: LegitimizationRuleSet + + interface LegitimizationRuleSet { + + // When does this set of rules expire and + // we automatically transition to the successor + // measure? + expiration_time: Timestamp; + + // Name of the measure to apply when the expiration time is + // reached. If not set, we refer to the default + // set of rules (and the default account state). + successor_measure?: string; + + // Legitimization rules that are to be applied + // to this account. + rules: KycRule[]; + + // Custom measures that KYC rules and the + // ``successor_measure`` may refer to. + custom_measures: { "$measure_name" : MeasureInformation; }; + + } + + .. ts:def:: KycRule + + interface KycRule { + + // Type of operation to which the rule applies. + // + // Must be one of "WITHDRAW", "DEPOSIT", + // (p2p) "MERGE", (wallet) "BALANCE", + // (reserve) "CLOSE", "AGGREGATE", + // "TRANSACTION" or "REFUND". + operation_type: string; + + // Name of the configuration section this rule + // originates from. Not available for all rules. + // Primarily informational, but also useful to + // explicitly manipulate rules by-name in AML programs. + rule_name?: string; + + // The measures will be taken if the given + // threshold is crossed over the given timeframe. + threshold: Amount; + + // Over which duration should the ``threshold`` be + // computed. All amounts of the respective + // ``operation_type`` will be added up for this + // duration and the sum compared to the ``threshold``. + timeframe: RelativeTime; + + // Array of names of measures to apply. + // Names listed can be original measures or + // custom measures from the `AmlOutcome`. + // A special measure "verboten" is used if the + // threshold may never be crossed. + measures: string[]; + + // If multiple rules apply to the same account + // at the same time, the number with the highest + // rule determines which set of measures will + // be activated and thus become visible for the + // user. + display_priority: Integer; + + // True if the rule (specifically, operation_type, + // threshold, timeframe) and the general nature of + // the measures (verboten or approval required) + // should be exposed to the client. + // Defaults to "false" if not set. + exposed?: boolean; + + // True if all the measures will eventually need to + // be satisfied, false if any of the measures should + // do. Primarily used by the SPA to indicate how + // the measures apply when showing them to the user; + // in the end, AML programs will decide after each + // measure what to do next. + // Default (if missing) is false. + is_and_combinator?: boolean; + + } diff --git a/core/exchange/get-aml-OFFICER_PUB-kyc-statistics-NAMES.rst b/core/exchange/get-aml-OFFICER_PUB-kyc-statistics-NAMES.rst @@ -0,0 +1,59 @@ +.. http:get:: /aml/$OFFICER_PUB/kyc-statistics/$NAMES + + Returns the number of KYC events matching the space-separated (!) + list of given event types ``$NAMES`` in + the specified time range. Note that this query can be slow as the + statistics are computed on-demand. (This is OK as such requests should be + rare.) + + This endpoint was introduced in protocol **v20**. It was + modified in **v30** revision 1 to accept a space-separated + list of names instead of a single name and to return an array + of results. + + **Request:** + + *Taler-AML-Officer-Signature*: + The client must provide Base-32 encoded EdDSA signature with + ``$OFFICER_PRIV``, affirming the desire to obtain AML data. Note that this + is merely a simple authentication mechanism, the details of the request are + not protected by the signature. + + :query start_date=TIMESTAMP: + *Optional*. Specifies the date when to + start looking (inclusive). If not given, the start time of the + exchange operation is used. The TIMESTAMP is given + in seconds since the UNIX epoch. + :query end_date=TIMESTAMP: + *Optional*. Specifies the date when to + stop looking (exclusive). If not given, the current date is used. The TIMESTAMP is given + in seconds since the UNIX epoch. + + **Response:** + + :http:statuscode:`200 OK`: + The responds will be an `AmlStatisticsResponse` message. + :http:statuscode:`204 No content`: + All the event counters are zero. + + **Details:** + + .. ts:def:: AmlStatisticsResponse + + interface AmlStatisticsResponse { + // Statistics that were found. + statistics: EventCounter[]; + } + + + .. ts:def:: EventCounter + + interface EventCounter { + // Name of the statistic that is being returned. + name: string; + + // Number of events of the specified type in + // the given range. + counter: Integer; + + } diff --git a/core/exchange/get-aml-OFFICER_PUB-legitimizations.rst b/core/exchange/get-aml-OFFICER_PUB-legitimizations.rst @@ -0,0 +1,72 @@ +.. http:get:: /aml/$OFFICER_PUB/legitimizations + + Enables AML staff to see which legitimizations are pending + or have been completed. + returns a list of active or finished legitimization + measures (by account). + + This endpoint was introduced in protocol **v23**. + + **Request:** + + *Taler-AML-Officer-Signature*: + The client must provide Base-32 encoded EdDSA signature with + ``$OFFICER_PRIV``, affirming the desire to obtain AML data. Note that + this is merely a simple authentication mechanism, the details of the + request are not protected by the signature. + + :query limit: + *Optional*. takes value of the form ``N (-N)``, so that at + most ``N`` values strictly older (younger) than ``start`` are returned. + Defaults to ``-20`` to return the last 20 entries (before ``start``). + :query offset: + *Optional*. Row number threshold, see ``limit`` for its + interpretation. Defaults to ``INT64_MAX``, namely the biggest row id + possible in the database. + :query h_payto: + *Optional*. Account selector using the *normalized* payto URI. + All matching accounts are returned if this + filter is absent, otherwise only decisions for this account. + :query active: + *Optional*. If set to yes, only return active decisions, if no only + decisions that have been superseded. Do not give (or use "all") to + see all decisions regardless of activity status. + + **Response:** + + :http:statuscode:`200 Ok`: + Information about possible measures is returned in a + `LegitimizationMeasuresList` object. + + **Details:** + + .. ts:def:: LegitimizationMeasuresList + + interface LegitimizationMeasuresList { + + // Legitimization measures. + measures: LegitimizationMeasureDetails[]; + + } + + .. ts:def:: LegitimizationMeasureDetails + + interface LegitimizationMeasureDetails { + + // Hash of the normalized payto:// URI of the account the + // measure applies to. + h_payto: HashCode; + + // Row of the measure in the exchange database. + rowid: Integer; + + // When was the measure started? + start_time: Timestamp; + + // The the actual measures. + measures: LegitimizationMeasures; + + // Was this measure finished by the customer? + is_finished: boolean; + + } diff --git a/core/exchange/get-aml-OFFICER_PUB-measures.rst b/core/exchange/get-aml-OFFICER_PUB-measures.rst @@ -0,0 +1,136 @@ +.. http:get:: /aml/$OFFICER_PUB/measures + + To enable the AML staff SPA to give AML staff a choice of possible measures, a + new endpoint ``/aml/$OFFICER_PUB/measures`` is added that allows the AML SPA + to dynamically GET the list of available measures. It returns a list of known + KYC checks (by name) with their descriptions and a list of AML programs with + information about the required context. + + This endpoint was introduced in protocol **v20**. + + **Request:** + + *Taler-AML-Officer-Signature*: + The client must provide Base-32 encoded EdDSA signature with + ``$OFFICER_PRIV``, affirming the desire to obtain AML data. Note that + this is merely a simple authentication mechanism, the details of the + request are not protected by the signature. + + **Response:** + + :http:statuscode:`200 Ok`: + Information about possible measures is returned in a + `AvailableMeasureSummary` object. + + **Details:** + + .. ts:def:: AvailableMeasureSummary + + interface AvailableMeasureSummary { + + // Available original measures that can be + // triggered directly by default rules. + roots: { "$measure_name" : MeasureInformation; }; + + // Available AML programs. + programs: { "$prog_name" : AmlProgramRequirement; }; + + // Available KYC checks. + checks: { "$check_name" : KycCheckInformation; }; + + // Default KYC rules. This is the set of KYC rules that + // applies by default to new "accounts". Note that some + // rules only apply to wallets, while others only apply to + // bank accounts. The returned array is the union of all + // possible rules, applications should consider the + // ``operation_type`` to filter for rules that actually + // apply to a specific situation. + // @since protocol **v28**. + default_rules: KycRule[]; + + } + + .. ts:def:: MeasureInformation + + interface MeasureInformation { + + // Name of a KYC check. + check_name: string; + + // Name of an AML program. + // Optional @since protocol **v30**. + prog_name?: string; + + // Context for the check. Optional. + context?: Object; + + // Operation that this measure relates to. + // NULL if unknown. Useful as a hint to the + // user if there are many (voluntary) measures + // and some related to unlocking certain operations. + // (and due to zero-amount thresholds, no measure + // was actually specifically triggered). + // + // Must be one of "WITHDRAW", "DEPOSIT", + // (p2p) "MERGE", (wallet) "BALANCE", + // (reserve) "CLOSE", "AGGREGATE", + // "TRANSACTION" or "REFUND". + // @since protocol **v21**. + operation_type?: string; + + // Can this measure be undertaken voluntarily? + // Optional, default is false. + // @since protocol **vATTEST**. + voluntary?: boolean; + + } + + .. ts:def:: AmlProgramRequirement + + interface AmlProgramRequirement { + + // Description of what the AML program does. + description: string; + + // List of required field names in the context to run this + // AML program. SPA must check that the AML staff is providing + // adequate CONTEXT when defining a measure using this program. + context: string[]; + + // List of required attribute names in the + // input of this AML program. These attributes + // are the minimum that the check must produce + // (it may produce more). + inputs: string[]; + + } + + .. ts:def:: KycCheckInformation + + interface KycCheckInformation { + + // Description of the KYC check. Should be shown + // to the AML staff but will also be shown to the + // client when they initiate the check in the KYC SPA. + description: string; + + // Map from IETF BCP 47 language tags to localized + // description texts. + description_i18n ?: { [lang_tag: string]: string}; + + // Names of the fields that the CONTEXT must provide + // as inputs to this check. + // SPA must check that the AML staff is providing + // adequate CONTEXT when defining a measure using + // this check. + requires: string[]; + + // Names of the attributes the check will output. + // SPA must check that the outputs match the + // required inputs when combining a KYC check + // with an AML program into a measure. + outputs: string[]; + + // Name of a root measure taken when this check fails. + fallback: string; + } diff --git a/core/exchange/get-aml-OFFICER_PUB-transfers-credit.rst b/core/exchange/get-aml-OFFICER_PUB-transfers-credit.rst @@ -0,0 +1,80 @@ +.. http:get:: /aml/$OFFICER_PUB/transfers-credit +.. http:get:: /aml/$OFFICER_PUB/transfers-debit +.. http:get:: /aml/$OFFICER_PUB/transfers-kycauth + + Obtain exchange's bank account wire transfer history data. Shows effective + incoming wire transfers (credit), incoming wire transfers for KYC + authorization (kycauth), or effective outgoing (aggregated, closed) + wire transfers (debit). Note that bounced incoming + wire transfers and drain outgoing wire transfers requests are excluded (as + they are not relevant for AML processes and would just distract). The + wire transfer subject is also not given as in all cases it will just + be some cryptographic data that is meaningless for humans. + + The first two endpoints were introduced in protocol **v25**. + ``transfers-kycauth`` was added in protocol **v29**. + + **Request:** + + *Taler-AML-Officer-Signature*: + The client must provide Base-32 encoded EdDSA signature with + ``$OFFICER_PRIV``, affirming the desire to obtain AML data. Note that + this is merely a simple authentication mechanism, the details of the + request are not protected by the signature. + + :query threshold: + *Optional*. minimum amount ("CURRENCY:VAL.FRAC") to return. All amounts + below the given threshold will be filtered. + :query limit: + *Optional*. takes value of the form ``N (-N)``, so that at + most ``N`` values strictly older (younger) than ``start`` are returned. + Defaults to ``-20`` to return the last 20 entries (before ``start``). + :query offset: + *Optional*. Row number threshold, see ``delta`` for its + interpretation. Defaults to ``INT64_MAX``, namely the biggest row id + possible in the database. + :query h_payto: + *Optional*. Account selector using the *normalized* payto URI. + Information for all accounts is returned if this + filter is absent, otherwise only transactions for this account. + @since protocol **v29**. + + **Response:** + + :http:statuscode:`200 OK`: + The responds will be an `ExchangeTransferList` message. + :http:statuscode:`204 No content`: + There are no matching transactions. + :http:statuscode:`403 Forbidden`: + The signature is invalid. + :http:statuscode:`404 Not found`: + The designated AML account is not known. + :http:statuscode:`409 Conflict`: + The designated AML account is not enabled. + + .. ts:def:: ExchangeTransferList + + interface ExchangeTransferList { + + // Matching transaction of the exchange + transfers: ExchangeTransferListEntry[]; + + } + + .. ts:def:: ExchangeTransferListEntry + + interface ExchangeTransferListEntry { + + // Row ID of the record. Used to filter by offset. + rowid: Integer; + + // payto://-URI of the other account. + payto_uri: string; + + // The amount involved. + amount: Amount; + + // Time when the transfer was made + execution_time: Timestamp; + + } diff --git a/core/exchange/get-coins-COIN_PUB-history.rst b/core/exchange/get-coins-COIN_PUB-history.rst @@ -0,0 +1 @@ +.. http:get:: /coins/$COIN_PUB/history diff --git a/core/exchange/get-config.rst b/core/exchange/get-config.rst @@ -0,0 +1,95 @@ +.. http:get:: /config + + Return the protocol version and currency supported by this exchange backend, + as well as the list of possible KYC requirements. This endpoint is largely + for the SPA for AML officers. Merchants should use ``/keys`` which also + contains the protocol version and currency. + This specification corresponds to ``current`` protocol being **v31**. + + **Response:** + + :http:statuscode:`200 OK`: + The body is a `VersionResponse`. + + .. ts:def:: ExchangeVersionResponse + + interface ExchangeVersionResponse { + // libtool-style representation of the Exchange protocol version, see + // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning + // The format is "current:revision:age". + version: string; + + // Name of the protocol. + name: "taler-exchange"; + + // URN of the implementation (needed to interpret 'revision' in version). + // @since protocol **v18**, may become mandatory in the future. + implementation?: string; + + // Currency supported by this exchange, given + // as a currency code ("USD" or "EUR"). + currency: string; + + // Shopping URL where users may find shops that accept + // digital cash issued by this exchange. + // @since protocol **v21**. + shopping_url?: string; + + // Open banking gateway base URL where wallets can + // initiate wire transfers to withdraw + // digital cash from this exchange. + // @since protocol **v30**. + open_banking_gateway?: string; + + // How wallets should render this currency. + currency_specification: CurrencySpecification; + + // Names of supported KYC requirements. + // @deprected in **v24**. + supported_kyc_requirements: string[]; + + // Bank-specific dialect for the AML SPA. Determines + // which set of forms is available as well as statistics + // to show and sets of properties/events to trigger in + // AML decisions. + // @since protocol **v24**. + aml_spa_dialect?: string; + + } + + .. ts:def:: CurrencySpecification + + interface CurrencySpecification { + // Name of the currency. Like "US Dollar". + name: string; + + // Code of the currency. + // @deprecated in protocol **v18** for the exchange, and + // @deprecated in protocol **v6** for the merchant. + currency: string; + + // how many digits the user may enter after the decimal_separator + num_fractional_input_digits: Integer; + + // Number of fractional digits to render in normal font and size. + num_fractional_normal_digits: Integer; + + // Number of fractional digits to render always, if needed by + // padding with zeros. + num_fractional_trailing_zero_digits: Integer; + + // map of powers of 10 to alternative currency names / symbols, must + // always have an entry under "0" that defines the base name, + // e.g. "0 => €" or "3 => k€". For BTC, would be "0 => BTC, -3 => mBTC". + // Communicates the currency symbol to be used. + alt_unit_names: { log10 : string }; + + // An array of common amounts that should be turned into + // display buttons in dialogs where the user might like + // a short-cut. The array should have four entries, but + // may have fewer or more entries. Wallets may omit + // later entries in the array. + // @since protocol **v30** (rev 2) + common_amounts: Amount[]; + + } diff --git a/core/exchange/get-contracts-CONTRACT_PUB.rst b/core/exchange/get-contracts-CONTRACT_PUB.rst @@ -0,0 +1,28 @@ +.. http:get:: /contracts/$CONTRACT_PUB + + Obtain P2P contract associated with the given contract public key. + + **Response:** + + :http:statuscode:`200 Ok`: + The contract is being returned using a `ExchangeContractResponse`. + :http:statuscode:`404 Not found`: + The contract is unknown. + This response comes with a standard `ErrorDetail` response. + + **Details:** + + .. ts:def:: ExchangeContractResponse + + interface ExchangeContractResponse { + + // Public key of the purse into which payments must be + // made for this contract. + purse_pub: PursePublicKey; + + // Signature over the contract + econtract_sig: PurseContractSignature; + + // Encrypted contract. + econtract: EncryptedContract; + } diff --git a/core/exchange/get-deposits-H_WIRE-MERCHANT_PUB-H_CONTRACT_TERMS-COIN_PUB.rst b/core/exchange/get-deposits-H_WIRE-MERCHANT_PUB-H_CONTRACT_TERMS-COIN_PUB.rst @@ -0,0 +1,107 @@ +.. http:get:: /deposits/$H_WIRE/$MERCHANT_PUB/$H_CONTRACT_TERMS/$COIN_PUB + + Provide the wire transfer identifier associated with an (existing) deposit operation. + The arguments are the hash of the merchant's payment details (H_WIRE), the + merchant's public key (EdDSA), the hash of the contract terms that were paid + (H_CONTRACT_TERMS) and the public key of the coin used for the payment (COIN_PUB). + + **Request:** + + :query merchant_sig: EdDSA signature of the merchant made with purpose + ``TALER_SIGNATURE_MERCHANT_TRACK_TRANSACTION`` over a + ``TALER_DepositTrackPS``, affirming that it is really the merchant who + requires obtaining the wire transfer identifier. + :query timeout_ms=NUMBER: *Optional.* If specified, the exchange will wait + up to ``NUMBER`` milliseconds for completion of a deposit operation before + sending the HTTP response. + :query lpt=TARGET: *Optional*. + Specifies what status change we are long-polling for. + Use 1 to wait for the a 202 state where ``kyc_ok`` is false *or* a 200 OK response. + 2 to wait exclusively for a 200 OK response. + @since protocol **v21**. + + **Response:** + + :http:statuscode:`200 OK`: + The deposit has been executed by the exchange and we have a wire transfer identifier. + The response body is a `TrackTransactionResponse` object. + :http:statuscode:`202 Accepted`: + The deposit request has been accepted for processing, but was not yet + executed. Hence the exchange does not yet have a wire transfer identifier. The + merchant should come back later and ask again. + The response body is a `TrackTransactionAcceptedResponse`. + :http:statuscode:`403 Forbidden`: + A signature is invalid. + This response comes with a standard `ErrorDetail` response. + :http:statuscode:`404 Not found`: + The deposit operation is unknown to the exchange. + + **Details:** + + .. ts:def:: TrackTransactionResponse + + interface TrackTransactionResponse { + + // Raw wire transfer identifier of the deposit. + wtid: Base32; + + // When was the wire transfer given to the bank. + execution_time: Timestamp; + + // The contribution of this coin to the total (without fees) + coin_contribution: Amount; + + // Binary-only Signature_ with purpose ``TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE`` + // over a `TALER_ConfirmWirePS` + // whereby the exchange affirms the successful wire transfer. + exchange_sig: EddsaSignature; + + // Public EdDSA key 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:: TrackTransactionAcceptedResponse + + interface TrackTransactionAcceptedResponse { + + // Legitimization row. Largely useless, except + // not present if the deposit has not + // yet been aggregated to the point that a KYC + // requirement has been evaluated. + requirement_row?: Integer; + + // True if the KYC check for the merchant has been + // satisfied. False does not mean that KYC + // is strictly needed, unless also a + // legitimization_uuid is provided. + kyc_ok: boolean; + + // Time by which the exchange currently thinks the deposit will be executed. + // Actual execution may be later if the KYC check is not satisfied by then. + execution_time: Timestamp; + + // Public key associated with the account. The client must sign + // the initial request for the KYC status using the corresponding + // private key. Will be the merchant (instance) public key. + // + // This is ONLY given if the merchant did a KYC auth wire transfer. + // It is not given if the deposit was made to a reserve public key. + // The wallet would already know the reserve public key, plus there + // could be various reserve public keys (which do not change), while + // there is only the *latest* KYC auth wire transfer public key. + // + // Absent if no public key is currently associated + // with the account and the client MUST thus first + // credit the exchange via an inbound wire transfer + // to associate a public key with the debited account. + // Note that absence does *not* imply a need for a KYC auth + // transfer if the deposit was made to a reserve public key + // that was also used for the deposit. + // + // @since protocol **v20**. + account_pub?: EddsaPublicKey; + + } diff --git a/core/exchange/get-keys.rst b/core/exchange/get-keys.rst @@ -0,0 +1,621 @@ +.. http:get:: /keys + + Get a list of all denomination keys offered by the exchange, + as well as the exchange's current online signing key. + + **Request:** + + :query last_issue_date: Optional argument specifying the maximum value of any of the ``stamp_start`` members of the denomination keys of a ``/keys`` response that is already known to the client. Allows the exchange to only return keys that have changed since that timestamp. The given value must be an unsigned 64-bit integer representing seconds after 1970. If the timestamp does not exactly match the ``stamp_start`` of one of the denomination keys, all keys are returned. + + **Response:** + + :http:statuscode:`200 OK`: + The exchange responds with a `ExchangeKeysResponse` object. This request should + virtually always be successful. It only fails if the exchange is misconfigured or + has not yet been provisioned with key signatures via ``taler-exchange-offline``. + + **Details:** + + .. ts:def:: ExchangeKeysResponse + + interface ExchangeKeysResponse { + // libtool-style representation of the Exchange protocol version, see + // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning + // The format is "current:revision:age". + version: string; + + // The exchange's base URL. + base_url: string; + + // The exchange's currency or asset unit. + currency: string; + + // Shopping URL where users may find shops that accept + // digital cash issued by this exchange. + // @since protocol **v21**. + shopping_url?: string; + + // Open banking gateway base URL where wallets can + // initiate wire transfers to withdraw + // digital cash from this exchange. + // @since protocol **v30**. + open_banking_gateway?: string; + + // Instructs wallets to use certain bank-specific + // language (for buttons) and/or other UI/UX customization + // for compliance with the rules of that bank. + // The specific customizations to apply are done on a per-wallet + // basis as requested by the specific bank. They only + // apply when it is clear that the wallet is using digital + // cash from that bank. This is an advisory option, not + // all wallets must support all compliance languages. + // @since protocol **v24**. + bank_compliance_language?: string; + + // How wallets should render this currency. + currency_specification: CurrencySpecification; + + // Small(est?) amount that can likely be transferred to + // the exchange. Should be the default amount for KYC + // authentication wire transfers to this exchange. + // Optional, not present if not known or not configured. + // @since protocol **v21**. + tiny_amount?: Amount; + + // Absolute cost offset for the STEFAN curve used + // to (over) approximate fees payable by amount. + stefan_abs: Amount; + + // Factor to multiply the logarithm of the amount + // with to (over) approximate fees payable by amount. + // Note that the total to be paid is first to be + // divided by the smallest denomination to obtain + // the value that the logarithm is to be taken of. + stefan_log: Amount; + + // Linear cost factor for the STEFAN curve used + // to (over) approximate fees payable by amount. + // + // Note that this is a scalar, as it is multiplied + // with the actual amount. + stefan_lin: Float; + + // Type of the asset. "fiat", "crypto", "regional" + // or "stock". Wallets should adjust their UI/UX + // based on this value. + asset_type: string; + + // Array of wire accounts operated by the exchange for + // incoming wire transfers. + accounts: ExchangeWireAccount[]; + + // Object mapping names of wire methods (i.e. "iban" or "x-taler-bank") + // to wire fees. + wire_fees: { method : AggregateTransferFee[] }; + + // List of exchanges that this exchange is partnering + // with to enable wallet-to-wallet transfers. + wads: ExchangePartnerListEntry[]; + + // Set to true if this exchange allows the use + // of reserves for rewards. + // @deprecated in protocol **v18**. + rewards_allowed: false; + + // Set to true if this exchange has KYC enabled and thus + // requires KYC auth wire transfers prior to a first deposit. + // @since in protocol **v24**. + kyc_enabled: boolean; + + // Set to TRUE if wallets should disable the direct deposit feature + // and deposits should only go via Taler merchant APIs. + // Mainly used for regional currency and event currency deployments + // where wallets are not eligible to deposit back into originating + // bank accounts and, because KYC is not enabled, wallets are thus + // likely to send money to nirvana instead of where users want it. + // @since in protocol **v30**. + disable_direct_deposit: boolean; + + // EdDSA master public key of the exchange, used to sign entries + // in ``denoms`` and ``signkeys``. + master_public_key: EddsaPublicKey; + + // Relative duration until inactive reserves are closed; + // not signed (!), can change without notice. + reserve_closing_delay: RelativeTime; + + // Threshold amounts beyond which wallet should + // trigger the KYC process of the issuing exchange. + // Optional option, if not given there is no limit. + // Currency must match ``currency``. + wallet_balance_limit_without_kyc?: Amount[]; + + // Array of limits that apply to all accounts. + // All of the given limits will be hard limits. + // Wallets and merchants are expected to obey them + // and not even allow the user to cross them. + // @since protocol **v21**. + hard_limits: AccountLimit[]; + + // Array of limits with a soft threshold of zero + // that apply to all accounts without KYC. + // Wallets and merchants are expected to trigger + // a KYC process before attempting any zero-limited + // operations. + // @since protocol **v21**. + zero_limits: ZeroLimitedOperation[]; + + // Denominations offered by this exchange + denominations: DenomGroup[]; + + // Compact EdDSA `signature` (binary-only) over the + // contatentation of all of the master_sigs (in reverse + // chronological order by group) in the arrays under + // "denominations". Signature of `TALER_ExchangeKeySetPS` + exchange_sig: EddsaSignature; + + // Public EdDSA key of the exchange that was used to generate the signature. + // Should match one of the exchange's signing keys from ``signkeys``. It is given + // explicitly as the client might otherwise be confused by clock skew as to + // which signing key was used for the ``exchange_sig``. + exchange_pub: EddsaPublicKey; + + // Denominations for which the exchange currently offers/requests recoup. + recoup: RecoupDenoms[]; + + // Array of globally applicable fees by time range. + global_fees: GlobalFees[]; + + // The date when the denomination keys were last updated. + list_issue_date: Timestamp; + + // Auditors of the exchange. + auditors: AuditorKeys[]; + + // The exchange's signing keys. + signkeys: SignKey[]; + + // Optional field with a dictionary of (name, object) pairs defining the + // supported and enabled extensions, such as ``age_restriction``. + extensions?: { name: ExtensionManifest }; + + // Signature by the exchange master key of the SHA-256 hash of the + // normalized JSON-object of field extensions, if it was set. + // The signature has purpose TALER_SIGNATURE_MASTER_EXTENSIONS. + extensions_sig?: EddsaSignature; + + } + + The specification for the account object is: + + .. ts:def:: ExchangeWireAccount + + interface ExchangeWireAccount { + // Full ``payto://`` URI identifying the account and wire method + payto_uri: string; + + // URI to convert amounts from or to the currency used by + // this wire account of the exchange. Missing if no + // conversion is applicable. + conversion_url?: string; + + // Restrictions that apply to bank accounts that would send + // funds to the exchange (crediting this exchange bank account). + // Optional, empty array for unrestricted. + credit_restrictions: AccountRestriction[]; + + // Restrictions that apply to bank accounts that would receive + // funds from the exchange (debiting this exchange bank account). + // Optional, empty array for unrestricted. + debit_restrictions: AccountRestriction[]; + + // Signature using the exchange's offline key over + // a `TALER_MasterWireDetailsPS` + // with purpose ``TALER_SIGNATURE_MASTER_WIRE_DETAILS``. + master_sig: EddsaSignature; + + // Display label wallets should use to show this + // bank account. + // @since protocol **v19**. + bank_label?: string; + + // *Signed* integer with the display priority for + // this bank account. Optional, 0 if missing. + // @since protocol **v19**. + priority?: Integer; + + } + + .. ts:def:: AccountRestriction + + type AccountRestriction = + | RegexAccountRestriction + | DenyAllAccountRestriction + + .. ts:def:: DenyAllAccountRestriction + + // Account restriction that disables this type of + // account for the indicated operation categorically. + interface DenyAllAccountRestriction { + + type: "deny"; + } + + .. ts:def:: RegexAccountRestriction + + // Accounts interacting with this type of account + // restriction must have a normalized payto://-URI matching + // the given regex. + interface RegexAccountRestriction { + + type: "regex"; + + // Regular expression that the payto://-URI of the + // partner account must follow. The regular expression + // should follow posix-egrep, but without support for character + // classes, GNU extensions, back-references or intervals. See + // https://www.gnu.org/software/findutils/manual/html_node/find_html/posix_002degrep-regular-expression-syntax.html + // for a description of the posix-egrep syntax. Applications + // may support regexes with additional features, but exchanges + // must not use such regexes. + payto_regex: string; + + // Hint for a human to understand the restriction + // (that is hopefully easier to comprehend than the regex itself). + human_hint: string; + + // Map from IETF BCP 47 language tags to localized + // human hints. + human_hint_i18n?: { [lang_tag: string]: string }; + + } + + .. ts:def:: ZeroLimitedOperation + + interface ZeroLimitedOperation { + + // Operation that is limited to an amount of + // zero until the client has passed some KYC check. + // Must be one of "WITHDRAW", "DEPOSIT", + // (p2p) "MERGE", (wallet) "BALANCE", + // (reserve) "CLOSE", "AGGREGATE", + // "TRANSACTION" or "REFUND". + operation_type: string; + + } + + + .. ts:def:: AccountLimit + + interface AccountLimit { + + // Operation that is limited. + // Must be one of "WITHDRAW", "DEPOSIT", + // (p2p) "MERGE", (wallet) "BALANCE", + // (reserve) "CLOSE", "AGGREGATE", + // "TRANSACTION" or "REFUND". + operation_type: string; + + // Timeframe during which the limit applies. + // Not applicable for all operation_types + // (but always present in this object anyway). + timeframe: RelativeTime; + + // Maximum amount allowed during the given timeframe. + // Zero if the operation is simply forbidden. + threshold: Amount; + + // True if this is a soft limit that could be raised + // by passing KYC checks. Clients *may* deliberately + // try to cross limits and trigger measures resulting + // in 451 responses to begin KYC processes. + // Clients that are aware of hard limits *should* + // inform users about the hard limit and prevent flows + // in the UI that would cause violations of hard limits. + // Made optional in **v21** with a default of 'false' if missing. + soft_limit?: boolean; + } + + .. ts:def:: GlobalFees + + interface GlobalFees { + + // What date (inclusive) does these fees go into effect? + start_date: Timestamp; + + // What date (exclusive) does this fees stop going into effect? + end_date: Timestamp; + + // Account history fee, charged when a user wants to + // obtain a reserve/account history. + history_fee: Amount; + + // Annual fee charged for having an open account at the + // exchange. Charged to the account. If the account + // balance is insufficient to cover this fee, the account + // is automatically deleted/closed. (Note that the exchange + // will keep the account history around for longer for + // regulatory reasons.) + account_fee: Amount; + + // Purse fee, charged only if a purse is abandoned + // and was not covered by the account limit. + purse_fee: Amount; + + // How long will the exchange preserve the account history? + // After an account was deleted/closed, the exchange will + // retain the account history for legal reasons until this time. + history_expiration: RelativeTime; + + // 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; + + // How long does an exchange keep a purse around after a purse + // has expired (or been successfully merged)? A 'GET' request + // for a purse will succeed until the purse expiration time + // plus this value. + purse_timeout: RelativeTime; + + // Signature of `TALER_GlobalFeesPS`. + master_sig: EddsaSignature; + + } + + .. ts:def:: DenomGroup + + type DenomGroup = + | DenomGroupRsa + | DenomGroupCs + | DenomGroupRsaAgeRestricted + | DenomGroupCsAgeRestricted; + + .. ts:def:: DenomGroupRsa + + interface DenomGroupRsa extends DenomGroupCommon { + cipher: "RSA"; + + denoms: ({ + rsa_pub: RsaPublicKey; + } & DenomCommon)[]; + } + + .. ts:def:: DenomGroupCs + + interface DenomGroupCs extends DenomGroupCommon { + cipher: "CS"; + + denoms: ({ + cs_pub: Cs25519Point; + } & DenomCommon)[]; + } + + .. ts:def:: DenomGroupRsaAgeRestricted + + interface DenomGroupRsaAgeRestricted extends DenomGroupCommon { + cipher: "RSA+age_restricted"; + age_mask: AgeMask; + + denoms: ({ + rsa_pub: RsaPublicKey; + } & DenomCommon)[]; + } + + .. ts:def:: DenomGroupCsAgeRestricted + + interface DenomGroupCsAgeRestricted extends DenomGroupCommon { + cipher: "CS+age_restricted"; + age_mask: AgeMask; + + denoms: ({ + cs_pub: Cs25519Point; + } & DenomCommon)[]; + } + + .. ts:def:: DenomGroupCommon + + // Common attributes for all denomination groups + interface DenomGroupCommon { + // How much are coins of this denomination worth? + value: Amount; + + // Fee charged by the exchange for withdrawing a coin of this denomination. + fee_withdraw: Amount; + + // Fee charged by the exchange for depositing a coin of this denomination. + fee_deposit: Amount; + + // Fee charged by the exchange for refreshing a coin of this denomination. + fee_refresh: Amount; + + // Fee charged by the exchange for refunding a coin of this denomination. + fee_refund: Amount; + + } + + .. ts:def:: DenomCommon + + interface DenomCommon { + // Signature of `TALER_DenominationKeyValidityPS`. + master_sig: EddsaSignature; + + // When does the denomination key become valid? + stamp_start: Timestamp; + + // When is it no longer possible to withdraw coins + // of this denomination? Note that while this option + // is given per denomination, all concurrently active + // denominations (of the same cipher type) + // will have exactly the same withdraw + // expiration time. Thus, the wallet can be sure what + // is the smallest denomination being offered at any + // particular point in time, and not worry about the + // exchange having merely failed to sign the key of + // only the smallest denomination unit. + stamp_expire_withdraw: Timestamp; + + // When is it no longer possible to deposit coins + // of this denomination? + stamp_expire_deposit: Timestamp; + + // Timestamp indicating by when legal disputes relating to these coins must + // be settled, as the exchange will afterwards destroy its evidence relating to + // transactions involving this coin. + stamp_expire_legal: Timestamp; + + // Set to 'true' if the exchange somehow "lost" + // the private key. The denomination was not + // necessarily revoked, but still cannot be used + // to withdraw coins at this time (theoretically, + // the private key could be recovered in the + // future; coins signed with the private key + // remain valid). + lost?: boolean; + } + + Fees for any of the operations can be zero, but the fields must still be + present. The currency of the ``fee_deposit``, ``fee_refresh`` and ``fee_refund`` must match the + currency of the ``value``. Theoretically, the ``fee_withdraw`` could be in a + different currency, but this is not currently supported by the + implementation. + + .. ts:def:: RecoupDenoms + + interface RecoupDenoms { + // Hash of the public key of the denomination that is being revoked under + // emergency protocol (see ``/recoup``). + h_denom_pub: HashCode; + + // We do not include any signature here, as the primary use-case for + // this emergency involves the exchange having lost its signing keys, + // so such a signature here would be pretty worthless. However, the + // exchange will not honor ``/recoup`` requests unless they are for + // denomination keys listed here. + } + + A signing key in the ``signkeys`` list is a JSON object with the following fields: + + .. ts:def:: SignKey + + interface SignKey { + // The actual exchange's EdDSA signing public key. + key: EddsaPublicKey; + + // Initial validity date for the signing key. + stamp_start: Timestamp; + + // Date when the exchange will stop using the signing key, allowed to overlap + // slightly with the next signing key's validity to allow for clock skew. + stamp_expire: Timestamp; + + // Date when all signatures made by the signing key expire and should + // henceforth no longer be considered valid in legal disputes. + stamp_end: Timestamp; + + // Signature over ``key`` and ``stamp_expire`` by the exchange master key. + // Signature of `TALER_ExchangeSigningKeyValidityPS`. + // Must have purpose ``TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY``. + master_sig: EddsaSignature; + } + + An entry in the ``auditors`` list is a JSON object with the following fields: + + .. ts:def:: AuditorKeys + + interface AuditorKeys { + // The auditor's EdDSA signing public key. + auditor_pub: EddsaPublicKey; + + // The auditor's URL. + auditor_url: string; + + // The auditor's name (for humans). + auditor_name: string; + + // An array of denomination keys the auditor affirms with its signature. + // Note that the message only includes the hash of the public key, while the + // signature is actually over the expanded information including expiration + // times and fees. The exact format is described below. + denomination_keys: AuditorDenominationKey[]; + } + + .. ts:def:: AuditorDenominationKey + + interface AuditorDenominationKey { + // Hash of the public RSA key used to sign coins of the respective + // denomination. Note that the auditor's signature covers more than just + // the hash, but this other information is already provided in ``denoms`` and + // thus not repeated here. + denom_pub_h: HashCode; + + // Signature of `TALER_ExchangeKeyValidityPS`. + auditor_sig: EddsaSignature; + } + + The same auditor may appear multiple times in the array for different subsets + of denomination keys, and the same denomination key hash may be listed + multiple times for the same or different auditors. The wallet or merchant + just should check that the denomination keys they use are in the set for at + least one of the auditors that they accept. + + .. note:: + + Both the individual denominations *and* the denomination list is signed, + allowing customers to prove that they received an inconsistent list. + + Aggregate wire transfer fees representing the fees the exchange + charges per wire transfer to a merchant must be specified as an + array in all wire transfer response objects under ``fees``. The + respective array contains objects with the following members: + + .. ts:def:: AggregateTransferFee + + interface AggregateTransferFee { + // Per transfer wire transfer fee. + wire_fee: Amount; + + // Per transfer closing fee. + closing_fee: Amount; + + // What date (inclusive) does this fee go into effect? + // The different fees must cover the full time period in which + // any of the denomination keys are valid without overlap. + start_date: Timestamp; + + // What date (exclusive) does this fee stop going into effect? + // The different fees must cover the full time period in which + // any of the denomination keys are valid without overlap. + end_date: Timestamp; + + // Signature of `TALER_MasterWireFeePS` with + // purpose ``TALER_SIGNATURE_MASTER_WIRE_FEES``. + sig: EddsaSignature; + } + + .. ts:def:: ExchangePartnerListEntry + + interface ExchangePartnerListEntry { + // Base URL of the partner exchange. + partner_base_url: string; + + // Public master key of the partner exchange. + partner_master_pub: EddsaPublicKey; + + // Per exchange-to-exchange transfer (wad) fee. + wad_fee: Amount; + + // Exchange-to-exchange wad (wire) transfer frequency. + wad_frequency: RelativeTime; + + // When did this partnership begin (under these conditions)? + start_date: Timestamp; + + // How long is this partnership expected to last? + end_date: Timestamp; + + // Signature using the exchange's offline key over + // `TALER_WadPartnerSignaturePS` + // with purpose ``TALER_SIGNATURE_MASTER_PARTNER_DETAILS``. + master_sig: EddsaSignature; + } diff --git a/core/exchange/get-kyc-check-H_NORMALIZED_PAYTO.rst b/core/exchange/get-kyc-check-H_NORMALIZED_PAYTO.rst @@ -0,0 +1,143 @@ +.. http:get:: /kyc-check/$H_NORMALIZED_PAYTO + + Checks the KYC status of a particular payment target. That information + allows the customer to choose the next KYC measure to satisfy, if any. + This endpoint is typically used by wallets or merchants that + have been told that a transaction is not happening because it triggered + some KYC/AML measure and now want to check how the KYC/AML + requirement could be fulfilled (or whether it already has been + statisfied and the operation can now proceed). Long-polling may be used + to instantly observe a change in the KYC requirement status. + + Given a valid pair of a normalized payto hash and account owner signature, the + ``/kyc-check/`` endpoint returns either just the KYC status or redirects the + client (202) to the next required stage of the KYC process. The redirection + must be for an HTTP(S) endpoint to be triggered via a simple HTTP GET. It + must always be the same endpoint for the same client, as the wallet/merchant + backend are not required to check for changes to this endpoint. Clients + that received a 202 status code may repeat the request and use long-polling + to detect a change of the HTTP status. + + This endpoint exists in this specific form only since protocol **v21**. + + **Request:** + + *Account-Owner-Signature*: + The client must provide Base-32 encoded EdDSA signature with + ``$ACCOUNT_PRIV``, affirming the desire to obtain KYC data. Note that + this is merely a simple authentication mechanism, the details of the + request are not protected by the signature. The ``$ACCOUNT_PRIV`` is + either the (wallet long-term) reserve private key or the merchant instance + private key. + + *Account-Owner-Pub*: + The client should provide the Base-32 encoded EdDSA public key of + ``$ACCOUNT_PUB``, enabling the exchange to easily locate the correct + public key used for the signature in case multiple wire transfers + were made to the account in the past. + + :query lpt=TARGET: *Optional*. + Specifies what status change we are long-polling for. + Use 1 to wait for the KYC auth transfer (access token available), + 2 to wait for an AML investigation to be done, + and 3 to wait for the KYC status to be OK. + @since protocol **v21**. + :query min_rule=NUMBER: *Optional*. + Specifies the ``rule_num`` of the previous ``kyc-check`` + already known to the client. When long-polling, the server + should always respond when it has a more recent rule. + @since protocol **v26**. + :query timeout_ms=NUMBER: *Optional.* If specified, the exchange will + wait up to ``timeout_ms`` milliseconds if the requirement continues + to be mandatory provisioning of KYC data by the client. + Ignored if the HTTP status code is already ``200 Ok``. Note that + clients cannot long-poll for AML staff actions, so status information + about an account being under AML review needs to be requested + periodically. + + **Response:** + + :http:statuscode:`200 Ok`: + No mandatory KYC actions are required by the client at this time. + The client *may* still visit the KYC URL to initiate voluntary checks. + The response will be an `AccountKycStatus` object which specifies + restrictions that currently apply to the account. If the + client attempts to exceed *soft* limits, the status may change + to a ``202 Accepted``. Hard limits cannot be lifted by passing KYC checks. + :http:statuscode:`202 Accepted`: + The account holder performed an operation that would have crossed + limits (hard or soft) and must be redirected to the provided location to perform + the required KYC checks to satisfy the legal requirements. Afterwards, the + ``/kyc-check/`` request should be repeated to check whether the + user has completed the process. + The response will be an `AccountKycStatus` object. + :http:statuscode:`204 No content`: + The exchange is not configured to perform KYC and thus + the legal requirements are already satisfied. + :http:statuscode:`403 Forbidden`: + The provided signature is not acceptable for the given account. The response will be an `KycCheckAccountExpectedResponse` object which specifies the expected account public key. + :http:statuscode:`404 Not found`: + The account identified by the normalized payto hash is unknown. + :http:statuscode:`409 Conflict`: + The bank account is not (yet) associated with a public key. The account owner must do a wire transfer to the exchange with the account public key in the wire transfer subject to enable authentication. + + **Details:** + + .. ts:def:: KycCheckAccountExpectedResponse + + interface KycCheckAccountExpectedResponse { + + // Public key for which the signature must be + // valid to authorize this request. + expected_account_pub: EddsaPublicKey; + } + + .. ts:def:: AccountKycStatus + + interface AccountKycStatus { + + // Current AML state for the target account. True if + // operations are not happening due to staff processing + // paperwork *or* due to legal requirements (so the + // client cannot do anything but wait). + // + // Note that not every AML staff action may be legally + // exposed to the client, so this is merely a hint that + // a client should be told that AML staff is currently + // reviewing the account. AML staff *may* review + // accounts without this flag being set! + aml_review: boolean; + + // Monotonically increasing number identifying the decision. + // 0 if no decision was taken for this account. Useful for + // long-polling via ``min_rule`` to long-poll for any change + // to the rules or limits. + rule_gen: Integer; + + // Access token needed to construct the ``/kyc-spa/`` + // URL that the user should open in a browser to + // proceed with the KYC process (optional if the status + // type is ``200 Ok``, mandatory if the HTTP status + // is ``202 Accepted``). + access_token: AccountAccessToken; + + // Array with limitations that currently apply to this + // account and that may be increased or lifted if the + // KYC check is passed. + // Note that additional limits *may* exist and not be + // communicated to the client. If such limits are + // reached, this *may* be indicated by the account + // going into ``aml_review`` state. However, it is + // also possible that the exchange may legally have + // to deny operations without being allowed to provide + // any justification. + // The limits should be used by the client to + // possibly structure their operations (e.g. withdraw + // what is possible below the limit, ask the user to + // pass KYC checks or withdraw the rest after the time + // limit is passed, warn the user to not withdraw too + // much or even prevent the user from generating a + // request that would cause it to exceed hard limits). + limits?: AccountLimit[]; + + } diff --git a/core/exchange/get-kyc-info-ACCESS_TOKEN.rst b/core/exchange/get-kyc-info-ACCESS_TOKEN.rst @@ -0,0 +1,93 @@ +.. http:get:: /kyc-info/$ACCESS_TOKEN + + The ``/kyc-info/$ACCESS_TOKEN`` endpoints are created per client + account hash (but access controlled via a unique target token) + to return information about the state of the KYC or AML process + to the KYC SPA. The SPA uses this information to show the user an + appropriate dialog. The SPA should also long-poll this endpoint for changes + to the AML/KYC state. Note that this is a client-facing endpoint, so it will + only provide a restricted amount of information to the customer (as some + laws may forbid us to inform particular customers about their true status). + The endpoint will typically inform the SPA about possible choices to + proceed, such as directly uploading files, contacting AML staff, or + proceeding with a particular KYC process at an external provider (such as + Challenger). If the user chooses to initate a KYC process at an external + provider, the SPA must request the respective process to be set-up by the + exchange via the ``/kyc-start/`` endpoint. + + This endpoint was introduced in protocol **v20**. + + **Request:** + + *If-None-Match*: + The client MAY provide an ``If-None-Match`` header with an ETag. + + :query timeout_ms=MILLISECONDS: + *Optional.* If specified, the exchange will wait up to MILLISECONDS for + a change to a more recent legitimization measure before returning a 304 + Not Modified status. + + **Response:** + + *Etag*: Will be set to the serial ID of the measure. Used for long-polling (only for 200 OK responses). + + :http:statuscode:`200 OK`: + The body is a `KycProcessClientInformation`. + :http:statuscode:`202 Accepted`: + The exchange is currently processing the KYC status. The request should be + repeated later again. + :http:statuscode:`204 No Content`: + There are no open KYC requirements or possible voluntary checks + the client might perform. + :http:statuscode:`304 Not Modified`: + The KYC requirements did not change. + + + **Details:** + + .. ts:def:: KycProcessClientInformation + + interface KycProcessClientInformation { + + // Array of requirements. + requirements: KycRequirementInformation[]; + + // True if the client is expected to eventually satisfy all requirements. + // Default (if missing) is false. + is_and_combinator?: boolean + + // List of available voluntary checks the client could undertake. + // @since protocol **vATTEST**. + voluntary_measures?: KycRequirementInformation[]; + } + + .. ts:def:: KycRequirementInformation + + interface KycRequirementInformation { + + // Which form should be used? Common values include "INFO" + // (to just show the descriptions but allow no action), + // "LINK" (to enable the user to obtain a link via + // ``/kyc-start/``) or any build-in form name supported + // by the SPA. + form: string; + + // Object with arbitrary additional context, completely depends on + // the specific form. + context?: Object; + + // English description of the requirement. + description: string; + + // Map from IETF BCP 47 language tags to localized + // description texts. + description_i18n ?: { [lang_tag: string]: string }; + + // ID of the requirement, useful to construct the + // ``/kyc-upload/$ID`` or ``/kyc-start/$ID`` endpoint URLs. + // Present if and only if "form" is not "INFO". The + // ``$ID`` value may itself contain ``/`` or ``?`` and + // basically encode any URL path (and optional arguments). + id?: string; + + } diff --git a/core/exchange/get-kyc-proof-PROVIDER_NAME.rst b/core/exchange/get-kyc-proof-PROVIDER_NAME.rst @@ -0,0 +1,62 @@ +.. http:get:: /kyc-proof/$PROVIDER_NAME?state=$H_NORMALIZED_PAYTO + + Upon completion of the process at the external KYC provider, the provider + must redirect the client (browser) to trigger a GET request to a new + ``/kyc-proof/$H_NORMALIZED_PAYTO/$PROVIDER_NAME`` endpoint. Once this endpoint is + triggered, the exchange will pass the received arguments to the respective + logic plugin. The logic plugin will then (asynchronously) update the KYC + status of the user. The logic plugin should redirect the user to the KYC + SPA. This endpoint deliberately does not use the ``$ACCESS_TOKEN`` as the + external KYC provider should not learn that token. + + This endpoint is thus accessed from the user's browser at the *end* of a + KYC process, possibly providing the exchange with additional + credentials to obtain the results of the KYC process. + Specifically, the URL arguments should provide + information to the exchange that allows it to verify that the + user has completed the KYC process. The details depend on + the logic, which is selected by the "$PROVIDER_NAME". + + While this is a GET (and thus safe, and idempotent), the operation + may actually trigger significant changes in the exchange's state. + In particular, it may update the KYC status of a particular + payment target. + + **Request:** + + Details on the request depend on the specific KYC logic + that was used. + + If the KYC plugin logic is OAuth 2.0, the query parameters are: + + :query code=CODE: + OAuth 2.0 code argument. + :query state=STATE: + OAuth 2.0 state argument with the H_NORMALIZED_PAYTO. + + .. note:: + + Depending on the OAuth variant used, additional + query parameters may need to be passed here. + + **Response:** + + Given that the response is returned to a user using a browser and **not** to + a Taler wallet, the response format is in human-readable HTML and not in + machine-readable JSON. + + :http:statuscode:`302 Found`: + The KYC operation succeeded and the + payment target is now authorized to transact. + The browser is redirected to a human-readable + page configured by the exchange operator. + :http:statuscode:`401 Unauthorized`: + The provided authorization token is invalid. + :http:statuscode:`404 Not found`: + The payment target is unknown. + :http:statuscode:`502 Bad Gateway`: + The exchange received an invalid reply from the + legitimization service. + :http:statuscode:`504 Gateway Timeout`: + The exchange did not receive a reply from the legitimization + service within a reasonable time period. diff --git a/core/exchange/get-kyc-spa-ACCESS_TOKEN.rst b/core/exchange/get-kyc-spa-ACCESS_TOKEN.rst @@ -0,0 +1,12 @@ +.. http:get:: /kyc-spa/$ACCESS_TOKEN +.. http:get:: /kyc-spa/$FILENAME + + A set of ``/kyc-spa/$ACCESS_TOKEN`` GET endpoints is created per account + hash that serves the KYC SPA. This is where the ``/kyc-check/`` endpoint + will in principle redirect clients. The KYC SPA will use the + ``$ACCESS_TOKEN`` of its URL to initialize itself via the + ``/kyc-info/$ACCESS_TOKEN`` endpoint family. The KYC SPA may download + additional resources via ``/kyc-spa/$FILENAME``. The filenames must not + match base32-encoded 256-bit values. + + This endpoint was introduced in protocol **v20**. diff --git a/core/exchange/get-kyc-webhook-PROVIDER_NAME-star.rst b/core/exchange/get-kyc-webhook-PROVIDER_NAME-star.rst @@ -0,0 +1,22 @@ +.. http:get:: /kyc-webhook/$PROVIDER_NAME/* +.. http:post:: /kyc-webhook/$PROVIDER_NAME/* +.. http:get:: /kyc-webhook/$LOGIC/* +.. http:post:: /kyc-webhook/$LOGIC/* + + All of the above endpoints can be used to update KYC status of a particular + payment target. They provide information to the KYC logic of the exchange + that allows it to verify that the user has completed the KYC process. May + be a GET or a POST request, depending on the specific "$LOGIC" and/or the + "$PROVIDER_NAME". + + **Request:** + + Details on the request depend on the specific KYC logic + that was used. + + **Response:** + + :http:statuscode:`204 No content`: + The operation succeeded. + :http:statuscode:`404 Not found`: + The specified logic is unknown. diff --git a/core/exchange/get-management-keys.rst b/core/exchange/get-management-keys.rst @@ -0,0 +1,138 @@ +.. http:get:: /management/keys + + Get a list of future public keys to be used by the exchange. Only to be + used by the exchange's offline key management team. Not useful for anyone + else (but also not secret, so access is public). + + **Response:** + + :http:statuscode:`200 OK`: + The exchange responds with a `FutureKeysResponse` object. This request should + virtually always be successful. + + **Details:** + + .. ts:def:: FutureKeysResponse + + interface FutureKeysResponse { + + // Future denominations to be offered by this exchange + // (only those lacking a master signature). + future_denoms: FutureDenom[]; + + // The exchange's future signing keys (only those lacking a master signature). + future_signkeys: FutureSignKey[]; + + // Master public key expected by this exchange (provided so that the + // offline signing tool can check that it has the right key). + master_pub: EddsaPublicKey; + + // Public key of the denomination security module. + denom_secmod_public_key: EddsaPublicKey; + + // Public key of the signkey security module. + signkey_secmod_public_key: EddsaPublicKey; + + } + + .. ts:def:: FutureDenom + + interface FutureDenom { + // Name in the configuration file that defines this denomination. + section_name: string; + + // How much are coins of this denomination worth? + value: Amount; + + // When does the denomination key become valid? + stamp_start: Timestamp; + + // When is it no longer possible to withdraw coins + // of this denomination? + stamp_expire_withdraw: Timestamp; + + // When is it no longer possible to deposit coins + // of this denomination? + stamp_expire_deposit: Timestamp; + + // Timestamp indicating by when legal disputes relating to these coins must + // be settled, as the exchange will afterwards destroy its evidence relating to + // transactions involving this coin. + stamp_expire_legal: Timestamp; + + // Public key for the denomination. + denom_pub: DenominationKey; + + // Fee charged by the exchange for withdrawing a coin of this denomination. + fee_withdraw: Amount; + + // Fee charged by the exchange for depositing a coin of this denomination. + fee_deposit: Amount; + + // Fee charged by the exchange for refreshing a coin of this denomination. + fee_refresh: Amount; + + // Fee charged by the exchange for refunding a coin of this denomination. + fee_refund: Amount; + + // Signature by the denomination security module + // over `TALER_DenominationKeyAnnouncementPS` + // for this denomination with purpose + // ``TALER_SIGNATURE_SM_DENOMINATION_KEY``. + denom_secmod_sig: EddsaSignature; + + } + + .. ts:def:: DenominationKey + + type DenominationKey = + | RsaDenominationKey + | CSDenominationKey; + + .. ts:def:: RsaDenominationKey + + interface RsaDenominationKey { + cipher: "RSA"; + + // 32-bit age mask. + age_mask: Integer; + + // RSA public key + rsa_pub: RsaPublicKey; + } + + .. ts:def:: CSDenominationKey + + interface CSDenominationKey { + cipher: "CS"; + + // 32-bit age mask. + age_mask: Integer; + + // Public key of the denomination. + cs_pub: Cs25519Point; + + } + + .. ts:def:: FutureSignKey + + interface FutureSignKey { + // The actual exchange's EdDSA signing public key. + key: EddsaPublicKey; + + // Initial validity date for the signing key. + stamp_start: Timestamp; + + // Date when the exchange will stop using the signing key, allowed to overlap + // slightly with the next signing key's validity to allow for clock skew. + stamp_expire: Timestamp; + + // Date when all signatures made by the signing key expire and should + // henceforth no longer be considered valid in legal disputes. + stamp_end: Timestamp; + + // Signature over `TALER_SigningKeyAnnouncementPS` + // for this signing key by the signkey security + // module using purpose ``TALER_SIGNATURE_SM_SIGNING_KEY``. + signkey_secmod_sig: EddsaSignature; + } diff --git a/core/exchange/get-purses-PURSE_PUB-merge.rst b/core/exchange/get-purses-PURSE_PUB-merge.rst @@ -0,0 +1,70 @@ +.. http:get:: /purses/$PURSE_PUB/merge +.. http:get:: /purses/$PURSE_PUB/deposit + + Obtain information about a purse. Depending on the suffix, + the long-polling (if any) will wait for either a merge or + a deposit event. + + **Request:** + + :query timeout_ms=NUMBER: *Optional.* If specified, + the exchange + will wait up to ``NUMBER`` 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 ``NUMBER`` milliseconds for completion + of a deposit operation before sending the HTTP response. + + **Response:** + + :http:statuscode:`200 OK`: + The operation succeeded, the exchange provides details + about the purse. + The response will include a `PurseStatus` object. + :http:statuscode:`404 Not found`: + The purse is unknown to the exchange. + :http:statuscode:`410 Gone`: + The purse expired before the deposit or merge was completed. + + **Details:** + + .. ts:def:: PurseStatus + + interface PurseStatus { + + // Total amount deposited into the purse so far. + // If 'total_deposit_amount' minus 'deposit_fees' + // exceeds 'merge_value_after_fees', and a + // 'merge_request' exists for the purse, then the + // purse will (have been) merged with the account. + balance: Amount; + + // Time of the merge, missing if "never". + merge_timestamp?: Timestamp; + + // Time of the deposits being complete, missing if "never". + // Note that this time may not be "stable": once sufficient + // deposits have been made, is "now" before the purse + // expiration, and otherwise set to the purse expiration. + // However, this should also not be relied upon. The key + // property is that it is either "never" or in the past. + deposit_timestamp?: Timestamp; + + // Time when the purse expires and + // funds that were not merged are refunded + // on the deposited coins. + // FIXME: Document the exchange protocol version + // in which this field became available. + purse_expiration: Timestamp; + + // EdDSA signature of the exchange over a + // `TALER_PurseStatusResponseSignaturePS` + // with purpose ``TALER_SIGNATURE_PURSE_STATUS_RESPONSE`` + // affirming the purse status. + exchange_sig: EddsaSignature; + + // EdDSA public key exchange used for 'exchange_sig'. + exchange_pub: EddsaPublicKey; + + } diff --git a/core/exchange/get-reserves-RESERVE_PUB-history.rst b/core/exchange/get-reserves-RESERVE_PUB-history.rst @@ -0,0 +1,308 @@ +.. http:get:: /reserves/$RESERVE_PUB/history + + Request information about the full history of + a reserve or an account. + + **Request:** + + 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 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. + + :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. + + **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. + + **Details:** + + .. ts:def:: ReserveHistory + + interface ReserveHistory { + // Balance left in the reserve. + balance: Amount; + + // If set, gives the maximum age group that the client is required to set + // during withdrawal. + maximum_age_group: Integer; + + // Transaction history for this reserve. + // May be partial (!). + history: TransactionHistoryItem[]; + } + + Objects in the transaction history have the following format: + + .. ts:def:: TransactionHistoryItem + + // Union discriminated by the "type" field. + type TransactionHistoryItem = + | AccountSetupTransaction + | ReserveWithdrawTransaction + | ReserveCreditTransaction + | ReserveClosingTransaction + | ReserveOpenRequestTransaction + | ReserveCloseRequestTransaction + | PurseMergeTransaction; + + .. ts:def:: AccountSetupTransaction + + interface AccountSetupTransaction { + type: "SETUP"; + + // Offset of this entry in the reserve history. + // Useful to request incremental histories via + // the "start" query parameter. + history_offset: Integer; + + // KYC fee agreed to by the reserve owner. + kyc_fee: Amount; + + // Time when the KYC was triggered. + kyc_timestamp: Timestamp; + + // 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; + + // 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:: ReserveWithdrawTransaction + + interface ReserveWithdrawTransaction { + type: "WITHDRAW"; + + // Offset of this entry in the reserve history. + // Useful to request incremental histories via + // the "start" query parameter. + history_offset: Integer; + + // Amount withdrawn. + amount: Amount; + + // Total fee that is charged for withdraw. + withdraw_fee: Amount; + + // Total number of coins in the withdraw request + num_coins: Integer; + + // Signature over a `TALER_WithdrawRequestPS` + // with purpose ``TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW`` + // created with the reserve's private key. + reserve_sig: EddsaSignature; + + // The hash of the all the planchets that were provided during the + // call to /withdraw. + h_planchets: HashCode; + + // The blinding seed that was provided. It will be NULL if + // no denominations of cipher type Clause-Schnorr were invovled + blinding_seed?: BlindingMasterSeed; + + // The array of hashes of public key of denominations for the coins. + denom_pub_hashes: HashCode[]; + + // The maximum age committed to, if the withdraw request + // required age-restriction + max_age?: Integer; + + // The noreveal index that was returned as part + // of a age-restricted withdraw, if applicable + noreveal_index?: Integer; + + } + + + .. ts:def:: ReserveCreditTransaction + + 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; + + // Amount deposited. + amount: Amount; + + // Sender account full payto:// URI. + sender_account_url: string; + + // 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:: ReserveClosingTransaction + + 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; + + // Closing balance. + amount: Amount; + + // Closing fee charged by the exchange. + closing_fee: Amount; + + // Wire transfer subject. + wtid: Base32; + + // Full payto URI of the wire account into which the funds were returned to. + receiver_account_details: string; + + // 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; + } + + + .. ts:def:: ReserveOpenRequestTransaction + + 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; + + // Open fee paid from the reserve. + open_fee: Amount; + + // This is a signature over + // a struct `TALER_ReserveOpenPS` with purpose + // ``TALER_SIGNATURE_WALLET_RESERVE_OPEN``. + reserve_sig: EddsaSignature; + + // Timestamp of the open request. + request_timestamp: Timestamp; + + // Requested expiration. + requested_expiration: Timestamp; + + // Requested number of free open purses. + requested_min_purses: Integer; + + } + + .. ts:def:: ReserveCloseRequestTransaction + + interface ReserveCloseRequestTransaction { + type: "CLOSE"; + + // Offset of this entry in the reserve history. + // Useful to request incremental histories via + // the "start" query parameter. + history_offset: Integer; + + // This is a signature over + // a struct `TALER_ReserveClosePS` with purpose + // ``TALER_SIGNATURE_WALLET_RESERVE_CLOSE``. + reserve_sig: EddsaSignature; + + // Hash over the full payto URI of the target account. + h_payto?: FullPaytoHash; + + // Timestamp of the close request. + request_timestamp: Timestamp; + } + + .. ts:def:: PurseMergeTransaction + + 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; + + // 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; + + // 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; + + // Client-side timestamp of when the merge request was made. + merge_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 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; + } diff --git a/core/exchange/get-reserves-RESERVE_PUB.rst b/core/exchange/get-reserves-RESERVE_PUB.rst @@ -0,0 +1,44 @@ +.. http:get:: /reserves/$RESERVE_PUB + + Request summary information about a reserve. + + **Request:** + + :query timeout_ms=MILLISECONDS: *Optional.* If specified, the exchange will wait up to MILLISECONDS for incoming funds before returning a 404 if the reserve does not yet exist. + + **Response:** + + :http:statuscode:`200 OK`: + The exchange responds with a `ReserveSummary` object; the reserve was known to the exchange. + :http:statuscode:`404 Not found`: + The reserve key does not belong to a reserve known to the exchange. + + **Details:** + + .. ts:def:: ReserveSummary + + interface ReserveSummary { + // Balance left in the reserve. + balance: Amount; + + // Full payto URI of the bank account that + // (most recently) funded this reserve. + // Useful as a hint for deposit operations for wallets. + // Missing if this reserve was only filled via P2P merges. + // @since protocol **v23**. + last_origin?: string; + + // The expiration date of the reserve. + // Any residual value of in the reserve after this date + // will be returned to the ``last_origin``, if present. + // @since protocol **vRECOUP**. + reserve_expiration: Timestamp; + + // If set, proof of age restriction is required and age restriction needs + // to be set for each coin to this value during the withdrawal from this reserve. + // The client then MUST use a denomination with support for age restriction enabled + // for the withdrawal and MUST set an appropriate value for ``max_age`` + // The value represents a valid age group from the list of permissible + // age groups as defined by the exchange's output to /keys. + maximum_age_group?: Integer; + } diff --git a/core/exchange/get-reserves-attest-RESERVE_PUB.rst b/core/exchange/get-reserves-attest-RESERVE_PUB.rst @@ -0,0 +1,25 @@ +.. http:get:: /reserves-attest/$RESERVE_PUB + + Request list of available KYC attributes about the owner of a reserve. + Used as a preliminary step to find out which subsets of attributes the + exchange could provide signatures over. + + **Response:** + + :http:statuscode:`200 OK`: + The exchange responds with a `ReserveKycAttributes` object. + :http:statuscode:`404 Not found`: + The reserve key does not belong to a reserve known to the exchange. + :http:statuscode:`409 Conflict`: + The exchange does not have the requested KYC information. + + **Details:** + + .. ts:def:: ReserveKycAttributes + + interface ReserveKycAttributes { + // Array of KYC attributes available. The attribute names + // listed are expected to be from the respective GANA + // registry. + details: string[]; + } diff --git a/core/exchange/get-seed.rst b/core/exchange/get-seed.rst @@ -0,0 +1,8 @@ +.. http:get:: /seed + + Return an entropy seed. The exchange will return a high-entropy + value that will differ for every call. The response is NOT in + JSON, but simply high-entropy binary data in the HTTP body. + This API should be used by wallets to guard themselves against + running on low-entropy (bad PRNG) hardware. Naturally, the entropy + returned MUST be mixed with locally generated entropy. diff --git a/core/exchange/get-transfers-WTID.rst b/core/exchange/get-transfers-WTID.rst @@ -0,0 +1,74 @@ +.. http:get:: /transfers/$WTID + + Provides deposits associated with a given wire transfer. The + wire transfer identifier (WTID) and the base URL for tracking + the wire transfer are both given in the wire transfer subject. + + **Request:** + + **Response:** + + :http:statuscode:`200 OK`: + The wire transfer is known to the exchange, details about it follow in the body. + The body of the response is a `TrackTransferResponse`. + :http:statuscode:`404 Not found`: + The wire transfer identifier is unknown to the exchange. + + .. ts:def:: TrackTransferResponse + + interface TrackTransferResponse { + // Actual amount of the wire transfer, excluding the wire fee. + total: Amount; + + // Applicable wire fee that was charged. + wire_fee: Amount; + + // Public key of the merchant (identical for all deposits). + merchant_pub: EddsaPublicKey; + + // Hash of the payto:// account URI (identical for all deposits). + h_payto: FullPaytoHash; + + // Time of the execution of the wire transfer by the exchange. + execution_time: Timestamp; + + // Details about the deposits. + deposits: TrackTransferDetail[]; + + // Signature from the exchange made with purpose + // ``TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT`` + // over a `TALER_WireDepositDataPS`. + exchange_sig: EddsaSignature; + + // Public EdDSA key 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: EddsaSignature; + } + + .. ts:def:: TrackTransferDetail + + interface TrackTransferDetail { + // SHA-512 hash of the contact of the merchant with the customer. + h_contract_terms: HashCode; + + // Coin's public key, both ECDHE and EdDSA. + coin_pub: CoinPublicKey; + + // The total amount the original deposit was worth, + // including fees and after applicable refunds. + deposit_value: Amount; + + // Applicable fees for the deposit, possibly + // reduced or waived due to refunds. + deposit_fee: Amount; + + // Refunds that were applied to the value of + // this coin. Optional. + // @since protocol **v19**. Before, refunds were + // incorrectly still included in the + // ``deposit_value`` (!). + refund_total?: Amount; + + } diff --git a/core/exchange/get-wads-WAD_ID.rst b/core/exchange/get-wads-WAD_ID.rst @@ -0,0 +1,82 @@ +.. 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; + + // Indicative time by which the wad was given to the + // bank to execute the wire transfer. + wad_execution_time: Timestamp; + + // Transfers aggregated in the wad. + items: WadItem[]; + + // EdDSA signature of the exchange affirming the wad + // data is correct, must be over `TALER_WadDataSignaturePS` + // and of purpose ``TALER_SIGNATURE_WAD_DATA``. + exchange_sig: EddsaSignature; + + // public key used to create the signature. + exchange_pub: EddsaPublicKey; + } + + Objects in the wad item list have the following format: + + .. ts:def:: WadItem + + interface WadItem { + + // Amount in the purse. + amount: Amount; + + // Normalized payto URI of the account the purse is to be merged into. + // Must be of the form: 'payto://taler/EXCHANGE_URL/RESERVE_PUB'. + payto_uri: string; + + // Purse public key. + purse_pub: EddsaPublicKey; + + // Hash of the contract. + h_contract: HashCode; + + // Indicative time by which the purse should expire + // if it has not been paid. + purse_expiration: Timestamp; + + // Client-side timestamp of when the merge request was made. + merge_timestamp: Timestamp; + + // Signature created with the reserve's private key. + // Must be of purpose ``TALER_SIGNATURE_ACCOUNT_MERGE`` + // and over `TALER_AccountMergeSignaturePS`. + reserve_sig: EddsaSignature; + + // Signature created with the purse's private key. + // Must be of purpose ``TALER_SIGNATURE_PURSE_MERGE`` + // and over `TALER_PurseMergeSignaturePS`. + 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; + } diff --git a/core/exchange/post-aml-OFFICER_PUB-decision.rst b/core/exchange/post-aml-OFFICER_PUB-decision.rst @@ -0,0 +1,89 @@ +.. http:post:: /aml/$OFFICER_PUB/decision + + Make an AML decision. Triggers the respective action and + records the justification. + + **Request:** + + The request must be an `AmlDecisionRequest` message. + + **Response** + + :http:statuscode:`204 No content`: + The AML decision has been executed and recorded successfully. + :http:statuscode:`403 Forbidden`: + The signature is invalid (or the AML officer not known). + :http:statuscode:`404 Not found`: + The normalized payto-address the decision was made for is unknown to the exchange. + :http:statuscode:`409 Conflict`: + The designated AML account is not enabled or a more recent + decision was already submitted. + + **Details:** + + .. ts:def:: AmlDecisionRequest + + interface AmlDecisionRequest { + + // Human-readable justification for the decision. + justification: string; + + // Hash of normalized payto-address of the account the decision is about. + // Identifies a GNU Taler wallet or an affected bank account. + h_payto: NormalizedPaytoHash; + + // Full payto address of the account the decision is about. + // Optional. Must be given if the account is not yet + // known to the exchange. If given, must match ``h_payto`` + // (when normalized and then hashed). + // @since protocol **v21**. + payto_uri?: string; + + // What are the new rules? + // New since protocol **v20**. + new_rules: LegitimizationRuleSet; + + // What are the new account properties? + // New since protocol **v20**. + properties: AccountProperties; + + // Array of AML/KYC events to trigger for statistics. + // Note that this information is not covered by the signature + // (which is OK as events are just for statistics). + // New since protocol **v24**. + events?: string[]; + + // Space-separated list of measures to trigger + // immediately on the account. + // Prefixed with a "+" to indicate that the + // measures should be ANDed. + // Should typically be used to give the user some + // information or request additional information. + // + // At most one measure with a SKIP check may be specified. + // + // @since protocol **v21**. + new_measures?: string; + + // True if the account should remain under investigation by AML staff. + // @since protocol **v20**. + keep_investigating: boolean; + + // Signature by the AML officer over a `TALER_AmlDecisionPS`. + // Must have purpose ``TALER_SIGNATURE_MASTER_AML_KEY``. + officer_sig: EddsaSignature; + + // When was the decision made? + decision_time: Timestamp; + + // KYC attributes uploaded by the AML officer + // The object *must* contain high-entropy salt, + // as the hash of the attributes will be + // stored in plain text. + attributes?: CustomerKycAttributes; + + // Expiration timestamp of the attributes. + // Mandatory if attributes are present. + attributes_expiration?: Timestamp; + + } diff --git a/core/exchange/post-auditors-AUDITOR_PUB-H_DENOM_PUB.rst b/core/exchange/post-auditors-AUDITOR_PUB-H_DENOM_PUB.rst @@ -0,0 +1,63 @@ +.. http:post:: /auditors/$AUDITOR_PUB/$H_DENOM_PUB + + This is used to add an auditor signature to the ``/keys`` response. It + affirms to wallets and merchants that this auditor is indeed auditing + the coins issued by the respective denomination. There is no "delete" + operation for this, as auditors can only stop auditing a denomination + when it expires. + + **Request:** + + The request must be a `AuditorSignatureAddMessage`. + + **Response:** + + :http:statuscode:`204 No content`: + The backend has successfully stored the auditor signature. + :http:statuscode:`403 Forbidden`: + The auditor signature is invalid. + :http:statuscode:`404 Not found`: + The denomination key for which the auditor is providing a signature is unknown. + The response will be a `DenominationUnknownMessage`. + :http:statuscode:`410 Gone`: + This auditor is no longer supported by the exchange. + :http:statuscode:`412 Precondition failed`: + This auditor is not yet known to the exchange. + + **Details:** + + .. ts:def:: DenominationUnknownMessage + + interface DenominationUnknownMessage { + + // Taler error code + // ``TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN`` + code: Integer; + + // Signature by the exchange over a + // `TALER_DenominationUnknownAffirmationPS`. + // Must have purpose ``TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_UNKNOWN``. + exchange_sig: EddsaSignature; + + // Public key of the exchange used to create + // the 'exchange_sig. + exchange_pub: EddsaPublicKey; + + // Hash of the denomination public key that is unknown. + h_denom_pub: HashCode; + + // When was the signature created. + timestamp: Timestamp; + + } + + .. ts:def:: AuditorSignatureAddMessage + + interface AuditorSignatureAddMessage { + + // Signature by the auditor over a + // `TALER_ExchangeKeyValidityPS`. + // Must have purpose ``TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS``. + auditor_sig: EddsaSignature; + + } diff --git a/core/exchange/post-batch-deposit.rst b/core/exchange/post-batch-deposit.rst @@ -0,0 +1,338 @@ +.. http:post:: /batch-deposit + + Deposit multiple coins and ask the exchange to transfer the given :ref:`amount` + into the merchant's bank account. This API is used by the merchant to redeem + the digital coins. + + **Request:** + + The request body must be a `BatchDepositRequest` object. + + **Response:** + + :http:statuscode:`200 OK`: + The operation succeeded, the exchange confirms that no double-spending took + place. The response will include a `DepositSuccess` object. + :http:statuscode:`403 Forbidden`: + One of the signatures is invalid. + This response comes with a standard `ErrorDetail` response. + :http:statuscode:`404 Not found`: + Either one of the denomination keys is not recognized (expired or invalid), + or the wire type is not recognized. + If a denomination key is unknown, the response will be + a `DenominationUnknownMessage`. + :http:statuscode:`409 Conflict`: + The deposit operation has either failed because a coin has insufficient + residual value, or because the same public key of a coin has been + previously used with a different denomination. + Which case it is can be decided by looking at the error code: + + 1. ``TALER_EC_EXCHANGE_DEPOSIT_CONFLICTING_CONTRACT`` (same coin used in different ways), + 2. ``TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS`` (balance insufficient), + 3. ``TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY`` (same coin public key, but different denomination). + 4. ``TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_AGE_HASH`` (same coin public key, but different age commitment). + + The request should not be repeated again with this coin. Instead, the client + can get from the exchange via the ``/coin/$COIN_PUB/history`` endpoint the record + of the transactions known for this coin's public key. + :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 + `DenominationGoneMessage`. 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 merchant has not yet passed the KYC checks. + The client must pass KYC checks before proceeding with the deposit. + The response will be an `LegitimizationNeededResponse` object. + @since protocol **v21**. + + **Details:** + + .. ts:def:: BatchDepositRequest + + interface BatchDepositRequest { + + // The merchant's account details as a full payto URI. + merchant_payto_uri: string; + + // The salt is used to hide the ``payto_uri`` from customers + // when computing the ``h_wire`` of the merchant. + wire_salt: WireSalt; + + // SHA-512 hash of the contract of the merchant with the customer. Further + // details are never disclosed to the exchange. + h_contract_terms: HashCode; + + // Merchant's signature over the h_contract_terms. + // @since protocol **v22** + merchant_sig: EddsaSignature; + + // The list of coins that are going to be deposited with this Request. + coins: BatchDepositRequestCoin[]; + + // Timestamp when the contract was finalized. + timestamp: Timestamp; + + // Indicative time by which the exchange undertakes to transfer the funds to + // the merchant, in case of successful payment. A wire transfer deadline of 'never' + // is not allowed. + wire_transfer_deadline: Timestamp; + + // EdDSA `public key of the merchant <merchant-pub>`, so that the client can identify the + // merchant for refund requests. + merchant_pub: EddsaPublicKey; + + // Date until which the merchant can issue a refund to the customer via the + // exchange, to be omitted if refunds are not allowed. + // + // THIS FIELD WILL BE DEPRICATED, once the refund mechanism becomes a + // policy via extension. + refund_deadline?: Timestamp; + + // CAVEAT: THIS IS WORK IN PROGRESS + // (Optional) policy for the batch-deposit. + // This might be a refund, auction or escrow policy. + policy?: DepositPolicy; + } + + .. ts:def:: BatchDepositRequestCoin + + interface BatchDepositRequestCoin { + // EdDSA public key of the coin being deposited. + coin_pub: EddsaPublicKey; + + // 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: DenominationSignature; + + // Amount to be deposited, can be a fraction of the + // coin's total value. + contribution: Amount; + + // Signature over `TALER_DepositRequestPS`, made by the customer with the + // `coin's private key <coin-priv>`. + coin_sig: EddsaSignature; + } + + .. ts:def:: DenominationSignature + + type DenominationSignature = DenomCipher & ( + | RsaDenominationSignature + | CSDenominationSignature + ); + + .. ts:def:: RsaDenominationSignature + + interface RsaDenominationSignature extends DenomCipher { + cipher: "RSA"; + + // RSA signature + rsa_signature: RsaSignature; + } + + .. ts:def:: CSDenominationSignature + + interface CSDenominationSignature extends DenomCipher { + cipher: "CS"; + + // R value component of the signature. + cs_signature_r: Cs25519Point; + + // s value component of the signature. + cs_signature_s: Cs25519Scalar; + + } + + .. ts:def:: DepositPolicy + + type DepositPolicy = + | PolicyMerchantRefund + | PolicyBrandtVickreyAuction + | PolicyEscrowedPayment; + + .. ts:def:: PolicyMerchantRefund + + // CAVEAT: THIS IS STILL WORK IN PROGRESS. + // This policy is optional and might not be supported by the exchange. + // If it does, the exchange MUST show support for this policy in the + // ``extensions`` field in the response to ``/keys``. + interface PolicyMerchantRefund { + type: "merchant_refund"; + + // EdDSA `public key of the merchant <merchant-pub>`, so that the client + // can identify the merchant for refund requests. + merchant_pub: EddsaPublicKey; + + // Date until which the merchant can issue a refund to the customer via + // the ``/extensions/policy_refund``-endpoint of the exchange. + deadline: Timestamp; + } + + .. ts:def:: PolicyBrandtVickreyAuction + + // CAVEAT: THIS IS STILL WORK IN PROGRESS. + // This policy is optional and might not be supported by the exchange. + // If it does, the exchange MUST show support for this policy in the + // ``extensions`` field in the response to ``/keys``. + interface PolicyBrandtVickreyAuction { + type: "brandt_vickrey_auction"; + + // Public key of this bidder. + // + // The bidder uses this key to sign the auction information and + // the messages it sends to the seller during the auction. + bidder_pub: EddsaPublicKey; + + // Hash of the auction terms + // + // The hash should be taken over a normalized JSON object of type + // `BrandtVickreyAuction`. + h_auction: HashCode; + + // The amount that this bidder commits to for this auction + // + // This amount can be larger than the contribution of a single coin. + // The bidder can increase funding of this auction policy by using + // sufficiently many coins during the deposit operation (single or batch) + // with the same policy. + commitment: Amount; + + // Date until the auction must have been successfully executed and + // a valid transcript provided to the + // ``/extensions/policy_brandt_vickrey_auction``-endpoint of the + // exchange. + // + // [If the auction has not been executed by then] OR [has been executed + // before then, but this bidder did not win], the coin's value doesn't + // change and the owner can refresh the coin. + // + // If this bidder won the auction, the winning price/amount from the + // outcome will be substracted from the coin and transfered to the + // merchant's ``payout_uri`` from the deposit request (minus a potential + // auction fee). For any remaining value, the bidder can refresh the + // coin to retrieve change. + deadline: Timestamp; + } + + .. ts:def:: BrandtVickreyAuction + + // CAVEAT: THIS IS STILL WORK IN PROGRESS. + // This structure defines an auction of Brandt-Vickory kind. + // It is used for the `PolicyBrandtVickreyAuction`. + interface BrandtVickreyAuction { + // Start date of the auction + time_start: Timestamp; + + // Maximum duration per round. There are four rounds in an auction of + // Brandt-Vickrey kind. + time_round: RelativeTime; + + // This integer m refers to the (m+1)-type of the Brandt-Vickrey-auction. + // - Type 0 refers to an auction with one highest-price winner, + // - Type 1 refers to an auction with one winner, paying the second + // highest price, + // - Type 2 refers to an auction with two winners, paying + // the third-highest price, + // - etc. + auction_type: Integer; + + // The vector of prices for the Brandt-Vickrey auction. The values MUST + // be in strictly increasing order. + prices: Amount[]; + + // The type of outcome of the auction. + // In case the auction is declared public, each bidder can calculate the + // winning price. This field is not relevant for the replay of a + // transcript, as the transcript must be provided by the seller who sees + // the winner(s) and winning price of the auction. + outcome_public: boolean; + + // The public key of the seller. + pubkey: EddsaPublicKey; + + // The seller's account details as a full payto URI. + payto_uri: string; + } + + + .. ts:def:: PolicyEscrowedPayment + + // CAVEAT: THIS IS STILL WORK IN PROGRESS + // This policy is optional and might not be supported by the exchange. + // If it does, the exchange MUST show support for this policy in the + // ``extensions`` field in the response to ``/keys``. + interface PolicyEscrowedPayment { + type: "escrowed_payment"; + + // Public key of this trustor, the owner of the coins. + // + // To claim the deposit, the merchant must provide the valid signature + // of the ``h_contract_terms`` field from the deposit, signed by _this_ + // key, to the ``/extensions/policy_escrow``-endpoint of the exchange, + // after the date specified in ``not_before`` and before the date + // specified in ``not_after``. + trustor_pub: EddsaPublicKey; + + // Latest date by which the deposit must be claimed. If the deposit + // has not been claimed by that date, the deposited coins can be + // refreshed by the (still) owner. + deadline: Timestamp; + } + + The deposit operation succeeds if the coin is valid for making a deposit and + has enough residual value that has not already been deposited or melted. + + .. ts:def:: DepositSuccess + + interface DepositSuccess { + // Optional base URL of the exchange for looking up wire transfers + // associated with this transaction. If not given, + // the base URL is the same as the one used for this request. + // Can be used if the base URL for ``/transactions/`` differs from that + // for ``/coins/``, i.e. for load balancing. Clients SHOULD + // respect the ``transaction_base_url`` if provided. Any HTTP server + // belonging to an exchange MUST generate a 307 or 308 redirection + // to the correct base URL should a client uses the wrong base + // URL, or if the base URL has changed since the deposit. + transaction_base_url?: string; + + // Timestamp when the deposit was received by the exchange. + exchange_timestamp: Timestamp; + + // `Public EdDSA key of the exchange <sign-key-pub>` that was used to + // generate the signature. + // Should match one of the exchange's signing keys from ``/keys``. It is given + // explicitly as the client might otherwise be confused by clock skew as to + // which signing key was used. + exchange_pub: EddsaPublicKey; + + // Deposit confirmation signature from the exchange. + // The EdDSA signature of `TALER_DepositConfirmationPS` using a current + // `signing key of the exchange <sign-key-priv>` affirming the successful + // deposit and that the exchange will transfer the funds after the refund + // deadline, or as soon as possible if the refund deadline is zero. + exchange_sig: EddsaSignature; + } + + .. ts:def:: DepositDoubleSpendError + + interface DepositDoubleSpendError { + + // Must be TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS + // or TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY + code: Integer; + + // A string explaining that the user tried to + // double-spend. + hint: string; + + // EdDSA public key of a coin being double-spent. + coin_pub: EddsaPublicKey; + + // Hash of the public key of the denomination of the coin. + h_denom_pub: HashCode; + + } diff --git a/core/exchange/post-blinding-prepare.rst b/core/exchange/post-blinding-prepare.rst @@ -0,0 +1,95 @@ +.. http:post:: /blinding-prepare + + Obtain exchange-side input values in preparation for a + blinding step of multiple coins for certain denomination + cipher types, specifically at this point for Clause-Schnorr + blind signatures. + + **Request:** + + The request body must be a `BlindingPrepareRequest` object. + + **Response:** + + :http:statuscode:`200 OK`: + The request was successful, and the response is a + `BlindingPrepareResponse`. Note that repeating exactly the same request + will again yield the same response (assuming none of the denominations is + expired). + :http:statuscode:`404 Not found`: + A denomination key is not known to the exchange. + The response is a `DenominationUnknownMessage`. + :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 `DenominationGoneMessage`. + Clients must evaluate the error code provided to understand + which of the cases this is and handle it accordingly. + + **Details:** + + + .. ts:def:: BlindingPrepareRequest + + type BlindingPrepareRequest = BlindingPrepareRequestCS; + + + .. ts:def:: BlindingPrepareRequestCS + + interface BlindingPrepareRequestCS { + // Cipher type + cipher: "CS"; + + // The type of operation this blinding is for. + operation: "withdraw" | "melt"; + + // Master seed for the Clause-Schnorr R-value creation. + // MUST not have been used in any prior request of this type. + seed: BlindingMasterSeed; + + // Array of denominations and coin offsets for + // each of the fresh coins with a CS-cipher + // denomination. + // The coin_offset values MUST be strongly increasing. + nks: BlindingInputParameter[]; + + } + + .. ts:def:: BlindingInputParameter + + interface BlindingInputParameter { + + // Offset of this coin in the list of + // fresh coins. May not match the array offset + // as the fresh coins may include non-CS + // denominations as well. + coin_offset: Integer; + + // Hash of the public key of the denomination the + // request relates to. Must be a CS denomination type. + denom_pub_hash: HashCode; + } + + + + .. ts:def:: BlindingPrepareResponse + + type BlindingPrepareResponse = BlindingPrepareResponseCS; + + + .. ts:def:: BlindingPrepareResponseCS + + interface BlindingPrepareResponseCS { + cipher: "CS"; + + // Array of pairs of CS values, one pair per input + r_pubs: CSRPublicPair[]; + } + + + .. ts:def:: CSRPublicPair + + // Pair of points (of type `CSRPublic`) on the curve Curve25519, + // one of which is randomly selected in the Clause-Schnorr + // signature scheme. + type CSRPublicPair = [CSRPublic, CSRPublic]; diff --git a/core/exchange/post-coins-COIN_PUB-refund.rst b/core/exchange/post-coins-COIN_PUB-refund.rst @@ -0,0 +1,93 @@ +.. http:post:: /coins/$COIN_PUB/refund + + Undo deposit of the given coin, restoring its value. + + **Request:** + + The request body must be a `RefundRequest` object. + + **Response:** + + :http:statuscode:`200 OK`: + The operation succeeded, the exchange confirms that the coin can now be refreshed. The response will include a `RefundSuccess` object. + :http:statuscode:`403 Forbidden`: + Merchant 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 a matching deposit operation (coin, contract, transaction ID and merchant public key must all match). + This response comes with a standard `ErrorDetail` response. + :http:statuscode:`409 Conflict`: + The exchange has previously received a refund request for the same coin, merchant and contract, but specifying a different amount for the same refund transaction ID. The response will be a `RefundFailure` object. + :http:statuscode:`410 Gone`: + It is too late for a refund by the exchange, the money was already sent to the merchant. + This response comes with a standard `ErrorDetail` response. + :http:statuscode:`412 Precondition failed`: + The request transaction ID is identical to a previous refund request by the same + merchant for the same coin and contract, but the refund amount differs. (The + failed precondition is that the ``rtransaction_id`` is not unique.) + The response will be a `RefundFailure` object with the conflicting refund request. + + **Details:** + + .. ts:def:: RefundRequest + + interface RefundRequest { + + // Amount to be refunded, can be a fraction of the + // coin's total deposit value (including deposit fee); + // must be larger than the refund fee. + refund_amount: Amount; + + // SHA-512 hash of the contact of the merchant with the customer. + h_contract_terms: HashCode; + + // 64-bit transaction id of the refund transaction between merchant and customer. + rtransaction_id: Integer; + + // EdDSA public key of the merchant. + merchant_pub: EddsaPublicKey; + + // EdDSA signature of the merchant over a + // `TALER_RefundRequestPS` with purpose + // ``TALER_SIGNATURE_MERCHANT_REFUND`` + // affirming the refund. + merchant_sig: EddsaPublicKey; + + } + + .. ts:def:: RefundSuccess + + interface RefundSuccess { + + // The EdDSA :ref:`signature` (binary-only) with purpose + // ``TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND`` over + // a `TALER_RecoupRefreshConfirmationPS` + // using a current signing key of the + // exchange affirming the successful refund. + exchange_sig: EddsaSignature; + + // Public EdDSA key of the exchange that was used to generate the signature. + // Should match one of the exchange's signing keys from ``/keys``. It is given + // explicitly as the client might otherwise be confused by clock skew as to + // which signing key was used. + exchange_pub: EddsaPublicKey; + } + + .. ts:def:: RefundFailure + + interface RefundFailure { + + // Numeric error code unique to the condition, which can be either + // related to the deposit value being insufficient for the requested + // refund(s), or the requested refund conflicting due to refund + // transaction number re-use (with different amounts). + code: Integer; + + // Human-readable description of the error message. + hint: string; + + // Information about the conflicting refund request(s). + // This will not be the full history of the coin, but only + // the relevant subset of the transactions. + history: CoinSpendHistoryItem[]; + } diff --git a/core/exchange/post-kyc-start-ID.rst b/core/exchange/post-kyc-start-ID.rst @@ -0,0 +1,37 @@ +.. http:post:: /kyc-start/$ID + + The ``/kyc-start/$ID`` POST endpoint allows the SPA to set up a new external + KYC process. It will return the URL that the client must GET to begin the + KYC process. The SPA should probably open this URL in a new window or tab. + The ``$ID`` will be provided as part of the ``/kyc-info`` body. In + practice, ``$ID`` will encode the ``$ACCESS_TOKEN``, legitimization measure + serial ID (to disambiguate) and the index of the selected measure (but these + details should be irrelevant for the client). + + **Request:** + + Use empty JSON body for now. + + **Response:** + + :http:statuscode:`200 Ok`: + The KYC process was successfully initiated. The URL is in a + `KycProcessStartInformation` object. + :http:statuscode:`404 Not Found`: + The ``$ID`` is unknown to the exchange. + + **Details:** + + .. ts:def:: KycProcessStartInformation + + interface KycProcessStartInformation { + + // URL to open. + redirect_url: string; + } + + .. note:: + + As this endpoint is involved in every KYC check at the beginning, this + is also the place where we could integrate the payment process for the KYC fee + in the future (since protocol **vATTEST**). diff --git a/core/exchange/post-kyc-upload-ID.rst b/core/exchange/post-kyc-upload-ID.rst @@ -0,0 +1,29 @@ +.. http:post:: /kyc-upload/$ID + + The ``/kyc-upload/$ID`` POST endpoint allows the SPA to upload + client-provided evidence. The ``$ID`` will be provided as part of the + ``/kyc-info`` body. This is for checks of type ``FORM``. In practice, + ``$ID`` will encode the ``$ACCESS_TOKEN``, legitimization measure serial ID + (to disambiguate) and the index of the selected measure (but these details + should be irrelevant for the client). + + This endpoint was introduced in protocol **v20**. + + **Request:** + + JSON body with data depending on the form being submitted. + Details will thus completely depend on the form, but it + MUST include a form ID and be generally of type + `CustomerKycAttributes`. + + **Response:** + + :http:statuscode:`204 No Content`: + The information was successfully uploaded. The SPA should fetch + an updated ``/kyc-info/``. + :http:statuscode:`404 Not Found`: + The ``$ID`` is unknown to the exchange. + :http:statuscode:`409 Conflict`: + The upload conflicts with a previous upload. + :http:statuscode:`413 Request Entity Too Large`: + The body is too large. diff --git a/core/exchange/post-kyc-wallet.rst b/core/exchange/post-kyc-wallet.rst @@ -0,0 +1,78 @@ +.. http:post:: /kyc-wallet + + The ``/kyc-wallet`` POST endpoint allows a wallet to notify an exchange if + it will cross a balance threshold. Here, the ``balance`` specified should be + the threshold (from the ``wallet_balance_limit_without_kyc`` array) that the + wallet would cross, and *not* the *exact* balance of the wallet. The exchange + will respond with a wire target UUID. The wallet can then use this UUID to + being the KYC process at ``/kyc-check/``. The wallet must only proceed to + obtain funds exceeding the threshold after the KYC process has concluded. + While wallets could be "hacked" to bypass this measure (we cannot + cryptographically enforce this), such modifications are a terms of service + violation which may have legal consequences for the user. + + Setup KYC identification for a wallet. Returns the KYC UUID. + This endpoint is used by compliant Taler wallets when they + are about to hit the balance threshold and thus need to have + the customer provide their personal details to the exchange. + The wallet is identified by its long-lived reserve public key + (which is used for P2P payments, not for withdrawals). + + **Request:** + + The request body must be a `WalletKycRequest` object. + + **Response:** + + :http:statuscode:`200 Ok`: + The balance + is below the threshold that requires KYC, or this + wallet already satisfied the KYC check for the + given balance. The `WalletKycCheckResponse` body + give details about the next applicable KYC check + requirements. + :http:statuscode:`204 No Content`: + KYC is disabled at this exchange. + :http:statuscode:`403 Forbidden`: + The provided signature is invalid. + This response comes with a standard `ErrorDetail` response. + :http:statuscode:`451 Unavailable for Legal Reasons`: + The wallet must undergo a KYC check. A KYC ID was created. + The response will be a `LegitimizationNeededResponse` object. + + **Details:** + + .. ts:def:: WalletKycRequest + + interface WalletKycRequest { + + // Balance threshold (not necessarily exact balance) + // to be crossed by the wallet that (may) trigger + // additional KYC requirements. + balance: Amount; + + // EdDSA signature of the wallet affirming the + // request, must be of purpose + // ``TALER_SIGNATURE_WALLET_ACCOUNT_SETUP`` + reserve_sig: EddsaSignature; + + // long-term wallet reserve-account + // public key used to create the signature. + reserve_pub: EddsaPublicKey; + } + + .. ts:def:: WalletKycCheckResponse + + interface WalletKycCheckResponse { + + // Next balance limit above which a KYC check + // may be required. Optional, not given if no + // threshold exists (assume infinity). + next_threshold?: Amount; + + // When does the current set of AML/KYC rules + // expire and the wallet needs to check again + // for updated thresholds. + expiration_time: Timestamp; + + } diff --git a/core/exchange/post-management-aml-officers.rst b/core/exchange/post-management-aml-officers.rst @@ -0,0 +1,93 @@ +.. http:post:: /management/aml-officers + + Update settings for an AML Officer status. + + **Request:** + + The request must be an `AmlOfficerSetup` message. + + **Response:** + + :http:statuscode:`204 No content`: + The officer settings have been updated successfully. + :http:statuscode:`403 Forbidden`: + The signature is invalid. + :http:statuscode:`409 Conflict`: + The exchange has previously received a conflicting configuration message. + + **Details:** + + .. ts:def:: AmlOfficerSetup + + interface AmlOfficerSetup { + + // Public key of the AML officer + officer_pub: EddsaPublicKey; + + // Legal full name of the AML officer + officer_name: string; + + // Is the account active? + is_active: boolean; + + // Is the account read-only? + read_only: boolean; + + // Signature by the exchange master key over a + // `TALER_MasterAmlOfficerStatusPS`. + // Must have purpose ``TALER_SIGNATURE_MASTER_AML_KEY``. + master_sig: EddsaSignature; + + // When will the change take effect? + change_date: Timestamp; + + } + + + .. http:post:: /management/partners + + Enables a partner exchange for wad transfers. + + **Request:** + + The request must be an `ExchangePartnerSetupRequest` message. + + **Response:** + + :http:statuscode:`204 No content`: + The partner has been added successfully. + :http:statuscode:`403 Forbidden`: + The signature is invalid. + :http:statuscode:`409 Conflict`: + The exchange has previously received a conflicting configuration message. + + **Details:** + + .. ts:def:: ExchangePartnerSetupRequest + + interface ExchangePartnerSetupRequest { + + // Base URL of the partner exchange + partner_base_url: string; + + // Master (offline) public key of the partner exchange. + partner_pub: EddsaPublicKey; + + // How frequently will wad transfers be made + wad_frequency: RelativeTime; + + // Signature by the exchange master key over a + // `TALER_PartnerConfigurationPS`. + // Must have purpose ``TALER_SIGNATURE_MASTER_PARTNER_DETAILS``. + master_sig: EddsaSignature; + + // When will the partner relationship start (inclusive). + start_date: Timestamp; + + // When will the partner relationship end (exclusive). + end_date: Timestamp; + + // Wad fee to be charged (to customers). + wad_fee: Amount; + + } diff --git a/core/exchange/post-management-auditors-AUDITOR_PUB-disable.rst b/core/exchange/post-management-auditors-AUDITOR_PUB-disable.rst @@ -0,0 +1,44 @@ +.. http:post:: /management/auditors/$AUDITOR_PUB/disable + + This request will be used to disable the use of the given auditor. + We use POST instead of DELETE because the exchange will retain state + about the auditor (specifically the end date) to prevent replay + attacks abusing the `AuditorSetupMessage`. Also, DELETE would not + support a body, which is needed to provide the signature authorizing + the operation. + + **Request:** + + The request must be a `AuditorTeardownMessage`. + + **Response** + + :http:statuscode:`204 No content`: + The auditor has successfully disabled the auditor. The body is empty. + :http:statuscode:`403 Forbidden`: + The signature is invalid. + :http:statuscode:`404 Not found`: + The auditor is unknown to the exchange. + :http:statuscode:`409 Conflict`: + The exchange has a more recent request related to this auditor key (replay detected). + + **Details:** + + .. ts:def:: AuditorTeardownMessage + + interface AuditorTeardownMessage { + + // Signature by the exchange master key over a + // `TALER_MasterDelAuditorPS`. + // Must have purpose ``TALER_SIGNATURE_MASTER_AUDITOR_DEL``. + master_sig: EddsaSignature; + + // When does the auditor become inactive? + // Should be the time when the signature was created, + // using the (monotonic!) local time of the system + // with the offline master public key. Note that + // even if the time is in the future, the auditor will + // become inactive immediately! Used ONLY to detect replay attacks. + validity_end: Timestamp; + + } diff --git a/core/exchange/post-management-auditors.rst b/core/exchange/post-management-auditors.rst @@ -0,0 +1,46 @@ +.. http:post:: /management/auditors + + This request will be used to enable an auditor. + + **Request:** + + The request must be a `AuditorSetupMessage`. + + **Response:** + + :http:statuscode:`204 No content`: + The auditor was successfully enabled. + :http:statuscode:`403 Forbidden`: + The master signature is invalid. + :http:statuscode:`409 Conflict`: + The exchange has a more recent request related to this auditor key (replay detected). + + **Details:** + + .. ts:def:: AuditorSetupMessage + + interface AuditorSetupMessage { + + // Base URL of the auditor. + auditor_url: string; + + // Human-readable name of the auditor. + auditor_name: string; + + // The auditor's EdDSA signing public key. + auditor_pub: EddsaPublicKey; + + // Signature by the exchange master ke yover a + // `TALER_MasterAddAuditorPS`. + // Must have purpose ``TALER_SIGNATURE_MASTER_ADD_AUDITOR``. + master_sig: EddsaSignature; + + // When does the auditor become active? + // Should be the time when the signature was created, + // using the (monotonic!) local time of the system + // with the offline master public key. Note that + // even if the time is in the future, the auditor will + // become active immediately! Used ONLY to detect replay attacks. + validity_start: Timestamp; + + } diff --git a/core/exchange/post-management-denominations-H_DENOM_PUB-revoke.rst b/core/exchange/post-management-denominations-H_DENOM_PUB-revoke.rst @@ -0,0 +1,29 @@ +.. http:post:: /management/denominations/$H_DENOM_PUB/revoke + + Revoke denomination key, preventing further use by the exchange. + Only to be used by the exchange's offline key management team. Not useful + for anyone else. + + **Request:** + + The request body must be a `DenomRevocationSignature` object. + + **Response:** + + :http:statuscode:`204 No content`: + The request was successfully processed. + :http:statuscode:`403 Forbidden`: + The provided signature is invalid. + + **Details:** + + .. ts:def:: DenomRevocationSignature + + interface DenomRevocationSignature { + + // Signature by the exchange master key over a + // `TALER_MasterDenominationKeyRevocationPS`. + // Must have purpose ``TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED``. + master_sig: EddsaSignature; + + } diff --git a/core/exchange/post-management-drain.rst b/core/exchange/post-management-drain.rst @@ -0,0 +1,45 @@ +.. http:post:: /management/drain + + This request is used to drain profits from the + exchange's escrow account to another regular + bank account of the exchange. The actual drain + requires running the ``taler-exchange-drain`` tool. + + **Request:** + + The request must be a `DrainProfitsMessage`. + + **Response:** + + :http:statuscode:`204 No content`: + The profit drain was scheduled. + :http:statuscode:`403 Forbidden`: + The master signature is invalid. + + **Details:** + + .. ts:def:: DrainProfitsMessage + + interface DrainProfitsMessage { + + // Configuration section of the account to debit. + debit_account_section: string; + + // Full payto URI of the account to credit. + credit_payto_uri: string; + + // Wire transfer identifier to use. + wtid: Base32; + + // Signature by the exchange master key over a + // `TALER_MasterDrainProfitPS`. + // Must have purpose ``TALER_SIGNATURE_MASTER_DRAIN_PROFITS``. + master_sig: EddsaSignature; + + // When was the message created. + date: Timestamp; + + // Amount to be drained. + amount: Amount; + + } diff --git a/core/exchange/post-management-global-fees.rst b/core/exchange/post-management-global-fees.rst @@ -0,0 +1,16 @@ +.. http:post:: /management/global-fees + + Provides global fee configuration for a timeframe. + + **Request:** + + The request must be a `GlobalFees` message. + + **Response:** + + :http:statuscode:`204 No content`: + The configuration update has been processed successfully. The body is empty. + :http:statuscode:`403 Forbidden`: + The signature is invalid. + :http:statuscode:`409 Conflict`: + The exchange has previously received a conflicting configuration message. diff --git a/core/exchange/post-management-keys.rst b/core/exchange/post-management-keys.rst @@ -0,0 +1,58 @@ +.. http:post:: /management/keys + + Provide master signatures for future public keys to be used by the exchange. + Only to be used by the exchange's offline key management team. Not useful + for anyone else. + + **Request:** + + The request body must be a `MasterSignatures` object. + + **Response:** + + :http:statuscode:`204 No content`: + The request was successfully processed. + :http:statuscode:`403 Forbidden`: + A provided signature is invalid. + :http:statuscode:`404 Not found`: + One of the keys for which a signature was provided is unknown to the exchange. + + **Details:** + + .. ts:def:: MasterSignatures + + interface MasterSignatures { + + // Provided master signatures for future denomination keys. + denom_sigs: DenomSignature[]; + + // Provided master signatures for future online signing keys. + signkey_sigs: SignKeySignature[]; + + } + + .. ts:def:: DenomSignature + + interface DenomSignature { + + // Hash of the public key of the denomination. + h_denom_pub: HashCode; + + // Signature over `TALER_DenominationKeyValidityPS`. + // Must have purpose ``TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY`` + master_sig: EddsaSignature; + + } + + .. ts:def:: SignKeySignature + + interface SignKeySignature { + // The actual exchange's EdDSA signing public key. + key: EddsaPublicKey; + + // Signature by the exchange master key over + // `TALER_ExchangeSigningKeyValidityPS`. + // Must have purpose ``TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY``. + master_sig: EddsaSignature; + + } diff --git a/core/exchange/post-management-signkeys-EXCHANGE_PUB-revoke.rst b/core/exchange/post-management-signkeys-EXCHANGE_PUB-revoke.rst @@ -0,0 +1,29 @@ +.. http:post:: /management/signkeys/$EXCHANGE_PUB/revoke + + Revoke exchange online signing key, preventing further use by the exchange. + Only to be used by the exchange's offline key management team. Not useful + for anyone else. + + **Request:** + + The request body must be a `SignkeyRevocationSignature` object. + + **Response:** + + :http:statuscode:`204 No content`: + The request was successfully processed. + :http:statuscode:`403 Forbidden`: + The provided signature is invalid. + + **Details:** + + .. ts:def:: SignkeyRevocationSignature + + interface SignkeyRevocationSignature { + + // Signature by the exchange master key over a + // `TALER_MasterSigningKeyRevocationPS`. + // Must have purpose ``TALER_SIGNATURE_MASTER_SIGN_KEY_REVOKED``. + master_sig: EddsaSignature; + + } diff --git a/core/exchange/post-management-wire-disable.rst b/core/exchange/post-management-wire-disable.rst @@ -0,0 +1,46 @@ +.. http:post:: /management/wire/disable + + This request will be used to disable the use of the given wire method. + We use POST instead of DELETE because the exchange will retain state + about the wire method (specifically the end date) to prevent replay + attacks abusing the `WireSetupMessage`. Also, DELETE would not + support a body, which is needed to provide the signature authorizing + the operation. + + **Request:** + + The request must be a `WireTeardownMessage`. + + **Response:** + + :http:statuscode:`204 No content`: + The auditor has successfully disabled the wire method. The body is empty. + :http:statuscode:`403 Forbidden`: + The signature is invalid. + :http:statuscode:`404 Not found`: + The wire method is unknown to the exchange. + :http:statuscode:`409 Conflict`: + The exchange has a more recent request related to this wire method (replay detected). + + **Details:** + + .. ts:def:: WireTeardownMessage + + interface WireTeardownMessage { + + // Full ``payto://`` URL identifying the account and wire method + payto_uri: string; + + // Signature using the exchange's offline key over a + // `TALER_MasterDelWirePS`. + // with purpose ``TALER_SIGNATURE_MASTER_WIRE_DEL``. + master_sig_del: EddsaSignature; + + // Should be the time when the signature was created, + // using the (monotonic!) local time of the system + // with the offline master public key. Note that + // even if the time is in the future, the wire method will + // become inactive immediately! Used ONLY to detect replay attacks. + validity_end: Timestamp; + + } diff --git a/core/exchange/post-management-wire-fee.rst b/core/exchange/post-management-wire-fee.rst @@ -0,0 +1,43 @@ +.. http:post:: /management/wire-fee + + This request is used to configure wire fees. + + **Request:** + + The request must be a `WireFeeSetupMessage`. + + **Response:** + + :http:statuscode:`204 No content`: + The wire fee was successfully configured. + :http:statuscode:`403 Forbidden`: + The master signature is invalid. + :http:statuscode:`409 Conflict`: + The exchange has a conflicting wire fee already set up. + + **Details:** + + .. ts:def:: WireFeeSetupMessage + + interface WireFeeSetupMessage { + + // Wire method the fee applies to. + wire_method: string; + + // Signature using the exchange's offline key + // with purpose ``TALER_SIGNATURE_MASTER_WIRE_FEES``. + master_sig_wire: EddsaSignature; + + // When does the wire fee validity period start? + fee_start: Timestamp; + + // When does the wire fee validity period end (exclusive). + fee_end: Timestamp; + + // Closing fee to charge during that time period for this wire method. + closing_fee: Amount; + + // Wire fee to charge during that time period for this wire method. + wire_fee: Amount; + + } diff --git a/core/exchange/post-management-wire.rst b/core/exchange/post-management-wire.rst @@ -0,0 +1,55 @@ +.. http:post:: /management/wire + + This request will be used to enable a wire method (exchange bank account). + + **Request:** + + The request must be a `WireSetupMessage`. + + **Response:** + + :http:statuscode:`204 No content`: + The wire method was successfully enabled. + :http:statuscode:`403 Forbidden`: + The master signature is invalid. + :http:statuscode:`409 Conflict`: + The exchange has a more recent request related to this wire method (replay detected). + + **Details:** + + .. ts:def:: WireSetupMessage + + interface WireSetupMessage { + + // Full ``payto://`` URL identifying the account and wire method + payto_uri: string; + + // Signature using the exchange's offline key + // over a `TALER_MasterWireDetailsPS` + // with purpose ``TALER_SIGNATURE_MASTER_WIRE_DETAILS``. + master_sig_wire: EddsaSignature; + + // Signature using the exchange's offline key over a + // `TALER_MasterAddWirePS` + // with purpose ``TALER_SIGNATURE_MASTER_WIRE_ADD``. + master_sig_add: EddsaSignature; + + // When does the wire method become active? + // Should be the time when the signature was created, + // using the (monotonic!) local time of the system + // with the offline master public key. Note that + // even if the time is in the future, the wire method will + // become active immediately! Used ONLY to detect replay attacks. + validity_start: Timestamp; + + // Display label wallets should use to show this + // bank account. + // @since protocol **v19**. + bank_label?: string; + + // *Signed* integer with the display priority for + // this bank account. + // @since protocol **v19**. + priority?: Integer; + + } diff --git a/core/exchange/post-melt.rst b/core/exchange/post-melt.rst @@ -0,0 +1,185 @@ +.. http:post:: /melt + + "Melts" a coin. Invalidates the coins and prepares for exchanging of fresh + coins. Taler uses a global parameter ``kappa`` for the cut-and-choose + component of the protocol, for which this request is the commitment. Thus, + various arguments are given ``kappa``-times in this step. At present ``kappa`` + is always 3. + + The base URL for ``/melt/``-requests may differ from the main base URL of the + exchange. The exchange MUST return a 307 or 308 redirection to the correct + base URL if this is the case. + + This endpoint was introduced in this form in protocol **v32**. + + :http:statuscode:`200 OK`: + The request was successful. The response body is `MeltResponse` in this case. + :http:statuscode:`403 Forbidden`: + One of the signatures is invalid. + :http:statuscode:`404 Not found`: + The exchange does not recognize the denomination key as belonging to the exchange, + or it has expired. + If the denomination key is unknown, the response will be + a `DenominationUnknownMessage`. + :http:statuscode:`409 Conflict`: + The operation is not allowed as the 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_GENERIC_INSUFFICIENT_FUNDS`` or + ``TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY``). + The response is `MeltForbiddenResponse` in both cases. + :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 + `DenominationGoneMessage`. Clients must evaluate + the error code provided to understand which of the + cases this is and handle it accordingly. + + **Details:** + + .. ts:def:: MeltRequest + + interface MeltRequest { + // The old coin's public key + old_coin_pub: CoinPublicKey; + + // Hash of the denomination public key of the old coin, + // to determine total coin value. + old_denom_pub_h: HashCode; + + // The hash of the age-commitment for the old coin. Only present + // if the denomination has support for age restriction. + old_age_commitment_h?: AgeCommitmentHash; + + // Signature over the old `coin public key <eddsa-coin-pub>` by the denomination. + old_denom_sig: DenominationSignature; + + // Amount of the value of the old coin that should be melted as part of + // this refresh operation, including melting fee. I.e.: + // melting fee of the old coin + // + sum over all values of fresh coins + // + sum over all withdraw fees for the fresh coins + value_with_fee: Amount; + + // @since v27 + // @deprecated **v32** + // Seed from which the nonces for the n*κ coin candidates are derived from. + // + // @since **v32** + // The ``refresh_seed`` is an opaque value to the exchange. + // It is provided by the client and is verified with the ``coin_sig`` below. + // Its purpose is to ensure that the honest owner of the old coin + // can replay a /melt request from data in the coin history, + // provided by the exchange and including this value, in case a wallet + // was restored into a state prior to the refresh operation. + // + // The honest owner of the old coin SHOULD use this value + // and the old coin's private key to derive kappa many + // batch seeds (one for each cut-and-choose candidate) + // like this: + // + // ``bs[] = HKDF(kappa*sizeof(HashCode),`` + // ``"refresh-batch-seeds",`` + // ``old_coin_priv,`` + // ``refresh_seed)`` + // + // These batch seeds (however constructed) are relevant in the + // subsequent reveal step of the cut-and-chose. Each of the + // revealed seeds is expanded to a batch of ``n`` transfer private keys + // via HKDF: + // + // ``tp[k][] = HKDF(n*sizeof(HashCode),`` + // ``"refresh-transfer-private-keys",`` + // ``bs[k])`` + // + // An individual coin's transfer private key at kappa-index k and + // coin index i in the batch is then ``tp[k][i]``. The corresponding + // transfer _public_ keys are given in the field ``transfer_pubs``. + refresh_seed: HashCode; + + // Master seed for the Clause-Schnorr R-value + // creation. Must match the /blinding-prepare request. + // Must not have been used in any prior melt request. + // Must be present if one of the fresh coin's + // denominations is of type Clause-Schnorr. + blinding_seed?: BlindingMasterSeed; + + // Array of ``n`` new hash codes of denomination public keys + // for the new coins to order. + denoms_h: HashCode[]; + + // ``kappa`` arrays of ``n`` entries for blinded coin candidates, + // each matching the respective entries in ``denoms_h``. + coin_evs: CoinEnvelope[kappa][]; + + // @since **v32** + // ``kappa`` arrays of ``n`` entries of transfer public keys each. + // These are ephemeral ECDHE keys that allow the owner of a coin + // to (re-)obtain the derived coins from a refresh operation, f.e. should + // the wallet state be restored from a backup, prior to the refresh operation. + transfer_pubs: EddsaPublicKey[kappa][]; + + // Signature by the `coin <coin-priv>` over `TALER_RefreshMeltCoinAffirmationPS`. + confirm_sig: EddsaSignature; + + } + + For details about the HKDF used to derive the new coin private keys and + the blinding factors from ECDHE between the transfer public keys and + the private key of the melted coin, please refer to the + implementation in ``libtalerutil``. + + .. ts:def:: MeltResponse + + interface MeltResponse { + // Which of the ``kappa`` indices does the client not have to reveal + // by calling the ``/reveal-melt`` endpoint. + noreveal_index: Integer; + + // Signature of `TALER_RefreshMeltConfirmationPS` whereby the exchange + // affirms the successful melt and confirming the ``noreveal_index``. + exchange_sig: EddsaSignature; + + // `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; + + // Base URL to use for operations on the refresh context + // (so the reveal operation). If not given, + // the base URL is the same as the one used for this request. + // Can be used if the base URL for ``/reveal-melt/`` differs from that + // for ``/melt/``, i.e. for load balancing. Clients SHOULD + // respect the reveal_base_url if provided. Any HTTP server + // belonging to an exchange MUST generate a 307 or 308 redirection + // to the correct base URL should a client uses the wrong base + // URL, or if the base URL has changed since the melt. + // + // When melting the same coin twice (technically allowed + // as the response might have been lost on the network), + // the exchange may return different values for the ``reveal_base_url``. + reveal_base_url?: string; + + } + + .. ts:def:: MeltForbiddenResponse + + interface MeltForbiddenResponse { + + // Must be TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS + // or TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY + code: Integer; + + // A string explaining that the user tried to + // double-spend. + hint: string; + + // EdDSA public key of a coin being double-spent. + coin_pub: EddsaPublicKey; + + // Hash of the public key of the denomination of the coin. + h_denom_pub: HashCode; + + } diff --git a/core/exchange/post-purses-PURSE_PUB-create.rst b/core/exchange/post-purses-PURSE_PUB-create.rst @@ -0,0 +1,205 @@ +.. http:post:: /purses/$PURSE_PUB/create + + Create a purse by depositing money into it. First step of a PUSH payment. + + **Request:** + + The request body must be a `PurseCreate` 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 `PurseCreateSuccessResponse` object. + :http:statuscode:`403 Forbidden`: + A coin, denomination or contract signature is invalid. + This response comes with a standard `ErrorDetail` response. + :http:statuscode:`404 Not Found`: + The denomination of one of the coins is unknown to the exchange. + :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, or because a purse with + the same public key but different meta data was created previously. + Which case it is + can be decided by looking at the error code + (``TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS`` or + ``TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY`` or + ``TALER_EC_EXCHANGE_PURSE_CREATE_CONFLICTING_META_DATA`` or + ``TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA`` or + ``TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA``). + The specific fields of the response depend on the error code + and include the signatures (and what was signed over) proving the + conflict. + :http:statuscode:`425 Too Early`: + This response type is used if the given purse expiration time + is too far in the future (at least from the perspective + of the exchange). Thus, retrying at a later time may + succeed. The client should look at the ``Date:`` header + of the response to see if a minor time difference is to + blame and possibly adjust the request accordingly. + (Note: this status code is not yet used.) + + + **Details:** + + .. ts:def:: PurseCreate + + interface PurseCreate { + + // Total value of the purse, excluding fees. + amount: Amount; + + // Minimum age required for all coins deposited into the purse. + min_age: Integer; + + // Optional encrypted contract, in case the buyer is + // proposing the contract and thus establishing the + // purse with the payment. + econtract?: EncryptedContract; + + // EdDSA public key used to approve merges of this purse. + merge_pub: EddsaPublicKey; + + // EdDSA signature of the purse over a + // `TALER_PurseRequestSignaturePS` + // of purpose ``TALER_SIGNATURE_WALLET_PURSE_CREATE`` + // confirming the key + // invariants associated with the purse. + // (amount, h_contract_terms, expiration). + purse_sig: EddsaSignature; + + // SHA-512 hash of the contact of the purse. + h_contract_terms: HashCode; + + // Array of coins being deposited into the purse. + // Maximum length is 128. + deposits: PurseDeposit[]; + + // 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; + + } + + .. ts:def:: EncryptedContract + + interface EncryptedContract { + + // Encrypted contract. + econtract: string; + + // Signature over the (encrypted) contract. + econtract_sig: EddsaSignature; + + // Ephemeral public key for the DH operation to decrypt the encrypted contract. + contract_pub: EddsaPublicKey; + + } + + .. ts:def:: PurseCreateSuccessResponse + + interface PurseCreateSuccessResponse { + + // Total amount deposited into the purse so far (without fees). + total_deposited: Amount; + + // Time at the exchange. + exchange_timestamp: Timestamp; + + // EdDSA signature of the exchange affirming the payment, + // of purpose ``TALER_SIGNATURE_PURSE_DEPOSIT_CONFIRMED`` + // over a `TALER_PurseDepositConfirmedSignaturePS`. + // Signs over the above and the purse public key and + // the hash of the contract terms. + exchange_sig: EddsaSignature; + + // public key used to create the signature. + exchange_pub: EddsaPublicKey; + + } + + .. ts:def:: PurseConflict + + // Union discriminated by the "code" field. + type PurseConflict = + | DepositDoubleSpendError + | PurseCreateConflict + | PurseDepositConflict + | PurseContractConflict; + + .. ts:def:: PurseCreateConflict + + interface PurseCreateConflict { + // Must be equal to TALER_EC_EXCHANGE_PURSE_CREATE_CONFLICTING_META_DATA + code: Integer; + + // Total amount to be merged into the reserve. + // (excludes fees). + amount: Amount; + + // Minimum age required for all coins deposited into the purse. + min_age: Integer; + + // 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 over + // `TALER_PurseMergeSignaturePS` of + // purpose ``TALER_SIGNATURE_WALLET_PURSE_MERGE`` + // confirming that the + // above details hold for this purse. + purse_sig: EddsaSignature; + + // 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; + } + + .. ts:def:: PurseDepositConflict + + interface PurseDepositConflict { + // Must be equal to TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA + code: Integer; + + // Public key of the coin being deposited into the purse. + coin_pub: EddsaPublicKey; + + // Signature over `TALER_PurseDepositSignaturePS` + // of purpose ``TALER_SIGNATURE_WALLET_PURSE_DEPOSIT`` + // made by the customer with the + // `coin's private key <coin-priv>`. + coin_sig: EddsaSignature; + + // Target exchange URL for the purse. Not present for the + // same exchange. + partner_url?: string; + + // Amount to be contributed to the purse by this coin. + amount: Amount; + + } + + .. ts:def:: PurseContractConflict + + interface PurseContractConflict { + // Must be equal to TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA + code: Integer; + + // Hash of the encrypted contract. + h_econtract: HashCode; + + // Signature over the contract. + econtract_sig: EddsaSignature; + + // Ephemeral public key for the DH operation to decrypt the contract. + contract_pub: EddsaPublicKey; + + } diff --git a/core/exchange/post-purses-PURSE_PUB-deposit.rst b/core/exchange/post-purses-PURSE_PUB-deposit.rst @@ -0,0 +1,119 @@ +.. http:post:: /purses/$PURSE_PUB/deposit + + Deposit money into a purse. Used by the buyer for a PULL payment. + + **Request:** + + The request body must be a `PurseDeposits` 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 `PurseDepositSuccessResponse` object. + :http:statuscode:`403 Forbidden`: + A coin or denomination signature is invalid. + This response comes with a standard `ErrorDetail` response. + :http:statuscode:`404 Not found`: + The purse is unknown. + This response comes with a standard `ErrorDetail` response. + :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_GENERIC_INSUFFICIENT_FUNDS`` or + ``TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY`` or + ``TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA``). + This response comes with a standard `PurseConflict` response + (alas some cases are impossible). + :http:statuscode:`410 Gone`: + The purse has expired. + + + **Details:** + + .. ts:def:: PurseDeposits + + interface PurseDeposits { + + // Array of coins to deposit into the purse. + deposits: PurseDeposit[]; + } + + .. ts:def:: PurseDeposit + + interface PurseDeposit { + + // Amount to be deposited, can be a fraction of the + // coin's total value. + amount: 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: DenominationSignature; + + // Age commitment for the coin, if the denomination is age-restricted. + age_commitment?: AgeCommitment; + + // Attestation for the minimum age, if the denomination is age-restricted. + attest?: Attestation; + + // Signature over `TALER_PurseDepositSignaturePS` + // of purpose ``TALER_SIGNATURE_WALLET_PURSE_DEPOSIT`` + // made by the customer with the + // `coin's private key <coin-priv>`. + coin_sig: EddsaSignature; + + // Public key of the coin being deposited into the purse. + coin_pub: EddsaPublicKey; + + } + + .. ts:def:: PurseDepositSuccessResponse + + interface PurseDepositSuccessResponse { + + // Total amount paid into the purse. + total_deposited: Amount; + + // Total amount expected in the purse. + purse_value_after_fees: Amount; + + // Time at which the deposit came into effect. + exchange_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; + + // SHA-512 hash of the contact of the purse. + h_contract_terms: HashCode; + + // EdDSA signature of the exchange affirming the payment, + // of purpose ``TALER_SIGNATURE_PURSE_DEPOSIT_CONFIRMED`` + // over a `TALER_PurseDepositConfirmedSignaturePS`. + // Signs over the above and the purse public key and + // the hash of the contract terms. + exchange_sig: EddsaSignature; + + // public key used to create the signature. + exchange_pub: EddsaPublicKey; + + } + + .. ts:def:: AgeCommitment + + // AgeCommitment is an array of public keys, one for each age group of the + // age-restricted denomination. + type AgeCommitment = Edx25519PublicKey[]; + + .. ts:def:: Attestation + + // An attestation for a minimum age is an Edx25519 signature of the age + // with purpose ``TALER_SIGNATURE_WALLET_AGE_ATTESTATION``. + type Attestation = string; diff --git a/core/exchange/post-purses-PURSE_PUB-merge.rst b/core/exchange/post-purses-PURSE_PUB-merge.rst @@ -0,0 +1,103 @@ +.. http:post:: /purses/$PURSE_PUB/merge + + Merge purse with account, adding the value of the purse into + the account. Endpoint to be used by the receiver of a PUSH payment. + + **Request:** + + The request body must be a `MergeRequest` object. + + **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:`402 Payment Required`: + The purse is not yet full and more money needs to be deposited + before the merge can be made. + :http:statuscode:`403 Forbidden`: + The signature of the merge request or the reserve was invalid. + This response comes with a standard `ErrorDetail` response. + :http:statuscode:`404 Not found`: + The merge operation failed as we could not find the purse + or the partner exchange. + This response comes with a standard `ErrorDetail` response. + :http:statuscode:`409 Conflict`: + The purse was already merged into a different reserve. + The response will include a `MergeConflict` object. + :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:`451 Unavailable For Legal Reasons`: + This account has not yet passed the KYC checks. + The client must pass KYC checks before proceeding with the merge. + The response will be an `LegitimizationNeededResponse` object. + + **Details:** + + .. ts:def:: MergeRequest + + interface MergeRequest { + + // Normalized payto URI of the account the purse is to be merged into. + // Must be of the form: 'payto://taler/$EXCHANGE_URL/$RESERVE_PUB'. + payto_uri: string; + + // EdDSA signature of the account/reserve affirming the merge + // over a `TALER_AccountMergeSignaturePS`. + // Must be of purpose ``TALER_SIGNATURE_ACCOUNT_MERGE`` + reserve_sig: EddsaSignature; + + // EdDSA signature of the merge private key affirming the merge + // over a `TALER_PurseMergeSignaturePS`. + // Must be of purpose ``TALER_SIGNATURE_PURSE_MERGE``. + merge_sig: EddsaSignature; + + // Client-side timestamp of when the merge request was made. + merge_timestamp: Timestamp; + + } + + .. ts:def:: MergeSuccess + + interface MergeSuccess { + + // Amount merged (excluding deposit fees). + merge_amount: Amount; + + // Time at which the merge came into effect. + // Maximum of the "payment_timestamp" and the + // "merge_timestamp". + exchange_timestamp: Timestamp; + + // EdDSA signature of the exchange affirming the merge of + // purpose ``TALER_SIGNATURE_PURSE_MERGE_SUCCESS`` + // over `TALER_PurseMergeSuccessSignaturePS`. + // Signs over the above and the account public key. + exchange_sig: EddsaSignature; + + // public key used to create the signature. + exchange_pub: EddsaPublicKey; + + } + + .. ts:def:: MergeConflict + + interface MergeConflict { + + // Client-side timestamp of when the merge request was made. + merge_timestamp: Timestamp; + + // EdDSA signature of the purse private key affirming the merge + // over a `TALER_PurseMergeSignaturePS`. + // Must be of purpose ``TALER_SIGNATURE_PURSE_MERGE``. + merge_sig: EddsaSignature; + + // Base URL of the exchange receiving the payment, only present + // if the exchange hosting the reserve is not this exchange. + partner_url?: string; + + // Public key of the reserve that the purse was merged into. + reserve_pub: EddsaPublicKey; + } diff --git a/core/exchange/post-recoup-refresh.rst b/core/exchange/post-recoup-refresh.rst @@ -0,0 +1,80 @@ +.. http:post:: /recoup-refresh + + Demand that a batch of coins be refunded to the original coin, + from which the coins were originally refreshed. + The coins must have been originated from the same call to refresh, and be + a subset of that original batch. + The remaining amount on the coin will be credited to the original coin + that the coins were refreshed from, in the same refresh request. + + The base URL for coin related requests may differ from the main base URL of the + exchange. The exchange MUST return a 307 or 308 redirection to the correct + base URL if this is the case. + + Note that the original refresh fees will **not** be recouped. + + .. note:: This endpoint still Work-in-Progress. It will be implemented in **vRECOUP**, sometime after **v32**. + + **Request:** + + The request body must be a `RecoupRefreshRequest` object. + + **Response:** + + :http:statuscode:`200 OK`: + The request was successful, and the response is a `RecoupRefreshConfirmation`. + 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 coin's signature is invalid. + This response comes with a standard `ErrorDetail` response. + :http:statuscode:`404 Not found`: + The denomination key is unknown, or the blinded + coin is not known to have been withdrawn. + If the denomination key is unknown, the response will be + a `DenominationUnknownMessage`. + :http:statuscode:`409 Conflict`: + The operation is not allowed as the 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 + (usually ``TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_BALANCE``). + The response is a `DepositDoubleSpendError`. + :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 not yet revoked. The response is a + `DenominationGoneMessage`. Clients must evaluate + the error code provided to understand which of the + cases this is and handle it accordingly. + + **Details:** + + .. ts:def:: RecoupRefreshRequest + + interface RecoupRefreshRequest { + // Public key of the original coin that will receive the recoup. + // MUST be the same as the one from the original refresh request. + old_coin_pub: EddsaPublicKey; + + // The details about the coins: + // An array of either + // a) the hash code of a blinded coin envelope (not to be recouped) + // b) the disclosed coin details, in order to recoup it. + // From these, the hash of all coin envelopes + // from the original refresh can be reconstructed. + coin_data: RecoupCoinData[]; + } + + + .. ts:def:: RecoupRefreshConfirmation + + interface RecoupRefreshConfirmation { + // Public key of the old coin that will receive the recoup. + old_coin_pub: EddsaPublicKey; + + // The new balance of the old coin, after it has absorved + // the residual values of the coins from the request. + balance: Amount; + } diff --git a/core/exchange/post-recoup-withdraw.rst b/core/exchange/post-recoup-withdraw.rst @@ -0,0 +1,142 @@ +.. http:post:: /recoup-withdraw + + Demand that a batch of coins be refunded to the reserve, + from which the coins were originally withdrawn. + The coins must have been originated from the same call to withdraw, and be + a subset of that original batch. + The remaining amount on the coin will be credited to the reserve + that the coins were withdrawn from, in the same withdraw request. + + Note that the original withdrawal fees will **not** be recouped. + + .. note:: This endpoint still Work-in-Progress. It will be implemented in **vRECOUP**, sometime after **v32**. + + **Request:** + + The request body must be a `RecoupWithdrawRequest` object. + + It provides sufficient information to + a) identify the originating withdraw request + b) proof that the coins to be recouped were part of that withdraw request + c) proof ownership of all coins requested to be recouped. + + **Response:** + + :http:statuscode:`200 OK`: + The request was successful, and the response is a `ReserveSummary`. + :http:statuscode:`403 Forbidden`: + A coin's signature is invalid. + This response comes with a standard `ErrorDetail` response. + :http:statuscode:`404 Not found`: + A denomination key is unknown, + the withdraw commitment is unknown + or a blinded coin is not known to have been withdrawn. + If a denomination key is unknown, the response will be + a `DenominationUnknownMessage`. + :http:statuscode:`409 Conflict`: + The operation is not allowed + as a coin has insufficient residual value, + or because the same public key of a coin + has been previously used with a different denomination. + Which case it is can be decided by looking at the error code + (usually ``TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS``). + The response is a `DepositDoubleSpendError`. + :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 not yet revoked. + The response is a `DenominationGoneMessage`. + Clients must evaluate the error code provided + to understand which of the cases this is and handle it accordingly. + + **Details:** + + .. ts:def:: RecoupWithdrawRequest + + interface RecoupWithdrawRequest { + // Public key of the reserve that will receive the recoup. + // MUST be the same as the one from the original withdraw. + reserve_pub: EddsaPublicKey; + + // The details about the coins: + // An array of either + // a) the hash code of a blinded coin envelope (not to be recouped) + // b) the disclosed coin details, in order to recoup it. + // From these, the hash of all coin envelopes + // from the original withdraw can be reconstructed. + coin_data: RecoupCoinData[]; + } + + .. ts:def:: RecoupCoinData + + // This is either + // a) the hash code of a blinded coin envelope (not to be recouped) + // b) the disclosed coin details, in order to recoup it. + type RecoupCoinData = + | NonRecoupedCoin + | RecoupDisclosedCoinDetails; + + .. ts:def:: NonRecoupedCoin + + interface NonRecoupedCoin { + type: "non_recouped_coin"; + + // This is the SHA512 hash code of a blinded coin envelope, + // including the corresponding denomination's hash. + // It is the output of the TALER_coin_ev_hash function + // from libtalerutil. + coin_ev: BlindedCoinEnvelopeHash; + }; + + .. ts:def:: BlindedCoinEnvelopeHash + + // The hash value of a blinded coin envelope, + // as it its generated by the function TALER_coin_ev_hash + // in libtalerutil. + type BlindedCoinEnvelopeHash = HashCode; + + .. ts:def:: RecoupDisclosedCoinDetails + + // This object provides all necessary coin data + // in order to call TALER_denom_blind and retrieve + // a blinded coin planchet, from which we can + // calculate the blinded coin envelope hash. + // It also contains the denomination's signature + // for the (unblinded) coin's public key, + // and the coin's signature to authorize the recoup request. + interface RecoupDisclosedCoinDetails { + type: "recoup_coin_details"; + + // The coin's public key + coin_pub: CoinPublicKey; + + // The blinding secret for this coin + // that was used during withdraw + coin_blinding_key_secret: DenominationBlindingKeySecret; + + // The coin's commitment for age restriction, + // if the denomination had age restriction support. + age_commitment_h?: AgeCommitmentHash; + + // The blinding nonce that went into this coin's + // blinded envelope + cs_session_nonce?: HashCode; + + // In case of Clause-Schnorr denomination, + // the blinding values that were provided + // for this coin, by the exchange, as response + // to a call to /blinding-prepare. + cs_r_pubs?: CSRPublicPair; + + // Unblinded signature of the coins' public key, + // signed by the denomination key. + denom_pub_sig: DenominationSignature; + + // The denomination public key. + // This denomination MUST be eligible for recoup, + // i.e. being listed in the "recoup" section of /config. + denom_pub_h: HashCode; + + // Signature of `TALER_RecoupRequestPS`, + // created by this coin's private key. + coin_sig: EddsaSignature; + } diff --git a/core/exchange/post-reserves-RESERVE_PUB-close.rst b/core/exchange/post-reserves-RESERVE_PUB-close.rst @@ -0,0 +1,66 @@ +.. http:post:: /reserves/$RESERVE_PUB/close + + Force immediate closure of a reserve. Does not actually + delete the reserve or the KYC data, but merely forces + the reserve's current balance to be wired back to the + account where it originated from, or another account of + the user's choosing if they performed the required KYC + check and designated another target account. + + **Request:** + + The request body must be a `ReserveCloseRequest` object. + + **Response:** + + :http:statuscode:`200 OK`: + The exchange responds with a `ReserveCloseResponse` object. + :http:statuscode:`403 Forbidden`: + The *TALER_SIGNATURE_WALLET_RESERVE_CLOSE* signature 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. + :http:statuscode:`409 Conflict`: + No target account was given, and the exchange does not know an + origin account for this reserve. + :http:statuscode:`451 Unavailable For Legal Reasons`: + This account has not yet passed the KYC checks, hence wiring + funds to a non-origin account is not allowed. + The client must pass KYC checks before the reserve can be opened. + The response will be an `LegitimizationNeededResponse` object. + + **Details:** + + .. ts:def:: ReserveCloseRequest + + interface ReserveCloseRequest { + // Signature of purpose + // ``TALER_SIGNATURE_WALLET_RESERVE_CLOSE`` over + // a `TALER_ReserveCloseRequestSignaturePS`. + 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 (with a status code of 400). + request_timestamp: Timestamp; + + // Full payto://-URI of the account the reserve balance is to be + // wired to. Must be of the form: 'payto://$METHOD' for a + // wire method supported by this exchange (if the + // method is not supported, this is a bad request (400)). + // If not given, the reserve's origin account + // will be used. If no origin account is known for the + // reserve and not given, this is a conflict (409). + payto_uri?: string; + + } + + .. ts:def:: ReserveCloseResponse + + interface ReserveCloseResponse { + + // Actual amount that will be wired (excludes closing fee). + wire_amount: Amount; + + } diff --git a/core/exchange/post-reserves-RESERVE_PUB-open.rst b/core/exchange/post-reserves-RESERVE_PUB-open.rst @@ -0,0 +1,128 @@ +.. http:post:: /reserves/$RESERVE_PUB/open + + Request keeping a reserve open for invoicing. + + **Request:** + + The request body must be a `ReserveOpenRequest` object. + + **Response:** + + :http:statuscode:`200 OK`: + The exchange responds with a `ReserveOpenResponse` object. + :http:statuscode:`402 Payment Required`: + The exchange responds with a `ReserveOpenFailure` object when + the payment offered is insufficient for the requested operation. + :http:statuscode:`403 Forbidden`: + The *TALER_SIGNATURE_WALLET_RESERVE_OPEN* signature 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. + :http:statuscode:`409 Conflict`: + The balance of the reserve or of a coin was insufficient. + Which case it is can be decided by looking at the error code + (``TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS`` or + ``TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY`` or + ``TALER_EC_EXCHANGE_OPEN_INSUFFICIENT_FUNDS``). + The specific fields of the response depend on the error code + and include the signatures (and what was signed over) proving the + conflict. + The response is `WithdrawError` object or a `DepositDoubleSpendError` + depending on the error type. + :http:statuscode:`451 Unavailable For Legal Reasons`: + This account has not yet passed the KYC checks. + The client must pass KYC checks before the reserve can be opened. + The response will be an `LegitimizationNeededResponse` object. + + **Details:** + + .. ts:def:: ReserveOpenRequest + + interface ReserveOpenRequest { + // Signature of purpose + // ``TALER_SIGNATURE_WALLET_RESERVE_OPEN`` over + // a `TALER_ReserveOpenPS`. + reserve_sig: EddsaSignature; + + // Array of payments made towards the cost of the + // operation. + payments: OpenPaymentDetail[]; + + // Amount to be paid from the reserve for this + // operation. + reserve_payment: Amount; + + // 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 (with a status code of 400). + request_timestamp: Timestamp; + + // Desired new expiration time for the reserve. + // If the reserve would expire before this time, + // the exchange will charge account fees (and + // possibly KYC fees) until the expiration time + // exceeds this timestamp. Note that the exchange + // will refuse requests (with a status code of 400) + // if the time is so far in the future that the + // fees are not yet known (see /keys). + reserve_expiration: Timestamp; + + // Desired open purse limit. Can be used to pay the + // annual account fee more than once to get a larger + // purse limit. + purse_limit: Integer; + + } + + .. ts:def:: ReserveOpenResponse + + interface ReserveOpenResponse { + // Transaction cost for extending the expiration time. + // Excludes KYC fees. + open_cost: Amount; + + // Current expiration time for the reserve. + reserve_expiration: Timestamp; + } + + .. ts:def:: ReserveOpenFailure + + interface ReserveOpenFailure { + // Transaction cost that should have been paid + // to extending the reserve as requested. + // Excludes KYC fees. + open_cost: Amount; + + // Remaining expiration time for the reserve. + reserve_expiration: Timestamp; + } + + .. ts:def:: OpenPaymentDetail + + interface OpenPaymentDetail { + + // Contribution of this coin to the overall amount. + // Can be a fraciton of the coin's total value. + amount: 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: DenominationSignature; + + // Age commitment for the coin, if the denomination is age-restricted. + age_commitment?: AgeCommitment; + + // Signature over `TALER_ReserveOpenDepositSignaturePS` + // of purpose ``TALER_SIGNATURE_WALLET_RESERVE_OPEN_DEPOSIT`` + // made by the customer with the + // `coin's private key <coin-priv>`. + coin_sig: EddsaSignature; + + // Public key of the coin being used to pay for + // opening the reserve. + coin_pub: EddsaPublicKey; + + } diff --git a/core/exchange/post-reserves-RESERVE_PUB-purse.rst b/core/exchange/post-reserves-RESERVE_PUB-purse.rst @@ -0,0 +1,100 @@ +.. http:post:: /reserves/$RESERVE_PUB/purse + + Create purse for an account. First step of a PULL payment. + + **Request:** + + The request body must be a `ReservePurseRequest` object. + + **Response:** + + :http:statuscode:`200 OK`: + The operation succeeded, the exchange confirms that the + purse was allocated. + The response will include a `PurseCreateSuccessResponse` object. + :http:statuscode:`402 Payment Required`: + The account needs to contain more funding to create more purses. + This response comes with a standard `ErrorDetail` response. + :http:statuscode:`403 Forbidden`: + Account or contract signature is invalid. + This response comes with a standard `ErrorDetail` response. + :http:statuscode:`404 Not found`: + The purse creation operation failed as we could not find the reserve. + This response comes with a standard `ErrorDetail` response. + :http:statuscode:`409 Conflict`: + The purse creation failed because a purse with + the same public key but different meta data was + created previously. Which specific conflict it is + can be decided by looking at the error code + (``TALER_EC_EXCHANGE_PURSE_CREATE_CONFLICTING_META_DATA`` or + ``TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA`` or + ``TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA``). + The specific fields of the response depend on the error code + and include the signatures (and what was signed over) proving the + conflict. + The response will be a `PurseConflict` response + (but not a `DepositDoubleSpendError`). + :http:statuscode:`451 Unavailable For Legal Reasons`: + This account has not yet passed the KYC checks. + The client must pass KYC checks before proceeding with the merge. + The response will be an `LegitimizationNeededResponse` object. + + **Details:** + + .. ts:def:: ReservePurseRequest + + interface ReservePurseRequest { + + // Minimum amount that must be credited to the reserve, that is + // the total value of the purse minus the deposit fees. + // If the deposit fees are lower, the contribution to the + // reserve can be higher! + purse_value: Amount; + + // Minimum age required for all coins deposited into the purse. + min_age: Integer; + + // Purse fee the reserve owner is willing to pay + // for the purse creation. Optional, if not present + // the purse is to be created from the purse quota + // of the reserve. + purse_fee: Amount; + + // Optional encrypted contract, in case the buyer is + // proposing the contract and thus establishing the + // purse with the payment. + econtract?: EncryptedContract; + + // EdDSA public key used to approve merges of this purse. + merge_pub: EddsaPublicKey; + + // EdDSA signature of the purse private key affirming the merge + // over a `TALER_PurseMergeSignaturePS`. + // Must be of purpose ``TALER_SIGNATURE_PURSE_MERGE``. + merge_sig: EddsaSignature; + + // EdDSA signature of the account/reserve affirming the merge. + // Must be of purpose ``TALER_SIGNATURE_WALLET_ACCOUNT_MERGE`` + reserve_sig: EddsaSignature; + + // Purse public key. + purse_pub: EddsaPublicKey; + + // EdDSA signature of the purse over + // `TALER_PurseRequestSignaturePS` of + // purpose ``TALER_SIGNATURE_PURSE_REQUEST`` + // confirming that the + // above details hold for this purse. + purse_sig: EddsaSignature; + + // SHA-512 hash of the contact of the purse. + h_contract_terms: HashCode; + + // Client-side timestamp of when the merge request was made. + merge_timestamp: Timestamp; + + // Indicative time by which the purse should expire + // if it has not been paid. + purse_expiration: Timestamp; + + } diff --git a/core/exchange/post-reserves-attest-RESERVE_PUB.rst b/core/exchange/post-reserves-attest-RESERVE_PUB.rst @@ -0,0 +1,62 @@ +.. http:post:: /reserves-attest/$RESERVE_PUB + + Request signed KYC information about the owner of a reserve. + This can be used by a reserve owner to include a proof + of their identity in invoices. + + **Request:** + + The request body must be a `ReserveAttestRequest` object. + + **Response:** + + :http:statuscode:`200 OK`: + The exchange responds with a `ReserveAttestResponse` object. + :http:statuscode:`403 Forbidden`: + The *TALER_SIGNATURE_WALLET_KYC_DETAILS* signature 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. + :http:statuscode:`409 Conflict`: + The exchange does not have the requested KYC information. + + **Details:** + + .. ts:def:: ReserveAttestRequest + + interface ReserveAttestRequest { + // Signature of purpose + // ``TALER_SIGNATURE_WALLET_ATTEST_DETAILS`` over + // a `TALER_WalletReserveAttestRequestSignaturePS`. + reserve_sig: EddsaSignature; + + // Client's time for the request. + request_timestamp: Timestamp; + + // Array of KYC attributes requested. + details: string[]; + } + + .. ts:def:: ReserveAttestResponse + + interface ReserveAttestResponse { + // Signature of purpose + // ``TALER_SIGNATURE_EXCHANGE_RESERVE_ATTEST_DETAILS`` over + // a `TALER_ExchangeAttestPS`. + exchange_sig: EddsaSignature; + + // Exchange public key used to create the + // signature. + exchange_pub: EddsaPublicKey; + + // Time when the exchange created the signature. + exchange_timestamp: Timestamp; + + // Expiration time for the provided information. + expiration_time: Timestamp; + + // KYC details (key-value pairs) as requested. + // The keys will match the elements of the + // ``details`` array from the request. + attributes: CustomerKycAttributes; + } diff --git a/core/exchange/post-reveal-melt.rst b/core/exchange/post-reveal-melt.rst @@ -0,0 +1,93 @@ +.. http:post:: /reveal-melt + + Reveal previously committed values to the exchange, except for the values + corresponding to the ``noreveal_index`` returned by the ``/melt`` step. + + The base URL for ``/reveal-melt``-request may differ from the main base URL of + the exchange. Clients SHOULD respect the ``reveal_base_url`` returned for the + coin during melt operations. The exchange MUST return a + 307 or 308 redirection to the correct base URL if the client failed to + respect the ``reveal_base_url`` or if the allocation has changed. + + The request body is a `RevealMeltRequest`. + + This endpoint was introduced in this form in protocol **v32**. + + :http:statuscode:`200 OK`: + The coin's' secret material matched the commitment and the original request was well-formed. + The response body is a `RevealResponse`. + :http:statuscode:`403 Forbidden`: + One of the signatures is invalid. + This response comes with a standard `ErrorDetail` response. + :http:statuscode:`404 Not found`: + The provided commitment is unknown. + :http:statuscode:`409 Conflict`: + There is a problem between the original commitment and the revealed secret data. + The returned information is proof of the mismatch, + and therefore rather verbose, as it includes most of the original /melt request, + but of course expected to be primarily used for diagnostics. + + The response body is a `RevealConflictResponse`. + + **Details:** + + Request body for a ``reveal-melt`` request + contains a JSON object with the following fields: + + .. ts:def:: RevealMeltRequest + + interface RevealMeltRequest { + // The refresh commitment from the ``/melt/`` step, + // see `TALER_RefreshCommitmentP`. + rc: HashCode; + + // @since v27 + // @deprecated **v32** + // The disclosed kappa-1 signatures by the old coin's private key, + // over Hash1a("Refresh", Cp, r, i), where Cp is the melted coin's public key, + // r is the public refresh nonce from the metling step and i runs over the + // _disclosed_ kappa-1 indices. + signatures: CoinSignature[kappa-1]; + + // @since **v32** + // The batch seeds for the transfer private keys to reveal, + // as they were generated for the previous `MeltRequest`. + // That is, assuming an honest client who had generated + // kappa many batch seeds via HKDF like this: + // + // ``bs[] = HKDF(kappa*sizeof(HashCode),`` + // ``"refresh-batch-seeds",`` + // ``old_coin_priv,`` + // ``refresh_seed)``, + // + // this field contains the entries in ``bs[]`` for all the indeces + // *except* the ``noreveal_index``. + batch_seeds: HashCode[kappa-1]; + + // IFF the denomination of the old coin had support for age restriction, + // the client MUST provide the original age commitment, i. e. the + // vector of public keys, or omitted otherwise. + // The size of the vector MUST be the number of age groups as defined by the + // Exchange in the field ``.age_groups`` of the extension ``age_restriction``. + age_commitment?: Edx25519PublicKey[]; + + } + + .. ts:def:: RevealResponse + + type RevealResponse = WithdrawResponse; + + + .. ts:def:: RevealConflictResponse + + interface RevealConflictResponse { + // Text describing the error. + hint: string; + + // Detailed error code. + code: Integer; + + // Commitment as calculated by the exchange from the revealed data. + rc_expected: HashCode; + + } diff --git a/core/exchange/post-reveal-withdraw.rst b/core/exchange/post-reveal-withdraw.rst @@ -0,0 +1,98 @@ +.. http:post:: /reveal-withdraw + + Reveal previously committed values to the exchange, except for the values + corresponding to the ``noreveal_index`` returned by the ``/withdraw`` step. + + The base URL for ``/reveal-withdraw``-request may differ from the main base URL of + the exchange. Clients SHOULD respect the ``reveal_base_url`` returned for the + coin during melt operations. The exchange MUST return a + 307 or 308 redirection to the correct base URL if the client failed to + respect the ``reveal_base_url`` or if the allocation has changed. + + The request body is a `RevealWithdrawRequest`. + + This endpoint was introduced in this form in protocol **v32**. + + :http:statuscode:`200 OK`: + The coin's' secret material matched the commitment and the original request was well-formed. + The response body is a `RevealResponse`. + :http:statuscode:`404 Not found`: + The provided commitment $RCH is unknown. + :http:statuscode:`409 Conflict`: + There is a problem between the original commitment and the revealed secret data. + The returned information is proof of the mismatch, + and therefore rather verbose, as it includes most of the original /withdraw request, + but of course expected to be primarily used for diagnostics. + + The response body is a `RevealConflictResponse`. + + The following specific error codes can be returned: + + - 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``. + - The computation of the hash of the commitment with provided input does + result in the value of field ``h_commitment``. + Error code: + ``TALER_EC_EXCHANGE_AGE_WITHDRAW_REVEAL_INVALID_HASH`` + + **Details:** + + Request body for a ``reveal-withdraw`` request + contains a JSON object with the following fields: + + .. ts:def:: RevealWithdrawRequest + + interface RevealWithdrawRequest { + // The reserve's public key from the previous call to /withdraw. + reserve_pub: string; + + // This is the h_planchets running hash of all blinded planchets + // from the previous call to /withdraw. + h_planchets: string; + + // Array of ``(kappa - 1)`` disclosed batch secrets, + // from which for each of the n coins in a batch + // their coin master secret is derived, + // from which in turn their private key, + // blinding, nonce (for Clause-Schnorr) and + // age-restriction is calculated. + disclosed_batch_seeds: AgeRestrictedPlanchetSeed[]; + + } + + .. ts:def:: AgeRestrictedPlanchetSeed + + // The master seed material from which for n coins in a batch, + // each 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+1,...,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 AgeRestrictedPlanchetSeed = string; + + .. ts:def:: PublishedAgeRestrictionBaseKey + + // 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 prove that they + // derived all public keys to age groups higher than their allowed maximum + // from this particular value. + const PublishedAgeRestrictionBaseKey = + new Edx25519PublicKey("CH0VKFDZ2GWRWHQBBGEK9MWV5YDQVJ0RXEE0KYT3NMB69F0R96TG"); diff --git a/core/exchange/post-withdraw.rst b/core/exchange/post-withdraw.rst @@ -0,0 +1,134 @@ +.. http:post:: /withdraw + + 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. + + **Request:** + + The request body must be a `WithdrawRequest` object. + + **Response:** + + :http:statuscode:`200 OK`: + The request was successful, and ``max_age`` was not set. + 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 coins signature to disk, the coins are not lost. + :http:statuscode:`201 Created`: + The request was successful, and ``max_age`` was set. + The response is a `AgeWithdrawResponse`. The client is expected + to call ``/reveal-withdraw`` next. + 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 coins signature to disk, the coins are not lost. + :http:statuscode:`403 Forbidden`: + A signature is invalid. This is usually the reserve signature. + This response comes with a standard `ErrorDetail` response with + a code of ``TALER_EC_EXCHANGE_WITHDRAW_RESERVE_SIGNATURE_INVALID``. + :http:statuscode:`404 Not found`: + One of the following reasons occured: + + 1. The reserve is unknown. The response comes with a standard + `ErrorDetail` response with error-code + ``TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN``. + If the reserve is unknown, the wallet should not report a + hard error, but instead long-poll for the reserve status to wait + for the wire transfer to complete. + Once the wire transfer has arrived, + the wallet should repeat the exact same request later again, + possibly using exactly the same blinded coins. + 2. A denomination keyis not known to the exchange. + The response comes with a standard + `ErrorDetail` response with error-code + ``TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN``. + This suggests the wallet has outdated ``/keys`` and + should fetch the latest ``/keys``. + :http:statuscode:`409 Conflict`: + One of the following reasons occured: + + 1. The balance of the reserve is not sufficient to withdraw the + coins of the indicated denominations. + The response is `WithdrawError` object with an error code of + ``TALER_EC_EXCHANGE_WITHDRAW_INSUFFICIENT_FUNDS``. An operation + withdrawing less money should succeed. + 2. The reserve has a birthday set and requires the request to + provide a ``max_age`` value. + The response comes with a standard `ErrorDetail` response with + an error-code of + ``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 a call to ``/withdraw``, this time + with ``max_age`` set accordingly and ``coin_evs`` being an array + of ``n*kappa`` elements of type `CoinEnvelope`. + 3. 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 ``/withdraw``. + 4. The request uses a nonce value that was previously seen by + the exchange for a different request. As nonces must be unique, + the request is rejected. This can only happen with some cipher + types that use nonces. + :http:statuscode:`410 Gone`: + A requested denomination key is no longer valid. There are two cases: + + 1. The denomination key is past its expiration. + The response is a `DenominationGoneMessage` with a code of + ``TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED``. + 2. The denominatoin key was revoked. The response is a + plain `ErrorDetail` with a code of ``TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED``. + :http:statuscode:`412 Precondition Failed`: + A requested denomination key is not yet valid. + It is before the validity start time. + The response is a `DenominationGoneMessage` with + ``TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE``. + A common case might be a difference in the current time between + wallet and exchange. The wallet could probably just wait a bit and + retry. Checking the server's ``Date:`` header should allow the + wallet to figure out how long to wait. Alternatively, the wallet + could try with an the previous denomination key generation. + Note: this is a bit of an abuse of the HTTP status code. + :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 `LegitimizationNeededResponse` 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. + :http:statuscode:`501 Not implemented`: + The client has provided a cipher that is not supported. + :http:statuscode:`502 Bad gateway`: + This indicates the exchange could not communicate with an + external process. This usually means the exchange could + not talk to one of its secmod helpers. + Here, a standard error message with a code of + ``TALER_EC_EXCHANGE_SIGNKEY_HELPER_UNAVAILABLE`` + is returned. + Wallets should retry the requests (with some delays) at + a later time. + :http:statuscode:`503 Service Unavailable`: + This primarily happens when the exchange currently has no + denomination signing keys at all, for example because the + offline signature did not yet happen. In this case, a standard + error message with a code of + ``TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING`` is returned. + + **Details:**