commit 11b087e82244f171b386a3d53facee7a43727983
parent c336901cbce62b7d2833fa5a930f9ca443d146da
Author: Christian Grothoff <christian@grothoff.org>
Date: Thu, 8 Aug 2024 12:41:38 +0200
reorganize docs structure
Diffstat:
6 files changed, 2518 insertions(+), 2479 deletions(-)
diff --git a/core/api-exchange.rst b/core/api-exchange.rst
@@ -24,9 +24,6 @@ for all details not specified in the individual requests.
The `glossary <https://docs.taler.net/taler-developer-manual.html#developer-glossary>`_
defines all specific terms used in this section.
-.. contents:: Table of Contents
- :local:
-
.. include:: tos.rst
.. _keys:
@@ -1314,1106 +1311,935 @@ Management operations authorized by master key
}
---------------
-AML operations
---------------
+---------------
+Auditor actions
+---------------
-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.
+.. _auditor_action:
+This part of the API is for the use by auditors interacting with the exchange.
-.. 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.
+.. http:post:: /auditors/$AUDITOR_PUB/$H_DENOM_PUB
- This endpoint was introduced in protocol **v20**.
+ 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:**
- *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.
+ The request must be a `AuditorSignatureAddMessage`.
**Response:**
- :http:statuscode:`200 Ok`:
- Information about possible measures is returned in a
- `AvailableMeasureSummary` object.
+ :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:: 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; };
+ .. ts:def:: DenominationUnknownMessage
- }
+ interface DenominationUnknownMessage {
- .. ts:def:: MeasureInformation
+ // Taler error code.
+ code: number;
- interface MeasureInformation {
+ // Signature by the exchange over a
+ // `TALER_DenominationUnknownAffirmationPS`.
+ // Must have purpose ``TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_UNKNOWN``.
+ exchange_sig: EddsaSignature;
- // Name of a KYC check.
- check_name: string;
+ // Public key of the exchange used to create
+ // the 'exchange_sig.
+ exchange_pub: EddsaPublicKey;
- // Name of an AML program.
- prog_name: string;
+ // Hash of the denomination public key that is unknown.
+ h_denom_pub: HashCode;
- // Context for the check. Optional.
- context?: Object;
+ // When was the signature created.
+ timestamp: Timestamp;
}
- .. ts:def:: AmlProgramRequirement
-
- interface AmlProgramRequirement {
-
- // Description of what the AML program does.
- description: string;
+ .. ts:def:: AuditorSignatureAddMessage
- // 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[];
+ interface AuditorSignatureAddMessage {
- // 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[];
+ // Signature by the auditor over a
+ // `TALER_ExchangeKeyValidityPS`.
+ // Must have purpose ``TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS``.
+ auditor_sig: EddsaSignature;
}
- .. ts:def:: KycCheckInformation
-
- interface KycCheckInformation {
+.. _exchange-withdrawal:
- // 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;
+----------
+Withdrawal
+----------
- // Map from IETF BCP 47 language tags to localized
- // description texts.
- description_i18n ?: { [lang_tag: string]: string};
+This API is used by the wallet to obtain digital coins.
- // 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[];
+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.
- // 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[];
+.. note::
- // Name of a root measure taken when this check fails.
- fallback: 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.
-.. http:get:: /aml/$OFFICER_PUB/kyc-statistics/$NAME
- Returns the number of KYC events matching the given event type ``$NAME`` 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.)
+.. http:get:: /reserves/$RESERVE_PUB
- This endpoint was introduced in protocol **v20**.
+ Request summary information about a reserve.
**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.
+ :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 responds will be an `EventCounter` message.
+ 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:: EventCounter
+ .. ts:def:: ReserveSummary
- interface EventCounter {
- // Number of events of the specified type in
- // the given range.
- counter: Integer;
- }
+ interface ReserveSummary {
+ // Balance left in the reserve.
+ balance: Amount;
+ // If set, age restriction is required 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.
+ // 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?: number;
+ }
-.. http:get:: /aml/$OFFICER_PUB/decisions
- **Request:**
+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.
+.. http:post:: /csr-withdraw
- This endpoint was introduced in this form in protocol **v20**.
+ Obtain exchange-side input values in preparation for a
+ withdraw step for certain denomination cipher types,
+ specifically at this point for Clause-Schnorr blind
+ signatures.
- :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. 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 superceeded. 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.
+ **Request:** The request body must be a `WithdrawPrepareRequest` object.
**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.
+ The request was successful, and the response is a `WithdrawPrepareResponse`. Note that repeating exactly the same request
+ will again yield the same response (assuming none of the denomination is expired).
:http:statuscode:`404 Not found`:
- The designated AML account is not known.
- :http:statuscode:`409 Conflict`:
- The designated AML account is not enabled.
+ The denomination key is not known to the exchange.
+ :http:statuscode:`410 Gone`:
+ The requested denomination key is not yet or no longer valid.
+ It either before the validity start, past the expiration or was revoked. The response is a
+ `DenominationExpiredMessage`. Clients must evaluate
+ the error code provided to understand which of the
+ cases this is and handle it accordingly.
**Details:**
- .. ts:def:: AmlDecisionsResponse
-
- interface AmlDecisionsResponse {
+ .. ts:def:: WithdrawPrepareRequest
- // Array of AML decisions matching the query.
- records: AmlDecision[];
- }
+ interface WithdrawPrepareRequest {
- .. ts:def:: AmlDecision
+ // Nonce to be used by the exchange to derive
+ // its private inputs from. Must not have ever
+ // been used before.
+ nonce: CSNonce;
- interface AmlDecision {
+ // Hash of the public key of the denomination the
+ // request relates to.
+ denom_pub_hash: HashCode;
- // Which payto-address is this record about.
- // Identifies a GNU Taler wallet or an affected bank account.
- h_payto: PaytoHash;
+ }
- // Row ID of the record. Used to filter by offset.
- rowid: Integer;
+ .. ts:def:: WithdrawPrepareResponse
- // Justification for the decision. NULL if none
- // is available.
- justification?: string;
+ type WithdrawPrepareResponse =
+ | ExchangeWithdrawValue;
- // When was the decision made?
- decision_time: Timestamp;
+ .. ts:def:: ExchangeWithdrawValue
- // 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.
- properties?: AccountProperties;
+ type ExchangeWithdrawValue =
+ | ExchangeRsaWithdrawValue
+ | ExchangeCsWithdrawValue;
- // What are the new rules?
- limits: LegitimizationRuleSet;
+ .. ts:def:: ExchangeRsaWithdrawValue
- // True if the account is under investigation by AML staff
- // after this decision.
- to_investigate: boolean;
+ interface ExchangeRsaWithdrawValue {
+ cipher: "RSA";
+ }
- // True if this is the active decision for the
- // account.
- is_active: boolean;
+ .. ts:def:: ExchangeCsWithdrawValue
- }
+ interface ExchangeCsWithdrawValue {
+ cipher: "CS";
- .. ts:def:: AccountProperties
+ // CSR R0 value
+ r_pub_0: CsRPublic;
- // 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 {
+ // CSR R1 value
+ r_pub_1: CsRPublic;
+ }
- // 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;
+Batch Withdraw
+~~~~~~~~~~~~~~
- // True if this is a high-risk account.
- // Rules for classifying accounts as at-risk
- // are exchange operator-dependent.
- high_risk?: boolean;
+.. http:post:: /reserves/$RESERVE_PUB/batch-withdraw
- // Business domain of the account owner.
- // The list of possible business domains is
- // operator- or country-dependent.
- business_domain?: string;
+ 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.
- // Is the client's account currently frozen?
- is_frozen?: boolean;
+ **Request:** The request body must be a `BatchWithdrawRequest` object.
- // Was the client's account reported to the authorities?
- was_reported?: boolean;
+ **Response:**
- }
+ :http:statuscode:`200 OK`:
+ The request was successful, and the response is a `BatchWithdrawResponse`.
+ Note that repeating exactly the same request will again yield the same
+ response, so if the network goes down during the transaction or before the
+ client can commit the coin signature to disk, the coin is not lost.
+ :http:statuscode:`403 Forbidden`:
+ A signature is invalid.
+ This response comes with a standard `ErrorDetail` response.
+ :http:statuscode:`404 Not found`:
+ A denomination key or the reserve are not known to the exchange. If the
+ denomination key is unknown, this suggests a bug in the wallet as the
+ wallet should have used current denomination keys from ``/keys``.
+ In this case, the response will be a `DenominationUnknownMessage`.
+ If the reserve is unknown, the wallet should not report a hard error yet, but
+ instead simply wait for up to a day, as the wire transaction might simply
+ not yet have completed and might be known to the exchange in the near future.
+ In this case, the wallet should repeat the exact same request later again
+ using exactly the same blinded coin.
+ :http:statuscode:`409 Conflict`:
+ One of the following reasons occured:
- .. ts:def:: LegitimizationRuleSet
+ 1. The balance of the reserve is not sufficient to withdraw the coins of the
+ indicated denominations. The response is `WithdrawError` object.
- interface LegitimizationRuleSet {
+ 2. The reserve has a birthday set and requires a request to ``/age-withdraw`` instead.
+ The response comes with a standard `ErrorDetail` response with error-code ``TALER_EC_EXCHANGE_RESERVES_AGE_RESTRICTION_REQUIRED`` and an additional field ``maximum_allowed_age`` for the maximum age (in years) that the client can commit to in the call to ``/age-withdraw``
+ :http:statuscode:`410 Gone`:
+ A requested denomination key is not yet or no longer valid.
+ It either before the validity start, past the expiration or was revoked.
+ The response is a `DenominationExpiredMessage`. Clients must evaluate the
+ error code provided to understand which of the cases this is and handle it
+ accordingly.
+ :http:statuscode:`451 Unavailable for Legal Reasons`:
+ This reserve has received funds from a purse or the amount withdrawn
+ exceeds another legal threshold and thus the reserve must
+ be upgraded to an account (with KYC) before the withdraw can
+ complete. Note that this response does NOT affirm that the
+ withdraw will ultimately complete with the requested amount.
+ The user should be redirected to the provided location to perform
+ the required KYC checks to open the account before withdrawing.
+ Afterwards, the request should be repeated.
+ The response will be an `LegitimizationNeededResponse` object.
- // When does this set of rules expire and
- // we automatically transition to the successor
- // measure?
- expiration_time: Timestamp;
+ 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.
- // 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[];
+ **Details:**
- // Custom measures that KYC rules and the
- // ``successor_measure`` may refer to.
- custom_measures: { "$measure_name" : MeasureInformation; };
+ .. ts:def:: BatchWithdrawRequest
- }
+ interface BatchWithdrawRequest {
+ // Array of requests for the individual coins to withdraw.
+ planchets: WithdrawRequest[];
- .. ts:def:: KycRule
+ }
- interface KycRule {
+ .. ts:def:: WithdrawRequest
- // Type of operation to which the rule applies.
- operation_type: string;
+ interface WithdrawRequest {
+ // Hash of a denomination public key, specifying the type of coin the client
+ // would like the exchange to create.
+ denom_pub_hash: HashCode;
- // The measures will be taken if the given
- // threshold is crossed over the given timeframe.
- threshold: Amount;
+ // Coin's blinded public key, should be (blindly) signed by the exchange's
+ // denomination private key.
+ coin_ev: CoinEnvelope;
- // 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;
+ // Signature of `TALER_WithdrawRequestPS` created with
+ // the `reserves's private key <reserve-priv>`
+ // using purpose ``TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW``.
+ reserve_sig: EddsaSignature;
- // 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;
+ .. ts:def:: BatchWithdrawResponse
- // 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;
+ interface BatchWithdrawResponse {
+ // Array of blinded signatures, in the same order as was
+ // given in the request.
+ ev_sigs: WithdrawResponse[];
}
-.. http:get:: /aml/$OFFICER_PUB/attributes/$H_PAYTO
+ .. ts:def:: WithdrawResponse
- Obtain attributes obtained as part of AML/KYC processes for a
- given account.
+ interface WithdrawResponse {
+ // The blinded signature over the 'coin_ev', affirms the coin's
+ // validity after unblinding.
+ ev_sig: BlindedDenominationSignature;
- This endpoint was introduced in protocol **v20**.
+ }
- **Request:**
+ .. ts:def:: BlindedDenominationSignature
- *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.
+ type BlindedDenominationSignature =
+ | RsaBlindedDenominationSignature
+ | CSBlindedDenominationSignature;
- :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.
+ .. ts:def:: RsaBlindedDenominationSignature
- **Response:**
+ interface RsaBlindedDenominationSignature {
+ cipher: "RSA";
- :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.
+ // (blinded) RSA signature
+ blinded_rsa_signature: BlindedRsaSignature;
+ }
- .. 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;
+ .. ts:def:: CSBlindedDenominationSignature
- // Name of the provider
- // which was used to collect the attributes. NULL if they were
- // just uploaded via a form by the account owner.
- provider_name?: string;
+ interface CSBlindedDenominationSignature {
+ type: "CS";
- // 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?: Object;
+ // Signer chosen bit value, 0 or 1, used
+ // in Clause Blind Schnorr to make the
+ // ROS problem harder.
+ b: Integer;
- // Time when the KYC data was collected
- collection_time: Timestamp;
+ // Blinded scalar calculated from c_b.
+ s: Cs25519Scalar;
}
+ .. ts:def:: WithdrawError
- .. http:post:: /aml/$OFFICER_PUB/decision
+ interface WithdrawError {
+ // Text describing the error.
+ hint: string;
- Make an AML decision. Triggers the respective action and
- records the justification.
+ // Detailed error code.
+ code: Integer;
- **Request:**
+ // Amount left in the reserve.
+ balance: Amount;
- The request must be an `AmlDecisionRequest` message.
+ // History of the reserve's activity, in the same format
+ // as returned by ``/reserve/$RID/history``.
+ history: TransactionHistoryItem[]
+ }
- **Response**
+ .. ts:def:: DenominationExpiredMessage
- :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 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.
+ interface DenominationExpiredMessage {
- **Details:**
+ // Taler error code. Note that beyond
+ // expiration this message format is also
+ // used if the key is not yet valid, or
+ // has been revoked.
+ code: number;
- .. ts:def:: AmlDecisionRequest
+ // Signature by the exchange over a
+ // `TALER_DenominationExpiredAffirmationPS`.
+ // Must have purpose ``TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_EXPIRED``.
+ exchange_sig: EddsaSignature;
- interface AmlDecisionRequest {
+ // Public key of the exchange used to create
+ // the 'exchange_sig.
+ exchange_pub: EddsaPublicKey;
- // Human-readable justification for the decision.
- justification: string;
+ // Hash of the denomination public key that is unknown.
+ h_denom_pub: HashCode;
- // Which payto-address is the decision about?
- // Identifies a GNU Taler wallet or an affected bank account.
- h_payto: PaytoHash;
+ // When was the signature created.
+ timestamp: Timestamp;
- // What are the new rules?
- // New since protocol **v20**.
- new_rules: LegitimizationRuleSet;
+ // What kind of operation was requested that now
+ // failed?
+ oper: string;
+ }
- // What are the new account properties?
- // New since protocol **v20**.
- properties: AccountProperties;
- // New measure to apply immediately to the account.
- // Should typically be used to give the user some
- // information or request additional information.
- // Use "verboten" to communicate to the customer
- // that there is no KYC check that could be passed
- // to modify the ``new_rules``.
- // New since protocol **v20**.
- new_measure?: string;
- // True if the account should remain under investigation by AML staff.
- // New 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;
+Withdraw with Age Restriction
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- }
+If the reserve was marked with a maximum age group, the client has to perform a
+cut&choose protocol with the exchange. It first calls
+``/reserves/$RESERVE_PUB/age-withdraw`` and commits to ``n*kappa`` coins. On
+success, the exchange answers this request with an noreveal-index. The client
+then has to call ``/age-withdraw/$ACH/reveal`` to reveal all ``n*(kappa - 1)``
+coins along with their age commitments to proof that they were appropriate.
+If so, the exchange will blindly sign ``n`` undisclosed coins from the request.
----------------
-Auditor actions
----------------
+.. http:post:: /reserves/$RESERVE_PUB/age-withdraw
-.. _auditor_action:
+ Withdraw multiple coins *with age restriction* from the same reserve.
+ Note that the client should commit all of the request details, including the
+ private key of the coins and the blinding factors, to disk *before* issuing
+ this request, so that it can recover the information if necessary in case of
+ transient failures, like power outage, network outage, etc.
-This part of the API is for the use by auditors interacting with the exchange.
+ **Request:** The request body must be a `AgeWithdrawRequest` object.
+ **Response:**
-.. http:post:: /auditors/$AUDITOR_PUB/$H_DENOM_PUB
+ :http:statuscode:`200 OK`:
+ The request was successful, and the response is a `AgeWithdrawResponse`.
+ Note that repeating exactly the same request will again yield the same
+ response, so if the network goes down during the transaction or before the
+ client can commit the coin signature to disk, the coin is not lost.
+ :http:statuscode:`403 Forbidden`:
+ A signature is invalid.
+ This response comes with a standard `ErrorDetail` response.
+ :http:statuscode:`409 Conflict`:
+ One of two reasons occured:
- 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.
+ 1. The balance of the reserve is not sufficient to withdraw the coins of the
+ given amount. The response is a `WithdrawError` object.
- **Request:**
+ 2. The provided value for ``max_age`` is higher than the allowed value according to the reserve's birthday.
+ The response comes with a standard `ErrorDetail` response with error-code ``TALER_EC_EXCHANGE_AGE_WITHDRAW_MAXIMUM_AGE_TOO_LARGE`` and an additional field ``maximum_allowed_age`` for the maximum age (in years) that the client can commit to in a call to ``/age-withdraw``
+ :http:statuscode:`410 Gone`:
+ A requested denomination key is not yet or no longer valid.
+ It either before the validity start, past the expiration or was revoked.
+ The response is a `DenominationExpiredMessage`. Clients must evaluate the
+ error code provided to understand which of the cases this is and handle it
+ accordingly.
+ :http:statuscode:`451 Unavailable for Legal Reasons`:
+ This reserve has received funds from a purse or the amount withdrawn
+ exceeds another legal threshold and thus the reserve must
+ be upgraded to an account (with KYC) before the withdraw can
+ complete. Note that this response does NOT affirm that the
+ withdraw will ultimately complete with the requested amount.
+ The user should be redirected to the provided location to perform
+ the required KYC checks to open the account before withdrawing.
+ Afterwards, the request should be repeated.
+ The response will be an `LegitimizationNeededResponse` object.
- The request must be a `AuditorSignatureAddMessage`.
+ .. ts:def:: AgeWithdrawRequest
- **Response:**
+ interface AgeWithdrawRequest {
+ // Array of ``n`` hash codes of denomination public keys to order.
+ // These denominations MUST support age restriction as defined in the
+ // output to /keys.
+ // The sum of all denomination's values and fees MUST be at most the
+ // balance of the reserve. The balance of the reserve will be
+ // immediatley reduced by that amount.
+ denoms_h: HashCode[];
- :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.
+ // ``n`` arrays of ``kappa`` entries with blinded coin envelopes. Each
+ // (toplevel) entry represents ``kappa`` canditates for a particular
+ // coin. The exchange will respond with an index ``gamma``, which is
+ // the index that shall remain undisclosed during the reveal phase.
+ // The SHA512 hash $ACH over the blinded coin envelopes is the commitment
+ // that is later used as the key to the reveal-URL.
+ blinded_coins_evs: CoinEnvelope[][];
- **Details:**
+ // The maximum age to commit to. MUST be the same as the maximum
+ // age in the reserve.
+ max_age: number;
- .. ts:def:: DenominationUnknownMessage
+ // Signature of `TALER_AgeWithdrawRequestPS` created with
+ // the `reserves's private key <reserve-priv>`
+ // using purpose ``TALER_SIGNATURE_WALLET_RESERVE_AGE_WITHDRAW``.
+ reserve_sig: EddsaSignature;
+ }
- interface DenominationUnknownMessage {
+ .. ts:def:: AgeWithdrawResponse
- // Taler error code.
- code: number;
+ interface AgeWithdrawResponse {
+ // index of the commitments that the client doesn't
+ // have to disclose
+ noreveal_index: Integer;
- // Signature by the exchange over a
- // `TALER_DenominationUnknownAffirmationPS`.
- // Must have purpose ``TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_UNKNOWN``.
+ // Signature of `TALER_AgeWithdrawConfirmationPS` whereby
+ // the exchange confirms the ``noreveal_index``.
exchange_sig: EddsaSignature;
- // Public key of the exchange used to create
- // the 'exchange_sig.
+ // `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;
+ }
- // Hash of the denomination public key that is unknown.
- h_denom_pub: HashCode;
-
- // When was the signature created.
- timestamp: Timestamp;
- }
- .. ts:def:: AuditorSignatureAddMessage
+.. http:post:: /age-withdraw/$ACH/reveal
- interface AuditorSignatureAddMessage {
+ The client has previously committed to multiple coins with age restriction
+ in a call to ``/reserve/$RESERVE_PUB/age-withdraw`` and got a
+ `AgeWithdrawResponse` from the exchange. By calling this
+ endpoint, the client has to reveal each coin and their ``kappa - 1``
+ age commitments, except for the age commitments with index
+ ``noreveal_index``. The hash of all commitments from the former withdraw
+ request is given as the ``$ACH`` value in the URL to this endpoint.
- // Signature by the auditor over a
- // `TALER_ExchangeKeyValidityPS`.
- // Must have purpose ``TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS``.
- auditor_sig: EddsaSignature;
- }
+ **Request:** The request body must be a `AgeWithdrawRevealRequest` object.
-.. _exchange-withdrawal:
+ **Response:**
-----------
-Withdrawal
-----------
+ :http:statuscode:`200 OK`:
+ The request was successful, and the response is a `AgeWithdrawRevealResponse`.
+ Note that repeating exactly the same request will again yield the same
+ response, so if the network goes down during the transaction or before the
+ client can commit the coin signature to disk, the coin is not lost.
+ :http:statuscode:`404 Not found`:
+ The provided commitment $ACH is unknown.
+ :http:statuscode:`409 Conflict`:
+ The reveal operation failed and the response is an `WithdrawError` object.
+ The error codes indicate one of two cases:
-This API is used by the wallet to obtain digital coins.
+ 1. An age commitment for at least one of the coins did not fulfill the
+ required maximum age requirement of the corresponding reserve.
+ Error code:
+ ``TALER_EC_EXCHANGE_GENERIC_COIN_AGE_REQUIREMENT_FAILURE``.
+ 2. The computation of the hash of the commitment with provided input does
+ result in the value $ACH.
+ Error code:
+ ``TALER_EC_EXCHANGE_AGE_WITHDRAW_REVEAL_INVALID_HASH``
-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::
+ .. ts:def:: AgeWithdrawRevealRequest
- 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.
+ interface AgeWithdrawRevealRequest {
+ // Array of ``n`` of ``(kappa - 1)`` disclosed coin master secrets, from
+ // which the coins' private key, blinding, nonce (for Clause-Schnorr) and
+ // age-restriction is calculated.
+ //
+ // Given each coin's private key and age commitment, the exchange will
+ // calculate each coin's blinded hash value und use all those (disclosed)
+ // blinded hashes together with the non-disclosed envelopes ``coin_evs``
+ // during the verification of the original age-withdraw-commitment.
+ disclosed_coin_secrets: AgeRestrictedCoinSecret[][];
+ }
+ .. ts:def:: AgeRestrictedCoinSecret
-.. http:get:: /reserves/$RESERVE_PUB
+ // The Master key material from which the coins' private key ``coin_priv``,
+ // blinding ``beta`` and nonce ``nonce`` (for Clause-Schnorr) itself are
+ // derived as usually in wallet-core. Given a coin's master key material,
+ // the age commitment for the coin MUST be derived from this private key as
+ // follows:
+ //
+ // Let m ∈ {1,...,M} be the maximum age group as defined in the reserve
+ // that the wallet can commit to.
+ //
+ // For age group $AG ∈ {1,...m}, set
+ // seed = HDKF(coin_secret, "age-commitment", $AG)
+ // p[$AG] = Edx25519_generate_private(seed)
+ // and calculate the corresponding Edx25519PublicKey as
+ // q[$AG] = Edx25519_public_from_private(p[$AG])
+ //
+ // For age groups $AG ∈ {m,...,M}, set
+ // f[$AG] = HDKF(coin_secret, "age-factor", $AG)
+ // and calculate the corresponding Edx25519PublicKey as
+ // q[$AG] = Edx25519_derive_public(`PublishedAgeRestrictionBaseKey`, f[$AG])
+ //
+ type AgeRestrictedCoinSecret = string;
- Request summary information about a reserve.
+ .. ts:def:: PublishedAgeRestrictionBaseKey
- **Request:**
+ // The value for ``PublishedAgeRestrictionBaseKey`` is a randomly chosen
+ // `Edx25519PublicKey` for which the private key is not known to the clients. It is
+ // used during the age-withdraw protocol so that clients can proof that they
+ // derived all public keys to age groups higher than their allowed maximum
+ // from this particular value.
+ const PublishedAgeRestrictionBaseKey =
+ new Edx25519PublicKey("CH0VKFDZ2GWRWHQBBGEK9MWV5YDQVJ0RXEE0KYT3NMB69F0R96TG");
- :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.
+ .. ts:def:: AgeWithdrawRevealResponse
- **Response:**
+ interface AgeWithdrawRevealResponse {
+ // List of the exchange's blinded RSA signatures on the new coins.
+ ev_sigs : BlindedDenominationSignature[];
+ }
- :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:**
+.. _reserve-history:
- .. ts:def:: ReserveSummary
+---------------
+Reserve History
+---------------
- interface ReserveSummary {
- // Balance left in the reserve.
- balance: Amount;
+.. http:get:: /reserves/$RESERVE_PUB/history
- // If set, age restriction is required 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.
- // 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?: number;
- }
+ Request information about the full history of
+ a reserve or an account.
+ **Request:**
-Withdraw
-~~~~~~~~
+ The GET request should come with the following HTTP headers:
-.. http:post:: /csr-withdraw
+ *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.
- Obtain exchange-side input values in preparation for a
- withdraw step for certain denomination cipher types,
- specifically at this point for Clause-Schnorr blind
- signatures.
+ *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.
- **Request:** The request body must be a `WithdrawPrepareRequest` object.
+ :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 request was successful, and the response is a `WithdrawPrepareResponse`. Note that repeating exactly the same request
- will again yield the same response (assuming none of the denomination is expired).
+ 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 denomination key is not known to the exchange.
- :http:statuscode:`410 Gone`:
- The requested denomination key is not yet or no longer valid.
- It either before the validity start, past the expiration or was revoked. The response is a
- `DenominationExpiredMessage`. Clients must evaluate
- the error code provided to understand which of the
- cases this is and handle it accordingly.
+ The reserve key does not belong to a reserve known to the exchange.
**Details:**
- .. ts:def:: WithdrawPrepareRequest
-
- interface WithdrawPrepareRequest {
+ .. ts:def:: ReserveHistory
- // Nonce to be used by the exchange to derive
- // its private inputs from. Must not have ever
- // been used before.
- nonce: CSNonce;
+ interface ReserveHistory {
+ // Balance left in the reserve.
+ balance: Amount;
- // Hash of the public key of the denomination the
- // request relates to.
- denom_pub_hash: HashCode;
+ // If set, gives the maximum age group that the client is required to set
+ // during withdrawal.
+ maximum_age_group: number;
+ // Transaction history for this reserve.
+ // May be partial (!).
+ history: TransactionHistoryItem[];
}
- .. ts:def:: WithdrawPrepareResponse
+ Objects in the transaction history have the following format:
- type WithdrawPrepareResponse =
- | ExchangeWithdrawValue;
+ .. ts:def:: TransactionHistoryItem
- .. ts:def:: ExchangeWithdrawValue
+ // Union discriminated by the "type" field.
+ type TransactionHistoryItem =
+ | AccountSetupTransaction
+ | ReserveWithdrawTransaction
+ | ReserveAgeWithdrawTransaction
+ | ReserveCreditTransaction
+ | ReserveClosingTransaction
+ | ReserveOpenRequestTransaction
+ | ReserveCloseRequestTransaction
+ | PurseMergeTransaction;
- type ExchangeWithdrawValue =
- | ExchangeRsaWithdrawValue
- | ExchangeCsWithdrawValue;
+ .. ts:def:: AccountSetupTransaction
- .. ts:def:: ExchangeRsaWithdrawValue
+ interface AccountSetupTransaction {
+ type: "SETUP";
- interface ExchangeRsaWithdrawValue {
- cipher: "RSA";
- }
+ // Offset of this entry in the reserve history.
+ // Useful to request incremental histories via
+ // the "start" query parameter.
+ history_offset: Integer;
- .. ts:def:: ExchangeCsWithdrawValue
+ // KYC fee agreed to by the reserve owner.
+ kyc_fee: Amount;
- interface ExchangeCsWithdrawValue {
- cipher: "CS";
+ // Time when the KYC was triggered.
+ kyc_timestamp: Timestamp;
- // CSR R0 value
- r_pub_0: CsRPublic;
+ // 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;
- // CSR R1 value
- r_pub_1: CsRPublic;
}
+ .. ts:def:: ReserveWithdrawTransaction
-Batch Withdraw
-~~~~~~~~~~~~~~
+ interface ReserveWithdrawTransaction {
+ type: "WITHDRAW";
-.. http:post:: /reserves/$RESERVE_PUB/batch-withdraw
+ // Offset of this entry in the reserve history.
+ // Useful to request incremental histories via
+ // the "start" query parameter.
+ history_offset: Integer;
- Withdraw multiple coins from the same reserve. Note that the client should
- commit all of the request details, including the private key of the coins and
- the blinding factors, to disk *before* issuing this request, so that it can
- recover the information if necessary in case of transient failures, like
- power outage, network outage, etc.
+ // Amount withdrawn.
+ amount: Amount;
- **Request:** The request body must be a `BatchWithdrawRequest` object.
+ // Hash of the denomination public key of the coin.
+ h_denom_pub: HashCode;
- **Response:**
+ // Hash of the blinded coin to be signed.
+ h_coin_envelope: HashCode;
- :http:statuscode:`200 OK`:
- The request was successful, and the response is a `BatchWithdrawResponse`.
- Note that repeating exactly the same request will again yield the same
- response, so if the network goes down during the transaction or before the
- client can commit the coin signature to disk, the coin is not lost.
- :http:statuscode:`403 Forbidden`:
- A signature is invalid.
- This response comes with a standard `ErrorDetail` response.
- :http:statuscode:`404 Not found`:
- A denomination key or the reserve are not known to the exchange. If the
- denomination key is unknown, this suggests a bug in the wallet as the
- wallet should have used current denomination keys from ``/keys``.
- In this case, the response will be a `DenominationUnknownMessage`.
- If the reserve is unknown, the wallet should not report a hard error yet, but
- instead simply wait for up to a day, as the wire transaction might simply
- not yet have completed and might be known to the exchange in the near future.
- In this case, the wallet should repeat the exact same request later again
- using exactly the same blinded coin.
- :http:statuscode:`409 Conflict`:
- One of the following reasons occured:
-
- 1. The balance of the reserve is not sufficient to withdraw the coins of the
- indicated denominations. The response is `WithdrawError` object.
+ // Signature over a `TALER_WithdrawRequestPS`
+ // with purpose ``TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW``
+ // created with the reserve's private key.
+ reserve_sig: EddsaSignature;
- 2. The reserve has a birthday set and requires a request to ``/age-withdraw`` instead.
- The response comes with a standard `ErrorDetail` response with error-code ``TALER_EC_EXCHANGE_RESERVES_AGE_RESTRICTION_REQUIRED`` and an additional field ``maximum_allowed_age`` for the maximum age (in years) that the client can commit to in the call to ``/age-withdraw``
- :http:statuscode:`410 Gone`:
- A requested denomination key is not yet or no longer valid.
- It either before the validity start, past the expiration or was revoked.
- The response is a `DenominationExpiredMessage`. Clients must evaluate the
- error code provided to understand which of the cases this is and handle it
- accordingly.
- :http:statuscode:`451 Unavailable for Legal Reasons`:
- This reserve has received funds from a purse or the amount withdrawn
- exceeds another legal threshold and thus the reserve must
- be upgraded to an account (with KYC) before the withdraw can
- complete. Note that this response does NOT affirm that the
- withdraw will ultimately complete with the requested amount.
- The user should be redirected to the provided location to perform
- the required KYC checks to open the account before withdrawing.
- Afterwards, the request should be repeated.
- The response will be an `LegitimizationNeededResponse` object.
+ // Fee that is charged for withdraw.
+ withdraw_fee: Amount;
+ }
- Implementation note: internally, we need to
- distinguish between upgrading the reserve to an
- account (due to P2P payment) and identifying the
- owner of the origin bank account (due to exceeding
- the withdraw amount threshold), as we need to create
- a different payto://-URI for the KYC check depending
- on the case.
+ .. ts:def:: ReserveAgeWithdrawTransaction
+ interface ReserveAgeWithdrawTransaction {
+ type: "AGEWITHDRAW";
- **Details:**
+ // Offset of this entry in the reserve history.
+ // Useful to request incremental histories via
+ // the "start" query parameter.
+ history_offset: Integer;
- .. ts:def:: BatchWithdrawRequest
+ // Total Amount withdrawn.
+ amount: Amount;
- interface BatchWithdrawRequest {
- // Array of requests for the individual coins to withdraw.
- planchets: WithdrawRequest[];
+ // Commitment of all ``n*kappa`` blinded coins.
+ h_commitment: HashCode;
- }
+ // Signature over a `TALER_AgeWithdrawRequestPS`
+ // with purpose ``TALER_SIGNATURE_WALLET_RESERVE_AGE_WITHDRAW``
+ // created with the reserve's private key.
+ reserve_sig: EddsaSignature;
- .. ts:def:: WithdrawRequest
+ // Fee that is charged for withdraw.
+ withdraw_fee: Amount;
+ }
- interface WithdrawRequest {
- // Hash of a denomination public key, specifying the type of coin the client
- // would like the exchange to create.
- denom_pub_hash: HashCode;
- // Coin's blinded public key, should be (blindly) signed by the exchange's
- // denomination private key.
- coin_ev: CoinEnvelope;
+ .. ts:def:: ReserveCreditTransaction
- // Signature of `TALER_WithdrawRequestPS` created with
- // the `reserves's private key <reserve-priv>`
- // using purpose ``TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW``.
- reserve_sig: EddsaSignature;
+ 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;
- .. ts:def:: BatchWithdrawResponse
+ // Sender account ``payto://`` URL.
+ sender_account_url: string;
- interface BatchWithdrawResponse {
- // Array of blinded signatures, in the same order as was
- // given in the request.
- ev_sigs: WithdrawResponse[];
+ // 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:: WithdrawResponse
-
- interface WithdrawResponse {
- // The blinded signature over the 'coin_ev', affirms the coin's
- // validity after unblinding.
- ev_sig: BlindedDenominationSignature;
-
- }
- .. ts:def:: BlindedDenominationSignature
+ .. ts:def:: ReserveClosingTransaction
- type BlindedDenominationSignature =
- | RsaBlindedDenominationSignature
- | CSBlindedDenominationSignature;
+ interface ReserveClosingTransaction {
+ type: "CLOSING";
- .. ts:def:: RsaBlindedDenominationSignature
+ // Offset of this entry in the reserve history.
+ // Useful to request incremental histories via
+ // the "start" query parameter.
+ history_offset: Integer;
- interface RsaBlindedDenominationSignature {
- cipher: "RSA";
+ // Closing balance.
+ amount: Amount;
- // (blinded) RSA signature
- blinded_rsa_signature: BlindedRsaSignature;
- }
+ // Closing fee charged by the exchange.
+ closing_fee: Amount;
- .. ts:def:: CSBlindedDenominationSignature
+ // Wire transfer subject.
+ wtid: Base32;
- interface CSBlindedDenominationSignature {
- type: "CS";
+ // ``payto://`` URI of the wire account into which the funds were returned to.
+ receiver_account_details: string;
- // Signer chosen bit value, 0 or 1, used
- // in Clause Blind Schnorr to make the
- // ROS problem harder.
- b: Integer;
+ // This is a signature over a
+ // struct `TALER_ReserveCloseConfirmationPS` with purpose
+ // ``TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED``.
+ exchange_sig: EddsaSignature;
- // Blinded scalar calculated from c_b.
- s: Cs25519Scalar;
+ // Public key used to create 'exchange_sig'.
+ exchange_pub: EddsaPublicKey;
+ // Time when the reserve was closed.
+ timestamp: Timestamp;
}
- .. ts:def:: WithdrawError
- interface WithdrawError {
- // Text describing the error.
- hint: string;
+ .. ts:def:: ReserveOpenRequestTransaction
- // Detailed error code.
- code: Integer;
+ interface ReserveOpenRequestTransaction {
+ type: "OPEN";
- // Amount left in the reserve.
- balance: Amount;
+ // Offset of this entry in the reserve history.
+ // Useful to request incremental histories via
+ // the "start" query parameter.
+ history_offset: Integer;
- // History of the reserve's activity, in the same format
- // as returned by ``/reserve/$RID/history``.
- history: TransactionHistoryItem[]
- }
+ // Open fee paid from the reserve.
+ open_fee: Amount;
- .. ts:def:: DenominationExpiredMessage
+ // This is a signature over
+ // a struct `TALER_ReserveOpenPS` with purpose
+ // ``TALER_SIGNATURE_WALLET_RESERVE_OPEN``.
+ reserve_sig: EddsaSignature;
- interface DenominationExpiredMessage {
+ // Timestamp of the open request.
+ request_timestamp: Timestamp;
- // Taler error code. Note that beyond
- // expiration this message format is also
- // used if the key is not yet valid, or
- // has been revoked.
- code: number;
+ // Requested expiration.
+ requested_expiration: Timestamp;
- // Signature by the exchange over a
- // `TALER_DenominationExpiredAffirmationPS`.
- // Must have purpose ``TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_EXPIRED``.
- exchange_sig: EddsaSignature;
+ // Requested number of free open purses.
+ requested_min_purses: Integer;
- // 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;
+ .. ts:def:: ReserveCloseRequestTransaction
- // When was the signature created.
- timestamp: Timestamp;
+ interface ReserveCloseRequestTransaction {
+ type: "CLOSE";
- // What kind of operation was requested that now
- // failed?
- oper: string;
- }
+ // 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;
+ // Target account ``payto://``, optional.
+ h_payto?: PaytoHash;
+ // Timestamp of the close request.
+ request_timestamp: Timestamp;
+ }
+ .. ts:def:: PurseMergeTransaction
-Withdraw with Age Restriction
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ interface PurseMergeTransaction {
+ type: "MERGE";
-If the reserve was marked with a maximum age group, the client has to perform a
-cut&choose protocol with the exchange. It first calls
-``/reserves/$RESERVE_PUB/age-withdraw`` and commits to ``n*kappa`` coins. On
-success, the exchange answers this request with an noreveal-index. The client
-then has to call ``/age-withdraw/$ACH/reveal`` to reveal all ``n*(kappa - 1)``
-coins along with their age commitments to proof that they were appropriate.
-If so, the exchange will blindly sign ``n`` undisclosed coins from the request.
+ // 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;
-.. http:post:: /reserves/$RESERVE_PUB/age-withdraw
+ // EdDSA public key used to approve merges of this purse.
+ merge_pub: EddsaPublicKey;
- Withdraw multiple coins *with age restriction* from the same reserve.
- Note that the client should commit all of the request details, including the
- private key of the coins and the blinding factors, to disk *before* issuing
- this request, so that it can recover the information if necessary in case of
- transient failures, like power outage, network outage, etc.
+ // Minimum age required for all coins deposited into the purse.
+ min_age: Integer;
- **Request:** The request body must be a `AgeWithdrawRequest` object.
+ // Number that identifies who created the purse
+ // and how it was paid for.
+ flags: Integer;
- **Response:**
+ // Purse public key.
+ purse_pub: EddsaPublicKey;
- :http:statuscode:`200 OK`:
- The request was successful, and the response is a `AgeWithdrawResponse`.
- Note that repeating exactly the same request will again yield the same
- response, so if the network goes down during the transaction or before the
- client can commit the coin signature to disk, the coin is not lost.
- :http:statuscode:`403 Forbidden`:
- A signature is invalid.
- This response comes with a standard `ErrorDetail` response.
- :http:statuscode:`409 Conflict`:
- One of two reasons occured:
-
- 1. The balance of the reserve is not sufficient to withdraw the coins of the
- given amount. The response is a `WithdrawError` object.
-
- 2. The provided value for ``max_age`` is higher than the allowed value according to the reserve's birthday.
- The response comes with a standard `ErrorDetail` response with error-code ``TALER_EC_EXCHANGE_AGE_WITHDRAW_MAXIMUM_AGE_TOO_LARGE`` and an additional field ``maximum_allowed_age`` for the maximum age (in years) that the client can commit to in a call to ``/age-withdraw``
- :http:statuscode:`410 Gone`:
- A requested denomination key is not yet or no longer valid.
- It either before the validity start, past the expiration or was revoked.
- The response is a `DenominationExpiredMessage`. Clients must evaluate the
- error code provided to understand which of the cases this is and handle it
- accordingly.
- :http:statuscode:`451 Unavailable for Legal Reasons`:
- This reserve has received funds from a purse or the amount withdrawn
- exceeds another legal threshold and thus the reserve must
- be upgraded to an account (with KYC) before the withdraw can
- complete. Note that this response does NOT affirm that the
- withdraw will ultimately complete with the requested amount.
- The user should be redirected to the provided location to perform
- the required KYC checks to open the account before withdrawing.
- Afterwards, the request should be repeated.
- The response will be an `LegitimizationNeededResponse` object.
-
- .. ts:def:: AgeWithdrawRequest
-
- interface AgeWithdrawRequest {
- // Array of ``n`` hash codes of denomination public keys to order.
- // These denominations MUST support age restriction as defined in the
- // output to /keys.
- // The sum of all denomination's values and fees MUST be at most the
- // balance of the reserve. The balance of the reserve will be
- // immediatley reduced by that amount.
- denoms_h: HashCode[];
-
- // ``n`` arrays of ``kappa`` entries with blinded coin envelopes. Each
- // (toplevel) entry represents ``kappa`` canditates for a particular
- // coin. The exchange will respond with an index ``gamma``, which is
- // the index that shall remain undisclosed during the reveal phase.
- // The SHA512 hash $ACH over the blinded coin envelopes is the commitment
- // that is later used as the key to the reveal-URL.
- blinded_coins_evs: CoinEnvelope[][];
-
- // The maximum age to commit to. MUST be the same as the maximum
- // age in the reserve.
- max_age: number;
-
- // Signature of `TALER_AgeWithdrawRequestPS` created with
- // the `reserves's private key <reserve-priv>`
- // using purpose ``TALER_SIGNATURE_WALLET_RESERVE_AGE_WITHDRAW``.
+ // EdDSA signature of the account/reserve affirming the merge
+ // over a `TALER_AccountMergeSignaturePS`.
+ // Must be of purpose ``TALER_SIGNATURE_ACCOUNT_MERGE``
reserve_sig: EddsaSignature;
- }
-
- .. ts:def:: AgeWithdrawResponse
-
- interface AgeWithdrawResponse {
- // index of the commitments that the client doesn't
- // have to disclose
- noreveal_index: Integer;
-
- // Signature of `TALER_AgeWithdrawConfirmationPS` 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;
- }
-
-
-
-.. http:post:: /age-withdraw/$ACH/reveal
-
- The client has previously committed to multiple coins with age restriction
- in a call to ``/reserve/$RESERVE_PUB/age-withdraw`` and got a
- `AgeWithdrawResponse` from the exchange. By calling this
- endpoint, the client has to reveal each coin and their ``kappa - 1``
- age commitments, except for the age commitments with index
- ``noreveal_index``. The hash of all commitments from the former withdraw
- request is given as the ``$ACH`` value in the URL to this endpoint.
-
-
- **Request:** The request body must be a `AgeWithdrawRevealRequest` object.
-
- **Response:**
-
- :http:statuscode:`200 OK`:
- The request was successful, and the response is a `AgeWithdrawRevealResponse`.
- Note that repeating exactly the same request will again yield the same
- response, so if the network goes down during the transaction or before the
- client can commit the coin signature to disk, the coin is not lost.
- :http:statuscode:`404 Not found`:
- The provided commitment $ACH is unknown.
- :http:statuscode:`409 Conflict`:
- The reveal operation failed and the response is an `WithdrawError` object.
- The error codes indicate one of two cases:
-
- 1. An age commitment for at least one of the coins did not fulfill the
- required maximum age requirement of the corresponding reserve.
- Error code:
- ``TALER_EC_EXCHANGE_GENERIC_COIN_AGE_REQUIREMENT_FAILURE``.
- 2. The computation of the hash of the commitment with provided input does
- result in the value $ACH.
- Error code:
- ``TALER_EC_EXCHANGE_AGE_WITHDRAW_REVEAL_INVALID_HASH``
-
-
- .. ts:def:: AgeWithdrawRevealRequest
-
- interface AgeWithdrawRevealRequest {
- // Array of ``n`` of ``(kappa - 1)`` disclosed coin master secrets, from
- // which the coins' private key, blinding, nonce (for Clause-Schnorr) and
- // age-restriction is calculated.
- //
- // Given each coin's private key and age commitment, the exchange will
- // calculate each coin's blinded hash value und use all those (disclosed)
- // blinded hashes together with the non-disclosed envelopes ``coin_evs``
- // during the verification of the original age-withdraw-commitment.
- disclosed_coin_secrets: AgeRestrictedCoinSecret[][];
- }
-
- .. ts:def:: AgeRestrictedCoinSecret
- // The Master key material from which the coins' private key ``coin_priv``,
- // blinding ``beta`` and nonce ``nonce`` (for Clause-Schnorr) itself are
- // derived as usually in wallet-core. Given a coin's master key material,
- // the age commitment for the coin MUST be derived from this private key as
- // follows:
- //
- // Let m ∈ {1,...,M} be the maximum age group as defined in the reserve
- // that the wallet can commit to.
- //
- // For age group $AG ∈ {1,...m}, set
- // seed = HDKF(coin_secret, "age-commitment", $AG)
- // p[$AG] = Edx25519_generate_private(seed)
- // and calculate the corresponding Edx25519PublicKey as
- // q[$AG] = Edx25519_public_from_private(p[$AG])
- //
- // For age groups $AG ∈ {m,...,M}, set
- // f[$AG] = HDKF(coin_secret, "age-factor", $AG)
- // and calculate the corresponding Edx25519PublicKey as
- // q[$AG] = Edx25519_derive_public(`PublishedAgeRestrictionBaseKey`, f[$AG])
- //
- type AgeRestrictedCoinSecret = string;
+ // Client-side timestamp of when the merge request was made.
+ merge_timestamp: Timestamp;
- .. ts:def:: PublishedAgeRestrictionBaseKey
+ // 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;
- // The value for ``PublishedAgeRestrictionBaseKey`` is a randomly chosen
- // `Edx25519PublicKey` for which the private key is not known to the clients. It is
- // used during the age-withdraw protocol so that clients can proof that they
- // derived all public keys to age groups higher than their allowed maximum
- // from this particular value.
- const PublishedAgeRestrictionBaseKey =
- new Edx25519PublicKey("CH0VKFDZ2GWRWHQBBGEK9MWV5YDQVJ0RXEE0KYT3NMB69F0R96TG");
+ // Purse fee the reserve owner paid for the purse creation.
+ purse_fee: Amount;
- .. ts:def:: AgeWithdrawRevealResponse
+ // Total amount merged into the reserve.
+ // (excludes fees).
+ amount: Amount;
- interface AgeWithdrawRevealResponse {
- // List of the exchange's blinded RSA signatures on the new coins.
- ev_sigs : BlindedDenominationSignature[];
+ // True if the purse was actually merged.
+ // If false, only the purse_fee has an impact
+ // on the reserve balance!
+ merged: boolean;
}
-.. _reserve-history:
+.. _coin-history:
----------------
-Reserve History
----------------
+------------
+Coin History
+------------
-.. http:get:: /reserves/$RESERVE_PUB/history
+.. http:get:: /coins/$COIN_PUB/history
- Request information about the full history of
- a reserve or an account.
+ 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:**
@@ -2421,461 +2247,332 @@ Reserve History
*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.
+ status code in case the coin 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
+ *Taler-Coin-History-Signature*: The client MUST provide Base-32 encoded
+ EdDSA signature over a TALER_SIGNATURE_COIN_HISTORY_REQUEST made with
the respective ``$RESERVE_PRIV``, affirming desire to download the current
- reserve transaction history.
+ coin transaction history.
- :query start=OFFSET: *Optional.* Only return reserve history entries with
+ :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 exchange responds with a `ReserveHistory` object; the reserve was known to the exchange.
+ 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 reserve history matches the one identified by the "If-none-match" HTTP header of the request.
+ 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_RESERVE_HISTORY_REQUEST* is invalid.
+ The *TALER_SIGNATURE_COIN_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:**
+ The coin public key is not (yet) known to the exchange.
- .. ts:def:: ReserveHistory
+ .. ts:def:: CoinHistoryResponse
- interface ReserveHistory {
- // Balance left in the reserve.
+ interface CoinHistoryResponse {
+ // Current balance of the coin.
balance: Amount;
- // If set, gives the maximum age group that the client is required to set
- // during withdrawal.
- maximum_age_group: number;
+ // Hash of the coin's denomination.
+ h_denom_pub: HashCode;
- // Transaction history for this reserve.
- // May be partial (!).
- history: TransactionHistoryItem[];
+ // Transaction history for the coin.
+ history: CoinSpendHistoryItem[];
}
- Objects in the transaction history have the following format:
-
- .. ts:def:: TransactionHistoryItem
+ .. ts:def:: CoinSpendHistoryItem
// Union discriminated by the "type" field.
- type TransactionHistoryItem =
- | AccountSetupTransaction
- | ReserveWithdrawTransaction
- | ReserveAgeWithdrawTransaction
- | ReserveCreditTransaction
- | ReserveClosingTransaction
- | ReserveOpenRequestTransaction
- | ReserveCloseRequestTransaction
- | PurseMergeTransaction;
-
- .. ts:def:: AccountSetupTransaction
-
- interface AccountSetupTransaction {
- type: "SETUP";
-
+ type CoinSpendHistoryItem =
+ | CoinDepositTransaction
+ | CoinMeltTransaction
+ | CoinRefundTransaction
+ | CoinRecoupTransaction
+ | CoinOldCoinRecoupTransaction
+ | CoinRecoupRefreshTransaction
+ | 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;
- // 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;
+ // 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;
- // Signature created with the reserve's private key.
- // Must be of purpose ``TALER_SIGNATURE_ACCOUNT_SETUP_REQUEST`` over
- // a ``TALER_AccountSetupRequestSignaturePS``.
- reserve_sig: EddsaSignature;
+ // Deposit fee.
+ deposit_fee: Amount;
- }
+ // Public key of the merchant.
+ merchant_pub: EddsaPublicKey;
- .. ts:def:: ReserveWithdrawTransaction
+ // Date when the operation was made.
+ timestamp: Timestamp;
- interface ReserveWithdrawTransaction {
- type: "WITHDRAW";
+ // 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;
- // Offset of this entry in the reserve history.
- // Useful to request incremental histories via
- // the "start" query parameter.
- history_offset: Integer;
+ // Signature over `TALER_DepositRequestPS`, made by the customer with the
+ // `coin's private key <coin-priv>`.
+ coin_sig: EddsaSignature;
- // Amount withdrawn.
- amount: Amount;
+ // Hash of the bank account from where we received the funds.
+ h_wire: HashCode;
- // Hash of the denomination public key of the coin.
+ // 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 of the blinded coin to be signed.
- h_coin_envelope: HashCode;
-
- // Signature over a `TALER_WithdrawRequestPS`
- // with purpose ``TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW``
- // created with the reserve's private key.
- reserve_sig: EddsaSignature;
+ // Hash over the proposal data of the contract that
+ // is being paid.
+ h_contract_terms: HashCode;
- // Fee that is charged for withdraw.
- withdraw_fee: Amount;
- }
+ }
- .. ts:def:: ReserveAgeWithdrawTransaction
+ .. ts:def:: CoinMeltTransaction
- interface ReserveAgeWithdrawTransaction {
- type: "AGEWITHDRAW";
+ 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;
- // Total Amount withdrawn.
+ // 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;
- // Commitment of all ``n*kappa`` blinded coins.
- h_commitment: HashCode;
+ // Signature by the coin over a
+ // `TALER_RefreshMeltCoinAffirmationPS` of
+ // purpose ``TALER_SIGNATURE_WALLET_COIN_MELT``.
+ coin_sig: EddsaSignature;
- // Signature over a `TALER_AgeWithdrawRequestPS`
- // with purpose ``TALER_SIGNATURE_WALLET_RESERVE_AGE_WITHDRAW``
- // created with the reserve's private key.
- reserve_sig: EddsaSignature;
+ // Melt fee.
+ melt_fee: Amount;
- // Fee that is charged for withdraw.
- withdraw_fee: Amount;
- }
+ // Commitment from the melt operation.
+ rc: TALER_RefreshCommitmentP;
+
+ // 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:: ReserveCreditTransaction
+ .. ts:def:: CoinRefundTransaction
- interface ReserveCreditTransaction {
- type: "CREDIT";
+ 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;
- // Amount deposited.
+ // 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;
- // Sender account ``payto://`` URL.
- sender_account_url: string;
+ // Refund fee.
+ refund_fee: Amount;
- // Opaque identifier internal to the exchange that
- // uniquely identifies the wire transfer that credited the reserve.
- wire_reference: Integer;
+ // Hash over the proposal data of the contract that
+ // is being refunded.
+ h_contract_terms: HashCode;
- // Timestamp of the incoming wire transfer.
- timestamp: Timestamp;
- }
+ // 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;
+ }
- .. ts:def:: ReserveClosingTransaction
+ .. ts:def:: CoinRecoupTransaction
- interface ReserveClosingTransaction {
- type: "CLOSING";
+ interface CoinRecoupTransaction {
+ type: "RECOUP";
// Offset of this entry in the reserve history.
// Useful to request incremental histories via
// the "start" query parameter.
history_offset: Integer;
- // Closing balance.
+ // 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;
- // Closing fee charged by the exchange.
- closing_fee: Amount;
+ // Date when the operation was made.
+ timestamp: Timestamp;
- // Wire transfer subject.
- wtid: Base32;
+ // Signature by the coin over a
+ // `TALER_RecoupRequestPS` with purpose
+ // ``TALER_SIGNATURE_WALLET_COIN_RECOUP``.
+ coin_sig: EddsaSignature;
- // ``payto://`` URI of the wire account into which the funds were returned to.
- receiver_account_details: string;
+ // 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;
- // This is a signature over a
- // struct `TALER_ReserveCloseConfirmationPS` with purpose
- // ``TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED``.
+ // Coin blinding key.
+ coin_blind: DenominationBlindingKeyP;
+
+ // Reserve receiving the recoup.
+ reserve_pub: EddsaPublicKey;
+
+ // Signature by the exchange over a
+ // `TALER_RecoupConfirmationPS`, must be
+ // of purpose ``TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP``.
exchange_sig: EddsaSignature;
- // Public key used to create 'exchange_sig'.
+ // Public key of the private key used to create 'exchange_sig'.
exchange_pub: EddsaPublicKey;
- // Time when the reserve was closed.
- timestamp: Timestamp;
}
+ .. ts:def:: CoinOldCoinRecoupTransaction
- .. ts:def:: ReserveOpenRequestTransaction
-
- interface ReserveOpenRequestTransaction {
- type: "OPEN";
+ interface CoinOldCoinRecoupTransaction {
+ type: "OLD-COIN-RECOUP";
// Offset of this entry in the reserve history.
// Useful to request incremental histories via
// the "start" query parameter.
history_offset: Integer;
- // 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;
+ // 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;
- // Timestamp of the open request.
- request_timestamp: Timestamp;
+ // Date when the operation was made.
+ timestamp: Timestamp;
- // Requested expiration.
- requested_expiration: Timestamp;
+ // Signature by the exchange over a
+ // `TALER_RecoupRefreshConfirmationPS`
+ // of purpose ``TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_REFRESH``.
+ exchange_sig: EddsaSignature;
- // Requested number of free open purses.
- requested_min_purses: Integer;
+ // Public key of the private key used to create 'exchange_sig'.
+ exchange_pub: EddsaPublicKey;
}
- .. ts:def:: ReserveCloseRequestTransaction
+ .. ts:def:: CoinRecoupRefreshTransaction
- interface ReserveCloseRequestTransaction {
- type: "CLOSE";
+ 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;
- // This is a signature over
- // a struct `TALER_ReserveClosePS` with purpose
- // ``TALER_SIGNATURE_WALLET_RESERVE_CLOSE``.
- reserve_sig: EddsaSignature;
+ // 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;
- // Target account ``payto://``, optional.
- h_payto?: PaytoHash;
+ // Date when the operation was made.
+ timestamp: Timestamp;
- // Timestamp of the close request.
- request_timestamp: Timestamp;
- }
+ // Signature by the coin over a `TALER_RecoupRequestPS`
+ // with purpose ``TALER_SIGNATURE_WALLET_COIN_RECOUP``.
+ coin_sig: EddsaSignature;
- .. ts:def:: PurseMergeTransaction
+ // 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 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 ``$RESERVE_PRIV``, affirming desire to download the current
- coin transaction history.
-
- :query start=OFFSET: *Optional.* Only return coin history entries with
- offsets above the given OFFSET. Allows clients to not
- retrieve history entries they already have.
-
-
- **Response:**
-
- :http:statuscode:`200 OK`:
- The operation succeeded, the exchange confirms that no double-spending took
- place. The response will include a `CoinHistoryResponse` object.
- :http:statuscode:`204 No content`:
- The reserve history is known, but at this point from the given starting point it is empty. Can only happen if OFFSET was positive.
- :http:statuscode:`304 Not modified`:
- The coin history has not changed since the previous query (detected via Etag
- in "If-none-match" header).
- :http:statuscode:`403 Forbidden`:
- The *TALER_SIGNATURE_COIN_HISTORY_REQUEST* is invalid.
- This response comes with a standard `ErrorDetail` response.
- :http:statuscode:`404 Not found`:
- The coin public key is not (yet) known to the exchange.
+ // Coin blinding key.
+ coin_blind: DenominationBlindingKeyP;
- .. ts:def:: CoinHistoryResponse
+ // Signature by the exchange over a
+ // `TALER_RecoupRefreshConfirmationPS`
+ // of purpose ``TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_REFRESH``.
+ exchange_sig: EddsaSignature;
- interface CoinHistoryResponse {
- // Current balance of the coin.
- balance: Amount;
+ // Public key used to sign 'exchange_sig'.
+ exchange_pub: EddsaPublicKey;
- // Hash of the coin's denomination.
- h_denom_pub: HashCode;
+ // Blinding factor of the revoked new coin.
+ new_coin_blinding_secret: DenominationBlindingKeySecret;
- // Transaction history for the coin.
- history: CoinSpendHistoryItem[];
+ // Blinded public key of the revoked new coin.
+ new_coin_ev: DenominationBlindingKeySecret;
}
- .. ts:def:: CoinSpendHistoryItem
-
- // Union discriminated by the "type" field.
- type CoinSpendHistoryItem =
- | CoinDepositTransaction
- | CoinMeltTransaction
- | CoinRefundTransaction
- | CoinRecoupTransaction
- | CoinOldCoinRecoupTransaction
- | CoinRecoupRefreshTransaction
- | CoinPurseDepositTransaction
- | CoinPurseRefundTransaction
- | CoinReserveOpenDepositTransaction;
-
- .. ts:def:: CoinDepositTransaction
+ .. ts:def:: CoinPurseDepositTransaction
- interface CoinDepositTransaction {
- type: "DEPOSIT";
+ 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 (or restored in the
- // case of a refund) by this transaction.
- // The amount given includes
+ // 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 this amount.
+ // subtracting the amount from
+ // the coin's denomination value.
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;
-
- // Signature over `TALER_DepositRequestPS`, made by the customer with the
- // `coin's private key <coin-priv>`.
- coin_sig: EddsaSignature;
-
- // 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 proposal data of the contract that
- // is being paid.
- h_contract_terms: HashCode;
-
- }
-
- .. 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;
+ // Public key of the purse.
+ purse_pub: EddsaPublicKey;
- // 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;
+ // Date when the purse was set to expire.
+ purse_expiration: Timestamp;
// Signature by the coin over a
- // `TALER_RefreshMeltCoinAffirmationPS` of
- // purpose ``TALER_SIGNATURE_WALLET_COIN_MELT``.
+ // `TALER_PurseDepositSignaturePS` of
+ // purpose ``TALER_SIGNATURE_PURSE_DEPOSIT``.
coin_sig: EddsaSignature;
- // Melt fee.
- melt_fee: Amount;
-
- // Commitment from the melt operation.
- rc: TALER_RefreshCommitmentP;
-
// 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.
@@ -2883,10 +2580,10 @@ Coin History
}
- .. ts:def:: CoinRefundTransaction
+ .. ts:def:: CoinPurseRefundTransaction
- interface CoinRefundTransaction {
- type: "REFUND";
+ interface CoinPurseRefundTransaction {
+ type: "PURSE-REFUND";
// Offset of this entry in the reserve history.
// Useful to request incremental histories via
@@ -2895,33 +2592,32 @@ Coin History
// The total amount of the coin's value restored
// by this transaction.
- // The amount given excludes the transaction fee.
+ // The amount given excludes the refund fee.
// The current coin value can thus be computed by
- // adding the amounts to the coin's denomination value.
+ // adding the amount to the coin's denomination value.
amount: Amount;
- // Refund fee.
+ // Refund fee (of the coin's denomination). The deposit
+ // fee will be waived.
refund_fee: Amount;
- // Hash over the proposal data of the contract that
- // is being refunded.
- h_contract_terms: HashCode;
+ // Public key of the purse that expired.
+ purse_pub: EddsaPublicKey;
- // Refund transaction ID.
- rtransaction_id: Integer;
+ // Signature by the exchange over a
+ // ``TALER_CoinPurseRefundConfirmationPS``
+ // of purpose ``TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_REFUND``.
+ exchange_sig: EddsaSignature;
- // `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;
+ // Public key used to sign 'exchange_sig'.
+ exchange_pub: EddsaPublicKey;
}
- .. ts:def:: CoinRecoupTransaction
+ .. ts:def:: CoinReserveOpenDepositTransaction
- interface CoinRecoupTransaction {
- type: "RECOUP";
+ interface CoinReserveOpenDepositTransaction {
+ type: "RESERVE-OPEN-DEPOSIT";
// Offset of this entry in the reserve history.
// Useful to request incremental histories via
@@ -2930,211 +2626,16 @@ Coin History
// 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;
+ // Note that this means the amount given includes
+ // the deposit fee.
+ coin_contribution: Amount;
- // Date when the operation was made.
- timestamp: Timestamp;
+ // Signature of the reserve open operation being paid for.
+ reserve_sig: EddsaSignature;
// 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.
- coin_blind: DenominationBlindingKeyP;
-
- // Reserve receiving the recoup.
- reserve_pub: EddsaPublicKey;
-
- // 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;
-
- }
-
- .. ts:def:: CoinOldCoinRecoupTransaction
-
- interface CoinOldCoinRecoupTransaction {
- type: "OLD-COIN-RECOUP";
-
- // Offset of this entry in the reserve history.
- // Useful to request incremental histories via
- // the "start" query parameter.
- history_offset: Integer;
-
- // The total amount of the coin's value restored
- // by this transaction.
- // The current coin value can thus be computed by
- // 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:: CoinRecoupRefreshTransaction
-
- 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;
-
- // Date when the operation was made.
- timestamp: Timestamp;
-
- // 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.
- coin_blind: DenominationBlindingKeyP;
-
- // 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;
-
- // Blinding factor of the revoked new coin.
- new_coin_blinding_secret: DenominationBlindingKeySecret;
-
- // Blinded public key of the revoked new coin.
- new_coin_ev: DenominationBlindingKeySecret;
- }
-
- .. 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;
-
- // Deposit fee.
- deposit_fee: Amount;
-
- // Public key of the purse.
- purse_pub: EddsaPublicKey;
-
- // Date when the purse was set to expire.
- purse_expiration: Timestamp;
-
- // 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;
-
- // Public key of the purse that expired.
- purse_pub: EddsaPublicKey;
-
- // 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;
-
- }
-
- .. 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``.
+ // `TALER_ReserveOpenDepositSignaturePS` of
+ // purpose ``TALER_SIGNATURE_RESERVE_OPEN_DEPOSIT``.
coin_sig: EddsaSignature;
}
@@ -5386,180 +4887,675 @@ regulatory compliance.
// basically encode any URL path (and optional arguments).
id?: string;
- }
+ }
+
+ .. ts:def:: KycCheckPublicInformation
+
+ // Since **vATTEST**.
+ interface KycCheckPublicInformation {
+
+ // English description of the check.
+ description: string;
+
+ // Map from IETF BCP 47 language tags to localized
+ // description texts.
+ description_i18n ?: { [lang_tag: string]: string };
+
+ // FIXME: is the above in any way sufficient
+ // to begin the check? Do we not need at least
+ // something more??!?
+ }
+
+
+.. 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:**
+
+ Basically oriented along the possible formats of a HTTP form being
+ POSTed. Details will depend on the form. The server will try to decode the
+ uploaded body from whatever format it is provided in.
+
+ **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 **vATTEST**).
+
+.. http:get:: /kyc-proof/$PROVIDER_NAME?state=$H_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_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_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; };
+
+ }
+
+ .. ts:def:: MeasureInformation
+
+ interface MeasureInformation {
+
+ // Name of a KYC check.
+ check_name: string;
+
+ // Name of an AML program.
+ prog_name: string;
+
+ // Context for the check. Optional.
+ context?: Object;
+
+ }
+
+ .. 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/$NAME
+
+ Returns the number of KYC events matching the given event type ``$NAME`` 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**.
+
+ **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 `EventCounter` message.
+
+ **Details:**
+
+ .. ts:def:: EventCounter
+
+ interface EventCounter {
+ // 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. 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 superceeded. 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: PaytoHash;
+
+ // 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.
+ 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.
+ operation_type: string;
- .. ts:def:: KycCheckPublicInformation
+ // The measures will be taken if the given
+ // threshold is crossed over the given timeframe.
+ threshold: Amount;
- // Since **vATTEST**.
- interface KycCheckPublicInformation {
+ // 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;
- // English description of the check.
- description: string;
+ // 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[];
- // Map from IETF BCP 47 language tags to localized
- // description texts.
- description_i18n ?: { [lang_tag: string]: 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;
- // FIXME: is the above in any way sufficient
- // to begin the check? Do we not need at least
- // something more??!?
- }
+ // 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;
+ }
-.. http:post:: /kyc-upload/$ID
+.. http:get:: /aml/$OFFICER_PUB/attributes/$H_PAYTO
- 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).
+ Obtain attributes obtained as part of AML/KYC processes for a
+ given account.
This endpoint was introduced in protocol **v20**.
**Request:**
- Basically oriented along the possible formats of a HTTP form being
- POSTed. Details will depend on the form. The server will try to decode the
- uploaded body from whatever format it is provided in.
+ *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:`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:`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 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).
+ The designated AML account is not enabled.
- **Request:**
+ .. ts:def:: KycAttributes
- Use empty JSON body for now.
+ interface KycAttributes {
- **Response:**
+ // Matching KYC attribute history of the account.
+ details: KycAttributeCollectionEvent[];
- :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:: KycAttributeCollectionEvent
- .. ts:def:: KycProcessStartInformation
+ interface KycAttributeCollectionEvent {
- interface KycProcessStartInformation {
+ // Row ID of the record. Used to filter by offset.
+ rowid: Integer;
- // URL to open.
- redirect_url: string;
- }
+ // Name of the provider
+ // which was used to collect the attributes. NULL if they were
+ // just uploaded via a form by the account owner.
+ provider_name?: string;
- .. note::
+ // 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?: Object;
- 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 **vATTEST**).
+ // Time when the KYC data was collected
+ collection_time: Timestamp;
-.. http:get:: /kyc-proof/$PROVIDER_NAME?state=$H_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_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".
+ .. http:post:: /aml/$OFFICER_PUB/decision
- 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.
+ Make an AML decision. Triggers the respective action and
+ records the justification.
**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:
+ The request must be an `AmlDecisionRequest` message.
- :query code=CODE:
- OAuth 2.0 code argument.
- :query state=STATE:
- OAuth 2.0 state argument with the H_PAYTO.
+ **Response**
- .. note::
+ :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 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.
- Depending on the OAuth variant used, additional
- query parameters may need to be passed here.
+ **Details:**
- **Response:**
+ .. ts:def:: AmlDecisionRequest
- 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.
+ interface AmlDecisionRequest {
- :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.
+ // Human-readable justification for the decision.
+ justification: string;
+ // Which payto-address is the decision about?
+ // Identifies a GNU Taler wallet or an affected bank account.
+ h_payto: PaytoHash;
-.. http:get:: /kyc-webhook/$PROVIDER_NAME/*
-.. http:post:: /kyc-webhook/$PROVIDER_NAME/*
-.. http:get:: /kyc-webhook/$LOGIC/*
-.. http:post:: /kyc-webhook/$LOGIC/*
+ // What are the new rules?
+ // New since protocol **v20**.
+ new_rules: LegitimizationRuleSet;
- 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".
+ // What are the new account properties?
+ // New since protocol **v20**.
+ properties: AccountProperties;
- **Request:**
+ // New measure to apply immediately to the account.
+ // Should typically be used to give the user some
+ // information or request additional information.
+ // Use "verboten" to communicate to the customer
+ // that there is no KYC check that could be passed
+ // to modify the ``new_rules``.
+ // New since protocol **v20**.
+ new_measure?: string;
- Details on the request depend on the specific KYC logic
- that was used.
+ // True if the account should remain under investigation by AML staff.
+ // New since protocol **v20**.
+ keep_investigating: boolean;
- **Response:**
+ // Signature by the AML officer over a `TALER_AmlDecisionPS`.
+ // Must have purpose ``TALER_SIGNATURE_MASTER_AML_KEY``.
+ officer_sig: EddsaSignature;
- :http:statuscode:`204 No content`:
- The operation succeeded.
- :http:statuscode:`404 Not found`:
- The specified logic is unknown.
+ // When was the decision made?
+ decision_time: Timestamp;
+ }
---------------
Reserve control
diff --git a/core/api-merchant.rst b/core/api-merchant.rst
@@ -24,9 +24,6 @@
Merchant Backend RESTful API
============================
-.. contents:: Table of Contents
- :local:
-
-----------------------
Base URLs and Instances
-----------------------
diff --git a/core/api-terminal.rst b/core/api-terminal.rst
@@ -22,8 +22,6 @@
Terminal API
============
-.. contents:: Table of Contents
- :local:
Introduction
------------
@@ -75,7 +73,7 @@ Config
provider_name: string;
// The currency supported by this Terminal-API
- // must be the same as the currency specified
+ // must be the same as the currency specified
// in the currency field of the wire gateway config
currency: string;
@@ -286,7 +284,7 @@ Config
// backend to check that the credit was confirmed.
provider_transaction_id?: string;
- // The fees which the customer had to pay to the
+ // The fees which the customer had to pay to the
// provider
terminal_fees?: Amount;
diff --git a/index.rst b/index.rst
@@ -60,12 +60,13 @@ Documentation Overview
taler-merchant-pos-terminal
taler-wallet
taler-exchange-manual
+ taler-kyc-manual
taler-challenger-manual
taler-auditor-manual
- taler-developer-manual
libeufin/index
- system-administration/index
design-documents/index
+ taler-developer-manual
+ system-administration/index
global-licensing
manindex
genindex
diff --git a/taler-exchange-manual.rst b/taler-exchange-manual.rst
@@ -944,324 +944,6 @@ Such a wire gateway configuration can be tested with the following commands:
On success, you will see some of your account's transaction history (or an
empty history), while on failure you should see an error message.
-.. _LegalSetup:
-
-Legal Setup
-===========
-
-This chapter describes how to setup certain legal aspects of a GNU Taler
-exchange. Users that just want to set up an exchange as an experiment without
-legal requirements can safely skip these steps.
-
-
-Legal conditions for using the service
---------------------------------------
-
-.. include:: frags/legal.rst
-
-
-KYC Configuration
-=================
-
-To legally operate, Taler exchange operators may have to comply with KYC
-regulation that requires financial institutions to identify parties involved
-in transactions at certain points.
-
-Taler permits an exchange to require KYC data under the following circumstances:
-
- * Customer withdraws money over a threshold
- * Wallet receives (via refunds) money resulting in a balance over a threshold
- * Wallet receives money via P2P payments over a threshold
- * Merchant receives money over a threshold
- * Reserve is "opened" for invoicing (**planned feature**)
-
-Any of the above requests can trigger the KYC process,
-which can be illustrated as follows:
-
-.. image:: images/kyc-process.png
-
-At the end of the KYC process, the wallet re-tries the
-original request, and assuming KYC was successful, the
-request should then succeed.
-
-
-Taler KYC Terminology
----------------------
-
-* **Check**: A check establishes a particular attribute of a user, such as
- their name based on an ID document and lifeness, mailing address, phone
- number, taxpayer identity, etc.
-
-* **Type of operation**: The operation type determines which Taler-specific
- operation has triggered the KYC requirement. We support four types of
- operation: withdraw (by customer), deposit (by merchant), P2P receive (by
- wallet) and (high) wallet balance.
-
-* **Condition**: A condition specifies when KYC is required. Conditions
- include the *type of operation*, a threshold amount (e.g. above EUR:1000)
- and possibly a time period (e.g. over the last month).
-
-* **Cost**: Metric for the business expense for a KYC check at a certain
- *provider*. Not in any currency, costs are simply relative and non-negative
- values. Costs are considered when multiple choices are allowed by the
- *configuration*.
-
-* **Expiration**: KYC legitimizations may be outdated. Expiration rules
- determine when *checks* have to be performed again.
-
-* **Legitimization rules**: The legitimization rules determine under which
- *conditions* which *checks* must be performend and the *expiration* time
- period for the *checks*.
-
-* **Logic**: Logic refers to a specific bit of code (realized as an exchange
- plugin) that enables the interaction with a specific *provider*. Logic
- typically requires configuration for access control (such as an
- authorization token) and possibly the endpoint of the specific *provider*
- implementing the respective API.
-
-* **Provider**: A provider performs a specific set of *checks* at a certain
- *cost*. Interaction with a provider is performed by provider-specific
- *logic*.
-
-Configuration of possible KYC/AML checks
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-The configuration specifies a set of possible KYC checks offered by external
-providers. The names of the configuration sections must being with
-``kyc-check-`` followed by an arbitrary ``$CHECK_NAME``.
-
-The list of possible FORM names is fixed in the SPA
-for a particular exchange release.
-
-The outcome of *any* check should always be uploaded encrypted into the
-``kyc_attributes`` table. It MUST include an ``expiration_time``.
-
-
-
-KYC Configuration Options
--------------------------
-
-The KYC configuration determines the *legitimization rules*, and specifies
-which providers offer which *checks* at what *cost*.
-
-The configuration specifies a set of providers, one per configuration section. The names of the configuration
-sections must being with ``kyc-proider-`` followed by
-an arbitrary ``$PROVIDER_ID``:
-
-.. code-block:: ini
- :caption: /etc/taler/conf.d/exchange-kyc-providers.conf
-
- [kyc-provider-$PROVIDER_ID]
- # How expensive is it to use this provider?
- # Used to pick the cheapest provider possible.
- COST = 42
- # Which plugin is responsible for this provider?
- # Choices include "oauth2", "kycaid" and "persona".
- LOGIC = oauth2
- # Which type of user does this provider handle?
- # Either INDIVIDUAL or BUSINESS.
- USER_TYPE = INDIVIDUAL
- # Which checks does this provider provide?
- # List of strings, no specific semantics.
- PROVIDED_CHECKS = SMS GOVID PHOTO
- # Plus additional logic-specific options, e.g.:
- AUTHORIZATION_TOKEN = superdupersecret
- FORM_ID = business_legi_form
- # How long is the check considered valid?
- EXPIRATION = 3650d
-
-The configuration also must specify a set of legitimization requirements, again one
-per configuration section:
-
-.. code-block:: ini
- :caption: /etc/taler/conf.d/exchange-kyc-rules.conf
-
- [kyc-legitimization-$RULE_NAME]
- # Operation that triggers this legitimization.
- # Must be one of WITHDRAW, DEPOSIT, P2P-RECEIVE
- # or WALLET-BALANCE.
- OPERATION_TYPE = WITHDRAW
- # Required checks to be performed.
- # List of strings, must individually match the
- # strings in one or more provider's PROVIDED_CHECKS.
- REQUIRED_CHECKS = SMS GOVID
- # Threshold amount above which the legitimization is
- # triggered. The total must be exceeded in the given
- # timeframe.
- THRESHOLD = KUDOS:100
- # Timeframe over which the amount to be compared to
- # the THRESHOLD is calculated. Can be 'forever'.
- # Ignored for WALLET-BALANCE.
- TIMEFRAME = 30d
-
-
-OAuth 2.0 specifics
--------------------
-
-In terms of configuration, the OAuth 2.0 logic requires the respective client
-credentials to be configured apriori to enable access to the legitimization
-service. The OAuth 2.0 configuration options are:
-
-.. code-block:: ini
- :caption: /etc/taler/conf.d/exchange-oauth2.conf
-
- [kyc-provider-example-oauth2]
- LOGIC = oauth2
- # (generic options omitted)
- # How long is the KYC check valid?
- KYC_OAUTH2_VALIDITY = forever
-
- # URL to which we redirect the user for the login process
- KYC_OAUTH2_AUTHORIZE_URL = "http://kyc.example.com/authorize"
- # URL where we POST the user's authentication information
- KYC_OAUTH2_TOKEN_URL = "http://kyc.example.com/token"
- # URL of the user info access point.
- KYC_OAUTH2_INFO_URL = "http://kyc.example.com/info"
-
- # Where does the client get redirected upon completion?
- KYC_OAUTH2_POST_URL = "http://example.com/thank-you"
-
- # For authentication to the OAuth2.0 service
- KYC_OAUTH2_CLIENT_ID = testcase
- KYC_OAUTH2_CLIENT_SECRET = password
-
- # Mustach template that converts OAuth2.0 data about the user
- # into GNU Taler standardized attribute data.
- KYC_OAUTH2_CONVERTER_HELPER = taler-exchange-kyc-oauth2-challenger.sh
-
-The converter helper is expected to be customized to the selected OAuth2.0
-service: different services may return different details about the user or
-business, hence there cannot be a universal converter for all purposes. The
-default shell script uses the ``jq`` tool to convert the JSON returned by the
-service into the KYC attributes (also in JSON) expected by the exchange. The
-script will need to be adjusted based on the attributes collected by the
-specific backend.
-
-The Challenger service for address validation supports OAuth2.0, but does not
-have a static AUTHORIZE_URL. Instead, the AUTHORIZE_URL must be enabled by the client
-using a special authenticated request to the Challenger's ``/setup`` endpoint.
-The exchange supports this by appending ``#setup`` to the AUTHORIZE_URL (note
-that fragments are illegal in OAuth2.0 URLs). Be careful to quote the URL,
-as ``#`` is otherwise interpreted as the beginning of a comment by the
-configuration file syntax.
-
-.. code-block:: ini
- :caption: /etc/taler/conf.d/exchange-challenger-oauth2.conf
-
- [kyc-provider-challenger-oauth2]
- LOGIC = oauth2
- KYC_OAUTH2_AUTHORIZE_URL = "http://challenger.example.com/authorize/#setup"
- KYC_OAUTH2_TOKEN_URL = "http://challenger.example.com/token"
- KYC_OAUTH2_INFO_URL = "http://challenger.example.com/info"
-
-When using OAuth 2.0, the *CLIENT REDIRECT URI* must be set to the
-``/kyc-proof/$PROVIDER_SECTION`` endpoint. For example, given the
-configuration above and an exchange running on the host
-``exchange.example.com``, the redirect URI would be
-``https://exchange.example.com/kyc-proof/kyc-provider-challenger-oauth2/``.
-
-
-
-Persona specifics
------------------
-
-We use the hosted flow. The Persona endpoints return a ``request-id``, which
-we log for diagnosis.
-
-Persona should be configured to use the ``/kyc-webhook/`` endpoint of the
-exchange to notify the exchange about the completion of KYC processes. The
-webhook is authenticated using a shared secret, which should be in the
-configuration. To use the Persona webhook, you must set the webhook URL in
-the Persona service to ``$EXCHANGE_BASE_URL/kyc-webhook/$SECTION_NAME/`` where
-``$SECTION_NAME`` is the name of the configuration section. You should also
-extract the authentication token for the webhook and put it into the
-configuration as shown above.
-
-
-.. code-block:: ini
- :caption: /etc/taler/conf.d/exchange-persona.conf
-
- [kyclogic-persona]
- # Webhook authorization token. Global for all uses
- # of the persona provider!
- WEBHOOK_AUTH_TOKEN = wbhsec_698b5a19-c790-47f6-b396-deb572ec82f9
-
- [kyc-provider-example-persona]
- LOGIC = persona
- # (generic options omitted)
-
- # How long is the KYC check valid?
- KYC_PERSONA_VALIDITY = 365d
-
- # Which subdomain is used for our API?
- KYC_PERSONA_SUBDOMAIN = taler
-
- # Authentication token to use.
- KYC_PERSONA_AUTH_TOKEN = persona_sandbox_42XXXX
-
- # Form to use.
- KYC_PERSONA_TEMPLATE_ID = itempl_Uj6Xxxxx
-
- # Where do we redirect to after KYC finished successfully.
- KYC_PERSONA_POST_URL = "https://taler.net/kyc-done"
-
- # Salt to give to requests for idempotency.
- # Optional.
- # KYC_PERSONA_SALT = salt
-
- # Helper to convert JSON with KYC data returned by Persona into GNU Taler
- # internal format. Should probably always be set to some variant of
- # "taler-exchange-kyc-persona-converter.sh".
- KYC_PERSONA_CONVERTER_HELPER = "taler-exchange-kyc-persona-converter.sh"
-
-The converter helper is expected to be customized to the
-selected template: different templates may return different details
-about the user or business, hence there cannot be a universal converter
-for all purposes. The default shell script uses the ``jq`` tool to
-convert the JSON returned by Persona into the KYC attributes (also
-in JSON) expected by the exchange. The script will need to be adjusted
-based on the attributes collected by the specific template.
-
-
-KYC AID specifics
------------------
-
-We use the hosted flow.
-
-KYCAID must be configured to use the ``/kyc-webhook/$SECTION_NAME/`` endpoint
-of the exchange to notify the exchange about the completion of KYC processes.
-
-.. code-block:: ini
- :caption: /etc/taler/conf.d/exchange-kycaid.conf
-
- [kyc-provider-example-kycaid]
- LOGIC = kycaid
- # (generic options omitted)
-
- # How long is the KYC check valid?
- KYC_KYCAID_VALIDITY = 365d
-
- # Authentication token to use.
- KYC_KYCAID_AUTH_TOKEN = XXX
-
- # Form to use.
- KYC_KYCAID_FORM_ID = XXX
-
- # URL to go to after the process is complete.
- KYC_KYCAID_POST_URL = "https://taler.net/kyc-done"
-
- # Script to convert the KYCAID data into the Taler format.
- KYC_KYCAID_CONVERTER_HELPER = taler-exchange-kyc-kycaid-converter.sh
-
-
-The converter helper is expected to be customized to the selected template:
-different templates may return different details about the user or business,
-hence there cannot be a universal converter for all purposes. The default
-shell script uses the ``jq`` tool to convert the JSON returned by Persona into
-the KYC attributes (also in JSON) expected by the exchange. The script will
-need to be adjusted based on the attributes collected by the specific
-template.
-
.. _Deployment:
@@ -1615,342 +1297,6 @@ of ``taler-exchange-offline``.
under highly unusual (“emergency”) conditions and not in normal
operation.
-AML Configuration
-=================
-
-The AML configuration steps are used to add or remove keys of exchange
-operator staff that are responsible for anti-money laundering (AML)
-compliance. These AML officers are shown suspicious transactions and are
-granted access to the KYC data of an exchange. They can then investigate the
-transaction and decide on freezing or permitting the transfer. They may also
-request additional KYC data from the consumer and can change the threshold
-amount above which a further AML review is triggered.
-
-AML Officer Setup
------------------
-
-To begin the AML setup, AML staff should launch the GNU Taler
-exchange AML SPA Web interface. (FIXME-Sebastian: how?). The
-SPA will generate a public-private key pair and store it in the
-local storage of the browser. The public key will be displayed
-and must be securely transmitted to the offline system for
-approval. Using the offline system, one can then configure
-which staff has access to the AML operations:
-
-.. code-block:: shell-session
-
- [root@exchange-offline]# taler-exchange-offline \
- aml-enable $PUBLIC_KEY "Legal Name" rw > aml.json
- [root@exchange-online]# taler-exchange-offline \
- upload < aml.json
-
-The above commands would add an AML officer with the given "Legal Name" with
-read-write (rw) access to the AML officer database. Using "ro" instead of
-"rw" would grant read-only access to the data, leaving out the ability to
-actually make AML decisions. Once AML access has been granted, the AML
-officer can use the SPA to review cases and (with "rw" access) take AML
-decisions.
-
-Access rights can be revoked at any time using:
-
-.. code-block:: shell-session
-
- [root@exchange-offline]# taler-exchange-offline \
- aml-disable $PUBLIC_KEY "Legal Name" > aml-off.json
- [root@exchange-online]# taler-exchange-offline \
- upload < aml-off.json
-
-
-AML Triggers
-------------
-
-AML decision processes are automatically triggered under certain configurable
-conditions. The primary condition that *must* be configured is the
-``AML_THRESHOLD``:
-
-.. code-block:: ini
- :caption: /etc/taler/conf.d/exchange-business.conf
-
- [exchange]
- # Accounts or wallets with monthly transaction volumes above this threshold
- # are considered suspicious and are automatically flagged for AML review
- # and put on hold until an AML officer has reached a decision.
- AML_THRESHOLD = "EUR:1000000"
-
-Additionally, certain KYC attributes (such as the user being a
-politically exposed person) may lead to an account being
-flagged for AML review. The specific logic is configured by
-providing the exchange with an external helper program that
-makes the decision given the KYC attributes:
-
-.. code-block:: ini
- :caption: /etc/taler/conf.d/exchange-business.conf
-
- [exchange]
- # Specifies a program to run on KYC attribute data to decide
- # whether we should immediately flag an account for AML review.
- KYC_AML_TRIGGER = taler-exchange-kyc-aml-pep-trigger.sh
-
-The given program will be given the KYC attributes in JSON format on standard
-input, and must return 0 to continue without AML and non-zero to flag the
-account for manual review. To disable this trigger, simply leave the option to
-its default value of '[/usr/bin/]true'. To flag all new users for manual
-review, simply set the program to '[/usr/bin/]false'.
-
-AML Forms
----------
-
-AML forms are defined by the DD 54 dynamic forms.
-The shipped implementation with of the exchange is installed in
-
-.. code-block:: shell-session
-
- ${INSTALL_PREFIX}/share/taler/exchange/spa/forms.js
-
-
-The variable ``form`` contains the list of all form available. For
-every entry in the list the next properties are expected to be present:
-
-``label``: used in the UI as the name of the form
-
-``id``: identification name, this will be saved in the exchange database
-along with the values to correctly render the form again.
-It should simple, short and without any character outside numbers,
-letters and underscore.
-
-``version``: when editing a form, instead of just replacing fields
-it will be better to create a new form with the same id and new version.
-That way old forms in the database will used old definition of the form.
-It should be a number.
-
-``impl`` : a function that returns the design and behavior of form.
-See DD 54 dynamic forms.
-
-.. attention::
-
- do not remove a form the list if it has been used. Otherwise you
- won't be able to see the information save in the exchange database.
-
-To add a new one you can simply copy and paste one element, and edit it.
-
-It is much easier to download ``@gnu-taler/aml-backoffice-ui`` source
-from ``https://git.taler.net/wallet-core.git/``, compile and copy the file
-from the ``dist/prod``.
-
-
-AML Programs
-------------
-
-AML programs are helper programs that can:
-
-* Generate a list of *required* context field names
- for the helper (introspection!) using the "--required-context"
- command-line switch. The output should use the same
- syntax as the REQUIRES clause of ``[kyc-check-]``
- configuration sections, except that new lines
- MUST be used to separate fields instead of ";".
-* Generate a list of *required* attribute names
- for the helper (introspection!) using the "--required-attributes"
- command-line switch. The output should use the same
- list of names as the ATTRIBUTES in the
- ``[kyc-provider-]`` configuration section
- (but may also include FORM field names).
-* Process an input JSON object of type
- `AmlProgramInput` into a JSON object of
- type `AmlOutcome`.
- This is the default behavior if no command-line switches
- are provided.
-
-.. ts:def:: AmlProgramInput
-
- interface AmlProgramInput {
-
- // JSON object that was provided as
- // part of the *measure*. This JSON object is
- // provided under "context" in the main JSON object
- // input to the AML program. This "context" should
- // satify both the REQUIRES clause of the respective
- // check and the output of "--requires" from the
- // AML program's command-line option.
- context?: Object;
-
- // JSON object that captures the
- // output of a ``[kyc-provider-]`` or (HTML) FORM.
- // The keys in the JSON object will be the attribute
- // names and the values must be strings representing
- // the data. In the case of file uploads, the data
- // MUST be base64-encoded.
- attributes: Object;
-
- // JSON array with the results of historic
- // AML desisions about the account.
- aml_history: AmlHistoryEntry[];
-
- // JSON array with the results of historic
- // KYC data about the account.
- kyc_history: KycHistoryEntry[];
-
- }
-
-.. ts:def:: AmlHistoryEntry
-
- interface AmlHistoryEntry {
- // When was the AML decision taken.
- decision_time : Timestamp;
-
- // What was the justification given for the decision.
- justification : string;
-
- // Public key of the AML officer taking the decision.
- decider_pub : AmlOfficerPublicKeyP;
-
- // Properties associated with the account by the decision.
- properties : Object;
-
- // New set of legitimization rules that was put in place.
- new_rules : LegitimizationRuleSet;
-
- // True if the account was flagged for (further)
- // investigation.
- to_investigate : boolean;
-
- // True if this is the currently active decision.
- is_active : boolean;
- }
-
-.. ts:def:: KycHistoryEntry
-
- interface KycHistoryEntry {
- // Name of the provider
- // which was used to collect the attributes. NULL if they were
- // just uploaded via a form by the account owner.
- provider_name?: string;
-
- // True if the KYC process completed.
- finished: boolean;
-
- // Numeric `error code <error-codes>`, if the
- // KYC process did not succeed; 0 on success.
- code: number;
-
- // Human-readable description of ``code``. Optional.
- hint?: string;
-
- // Optional detail given when the KYC process failed.
- error_message?: string;
-
- // Identifier of the user at the KYC provider. Optional.
- provider_user_id?: string;
-
- // Identifier of the KYC process at the KYC provider. Optional.
- provider_legitimization_id? :string;
-
- // The collected KYC data.
- // NULL if the attribute data could not
- // be decrypted or was not yet collected.
- attributes?: Object;
-
- // Time when the KYC data was collected
- collection_time: Timestamp;
-
- // Time when the KYC data will expire.
- expiration_time: Timestamp;
- }
-
-.. ts:def:: AmlOutcome
-
- interface AmlOutcome {
-
- // Should the client's account be investigated
- // by AML staff?
- // Defaults to false.
- to_investigate?: boolean;
-
- // 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.
- properties?: AccountProperties;
-
- // Types of events to add to the KYC events table.
- // (for statistics).
- events?: string[];
-
- // KYC rules to apply. Note that this
- // overrides *all* of the default rules
- // until the ``expiration_time`` and specifies
- // the successor measure to apply after the
- // expiration time.
- new_rules: LegitimizationRuleSet;
-
- }
-
-If the AML program fails (exits with a failure code or
-does not provide well-formed JSON output) the AML/KYC
-process continues with the FALLBACK measure. This should
-usually be one that asks AML staff to contact the
-systems administrator.
-
-AML programs are listed in the configuration file, one program per section:
-
-.. code-block:: ini
-
- [aml-program-$PROG_NAME]
-
- # Program to run.
- COMMAND = taler-helper-aml-pep
-
- # Human-readable description of what this
- # AML helper program will do. Used to show
- # to the AML staff.
- DESCRIPTION = "check if the customer is a PEP"
-
- # True if this AML program is enabled (and thus can be
- # used in measures and exposed to AML staff).
- # Optional, default is NO.
- ENABLED = YES
-
- # **original** measure to take if COMMAND fails
- # Usually points to a measure that asks AML staff
- # to contact the systems administrator. The fallback measure
- # context always includes the reasons for the
- # failure.
- FALLBACK = MEASURE_NAME
-
-AML Measures
-------------
-
-The exchange configuration specifies a set of
-**original** *measures* one per configuration section:
-
-.. code-block:: ini
-
- [kyc-measure-$MEASURE_NAME]
-
- # Possible check for this measure. Optional.
- # If not given, PROGRAM should be run immediately
- # (on an empty set of attributes).
- CHECK_NAME = IB_FORM
-
- # Context for the check. The context can be
- # just an empty JSON object if there is none.
- CONTEXT = {"choices":["individual","business"]}
-
- # Program to run on the context and check data to
- # determine the outcome and next measure.
- PROGRAM = taler-aml-program
-
-If no ``CHECK_NAME`` is provided at all, the AML ``PROGRAM`` is to be run
-immediately. This is useful if no client-interaction is required to arrive at
-a decision.
-
-.. note::
-
- The list of *measures* is not complete: AML staff may freely define new
- measures dynamically, usually by selecting checks, an AML program, and
- providing context.
-
-
Setup Linting
=============
@@ -2188,325 +1534,6 @@ exchange can then not detect double-spending. Hence this operation must
not be performed in a production system. You still also need to then
grant the permissions to the other exchange processes again.
-
-
-.. _ExchangeTemplateCustomization:
-
-Template Customization
-======================
-
-The Exchange comes with various HTML templates that are shown to
-guide users through the KYC process. The Exchange uses `C implementation of mustache
-<https://gitlab.com/jobol/mustach>`__ as the templating engine. This section
-describes the various templates. In general, the templates must be installed
-to the ``share/taler/exchange/templates/`` directory. The file names must be of
-the form ``$NAME.$LANG.must`` where ``$NAME`` is the name of the template and
-``$LANG`` is the 2-letter language code of the template. English templates
-must exist and will be used as a fallback. If the browser (user-agent) has
-provided language preferences in the HTTP header and the respective language
-exists, the correct language will be automatically served.
-
-The following subsections give details about each of the templates. Most
-subsection titles are the ``$NAME`` of the respective template.
-
-
-Generic Errors Templates
-------------------------
-
-A number of templates are used for generic errors. These are:
-
- * kyc-proof-already-done (KYC process already completed)
- * kyc-bad-request (400 Bad Request)
- * kyc-proof-endpoint-unknown (404 Not Found for KYC logic)
- * kyc-proof-internal-error (500 Internal Server Error)
- * kyc-proof-target-unknown (404 Not Found for KYC operation)
-
-All of these templates are instantiated using the following information:
-
- * ec: Integer; numeric Taler error code, should be shown to indicate the
- error compactly for reporting to developers
-
- * hint: String; human-readable Taler error code, should be shown for the
- user to understand the error
-
- * message: String; optional, extended human-readable text provided to elaborate
- on the error, should be shown to provide additional context
-
-
-kycaid-invalid-request
-----------------------
-
-The KYCaid plugin does not support requests to the
-``/kyc-proof/`` endpoint (HTTP 400 bad request).
-
-This template is instantiated using the following information:
-
- * ec: Integer; numeric Taler error code, should be shown to indicate the
- error compactly for reporting to developers
-
- * hint: String; human-readable Taler error code, should be shown for the
- user to understand the error
-
- * error: String; error code from the server
-
- * error_details: String; optional error description from the server
-
- * error_uri: optional URI with further details about the error from the server
-
-
-
-oauth2-authentication-failure
------------------------------
-
-The OAuth2 server said that the request was not
-properly authenticated (HTTP 403 Forbidden).
-
-
-This template is instantiated using the following information:
-
- * ec: Integer; numeric Taler error code, should be shown to indicate the
- error compactly for reporting to developers
-
- * hint: String; human-readable Taler error code, should be shown for the
- user to understand the error
-
-
-oauth2-authorization-failure
-----------------------------
-
-The OAuth2 server refused to return the KYC data
-because the authorization code provided was
-invalid (HTTP 403 Forbidden).
-
-This template is instantiated using the following information:
-
- * ec: Integer; numeric Taler error code, should be shown to indicate the
- error compactly for reporting to developers
-
- * hint: String; human-readable Taler error code, should be shown for the
- user to understand the error
-
- * error: String; error code from the server
-
- * error_message: String; error message from the server
-
-
-oauth2-authorization-failure-malformed
---------------------------------------
-
-The server refused the authorization, but then provided
-a malformed response (HTTP 502 Bad Gateway).
-
-This template is instantiated using the following information:
-
- * ec: Integer; numeric Taler error code, should be shown to indicate the
- error compactly for reporting to developers
-
- * hint: string; human-readable Taler error code, should be shown for the
- user to understand the error
-
- * debug: boolean; true if we are running in debug mode and are allowed to return HTML with potentially sensitive information
-
- * server_response: Object; could be NULL; this includes the (malformed) OAuth2 server response, it should be shown to the use if "debug" is true
-
-
-oauth2-bad-request
-------------------
-
-The client made an invalid request (HTTP 400 Bad Request).
-
-This template is instantiated using the following information:
-
- * ec: Integer; numeric Taler error code, should be shown to indicate the
- error compactly for reporting to developers
-
- * hint: String; human-readable Taler error code, should be shown for the
- user to understand the error
-
- * message: String; additional error message elaborating on what was bad about the request
-
-
-oauth2-conversion-failure
--------------------------
-
-Converting the KYC data into the exchange's internal
-format failed (HTTP 502 Bad Gateway).
-
-This template is instantiated using the following information:
-
- * ec: Integer; numeric Taler error code, should be shown to indicate the
- error compactly for reporting to developers
-
- * hint: string; human-readable Taler error code, should be shown for the
- user to understand the error
-
- * debug: boolean; true if we are running in debug mode and are allowed to return HTML with potentially sensitive information
-
- * converter: String; name of the conversion command that failed which was used by the Exchange
-
- * attributes: Object; attributes returned by the conversion command, often NULL (after all, conversion failed)
-
- * message: error message elaborating on the conversion failure
-
-
-oauth2-provider-failure
------------------------
-
-We did not get an acceptable response from the OAuth2
-provider (HTTP 502 Bad Gateway).
-
-This template is instantiated using the following information:
-
- * ec: Integer; numeric Taler error code, should be shown to indicate the
- error compactly for reporting to developers
-
- * hint: String; human-readable Taler error code, should be shown for the
- user to understand the error
-
- * message: String; could be NULL; text elaborating on the details of the failure
-
-
-persona-exchange-unauthorized
------------------------------
-
-The Persona server refused our request (HTTP 403 Forbidden from Persona, returned as a HTTP 502 Bad Gateway).
-
-This template is instantiated using the following information:
-
- * ec: Integer; numeric Taler error code, should be shown to indicate the
- error compactly for reporting to developers
-
- * hint: String; human-readable Taler error code, should be shown for the
- user to understand the error
-
- * data: Object; data returned from Persona service, optional
-
- * persona_http_status: Integer; HTTP status code returned by Persona
-
-
-persona-load-failure
---------------------
-
-The Persona server refused our request (HTTP 429 Too Many Requests from Persona, returned as a HTTP 503 Service Unavailable).
-
-This template is instantiated using the following information:
-
- * ec: Integer; numeric Taler error code, should be shown to indicate the
- error compactly for reporting to developers
-
- * hint: String; human-readable Taler error code, should be shown for the
- user to understand the error
-
- * data: Object; data returned from Persona service, optional
-
- * persona_http_status: Integer; HTTP status code returned by Persona
-
-
-persona-exchange-unpaid
------------------------
-
-The Persona server refused our request (HTTP 402 Payment REquired from Persona, returned as a HTTP 503 Service Unavailable).
-
-This template is instantiated using the following information:
-
- * ec: Integer; numeric Taler error code, should be shown to indicate the
- error compactly for reporting to developers
-
- * hint: String; human-readable Taler error code, should be shown for the
- user to understand the error
-
- * data: Object; data returned from Persona service, optional
-
- * persona_http_status: Integer; HTTP status code returned by Persona
-
-
-
-persona-logic-failure
----------------------
-
-The Persona server refused our request (HTTP 400, 403, 409, 422 from Persona, returned as a HTTP 502 Bad Gateway).
-
-This template is instantiated using the following information:
-
- * ec: Integer; numeric Taler error code, should be shown to indicate the
- error compactly for reporting to developers
-
- * hint: String; human-readable Taler error code, should be shown for the
- user to understand the error
-
- * data: Object; data returned from Persona service, optional
-
- * persona_http_status: Integer; HTTP status code returned by Persona
-
-
-persona-invalid-response
-------------------------
-
-The Persona server refused our request in an
-unexpected way; returned as a HTTP 502 Bad Gateway.
-
-This template is instantiated using the following information:
-
- * ec: Integer; numeric Taler error code, should be shown to indicate the
- error compactly for reporting to developers
-
- * hint: string; human-readable Taler error code, should be shown for the
- user to understand the error
-
- * debug: boolean; true if we are running in debug mode and are allowed to return HTML with potentially sensitive information
-
- * server_response: Object; could be NULL; this includes the (malformed) OAuth2 server response, it should be shown to the use if "debug" is true
-
-
-persona-network-timeout
------------------------
-
-The Persona server refused our request (HTTP 408 from Persona, returned as a HTTP 504 Gateway Timeout).
-
-This template is instantiated using the following information:
-
- * ec: Integer; numeric Taler error code, should be shown to indicate the
- error compactly for reporting to developers
-
- * hint: String; human-readable Taler error code, should be shown for the
- user to understand the error
-
- * data: Object; data returned from Persona service, optional
-
- * persona_http_status: Integer; HTTP status code returned by Persona
-
-
-persona-kyc-failed
-------------------
-
-The Persona server indicated a problem with the KYC process, saying it was not completed.
-
-This template is instantiated using the following information:
-
- * persona_inquiry_id: String; internal ID of the inquiry within Persona, useful for further diagnostics by staff
-
- * data: Object; could be NULL; this includes the server response, it contains extensive diagnostics, see Persona documentation on their ``/api/v1/inquiries/$ID``.
-
- * persona_http_status: Integer; HTTP status code returned by Persona
-
-persona-provider-failure
-------------------------
-
-The Persona server refused our request (HTTP 500 from Persona, returned as a HTTP 502 Bad Gateway).
-
-This template is instantiated using the following information:
-
- * ec: Integer; numeric Taler error code, should be shown to indicate the
- error compactly for reporting to developers
-
- * hint: String; human-readable Taler error code, should be shown for the
- user to understand the error
-
- * data: Object; data returned from Persona service, optional
-
- * persona_http_status: Integer; HTTP status code returned by Persona
-
-
.. _ExchangeBenchmarking:
Benchmarking
diff --git a/taler-kyc-manual.rst b/taler-kyc-manual.rst
@@ -0,0 +1,1020 @@
+..
+ This file is part of GNU TALER.
+
+ Copyright (C) 2014-2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 2.1, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+
+ @author Christian Grothoff
+ @author Florian Dold
+
+.. _KycOperatorManual:
+
+Exchange KYC/AML Operator Manual
+################################
+
+.. contents:: Table of Contents
+ :depth: 1
+ :local:
+
+
+Introduction
+============
+
+About GNU Taler
+---------------
+
+.. include:: frags/about-taler.rst
+
+
+About this manual
+-----------------
+
+This manual targets compliance experts working with system administrators to
+configure legitimization (KYC/KYB) and anti-money laundering (AML) processes
+for a GNU Taler exchange.
+
+
+
+
+.. _LegalSetup:
+
+Legal Setup
+===========
+
+This chapter describes how to setup certain legal aspects of a GNU Taler
+exchange. Users that just want to set up an exchange as an experiment without
+legal requirements can safely skip these steps.
+
+
+Legal conditions for using the service
+--------------------------------------
+
+.. include:: frags/legal.rst
+
+
+KYC Configuration
+=================
+
+To legally operate, Taler exchange operators may have to comply with KYC
+regulation that requires financial institutions to identify parties involved
+in transactions at certain points.
+
+Taler permits an exchange to require KYC data under the following circumstances:
+
+ * Customer withdraws money over a threshold
+ * Wallet receives (via refunds) money resulting in a balance over a threshold
+ * Wallet receives money via P2P payments over a threshold
+ * Merchant receives money over a threshold
+ * Reserve is "opened" for invoicing (**planned feature**)
+
+Any of the above requests can trigger the KYC process,
+which can be illustrated as follows:
+
+.. image:: images/kyc-process.png
+
+At the end of the KYC process, the wallet re-tries the
+original request, and assuming KYC was successful, the
+request should then succeed.
+
+
+Taler KYC Terminology
+---------------------
+
+* **Check**: A check establishes a particular attribute of a user, such as
+ their name based on an ID document and lifeness, mailing address, phone
+ number, taxpayer identity, etc.
+
+* **Type of operation**: The operation type determines which Taler-specific
+ operation has triggered the KYC requirement. We support four types of
+ operation: withdraw (by customer), deposit (by merchant), P2P receive (by
+ wallet) and (high) wallet balance.
+
+* **Condition**: A condition specifies when KYC is required. Conditions
+ include the *type of operation*, a threshold amount (e.g. above EUR:1000)
+ and possibly a time period (e.g. over the last month).
+
+* **Cost**: Metric for the business expense for a KYC check at a certain
+ *provider*. Not in any currency, costs are simply relative and non-negative
+ values. Costs are considered when multiple choices are allowed by the
+ *configuration*.
+
+* **Expiration**: KYC legitimizations may be outdated. Expiration rules
+ determine when *checks* have to be performed again.
+
+* **Legitimization rules**: The legitimization rules determine under which
+ *conditions* which *checks* must be performend and the *expiration* time
+ period for the *checks*.
+
+* **Logic**: Logic refers to a specific bit of code (realized as an exchange
+ plugin) that enables the interaction with a specific *provider*. Logic
+ typically requires configuration for access control (such as an
+ authorization token) and possibly the endpoint of the specific *provider*
+ implementing the respective API.
+
+* **Provider**: A provider performs a specific set of *checks* at a certain
+ *cost*. Interaction with a provider is performed by provider-specific
+ *logic*.
+
+Configuration of possible KYC/AML checks
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The configuration specifies a set of possible KYC checks offered by external
+providers. The names of the configuration sections must being with
+``kyc-check-`` followed by an arbitrary ``$CHECK_NAME``.
+
+The list of possible FORM names is fixed in the SPA
+for a particular exchange release.
+
+The outcome of *any* check should always be uploaded encrypted into the
+``kyc_attributes`` table. It MUST include an ``expiration_time``.
+
+
+
+KYC Configuration Options
+-------------------------
+
+The KYC configuration determines the *legitimization rules*, and specifies
+which providers offer which *checks* at what *cost*.
+
+The configuration specifies a set of providers, one per configuration section. The names of the configuration
+sections must being with ``kyc-proider-`` followed by
+an arbitrary ``$PROVIDER_ID``:
+
+.. code-block:: ini
+ :caption: /etc/taler/conf.d/exchange-kyc-providers.conf
+
+ [kyc-provider-$PROVIDER_ID]
+ # How expensive is it to use this provider?
+ # Used to pick the cheapest provider possible.
+ COST = 42
+ # Which plugin is responsible for this provider?
+ # Choices include "oauth2", "kycaid" and "persona".
+ LOGIC = oauth2
+ # Which type of user does this provider handle?
+ # Either INDIVIDUAL or BUSINESS.
+ USER_TYPE = INDIVIDUAL
+ # Which checks does this provider provide?
+ # List of strings, no specific semantics.
+ PROVIDED_CHECKS = SMS GOVID PHOTO
+ # Plus additional logic-specific options, e.g.:
+ AUTHORIZATION_TOKEN = superdupersecret
+ FORM_ID = business_legi_form
+ # How long is the check considered valid?
+ EXPIRATION = 3650d
+
+The configuration also must specify a set of legitimization requirements, again one
+per configuration section:
+
+.. code-block:: ini
+ :caption: /etc/taler/conf.d/exchange-kyc-rules.conf
+
+ [kyc-legitimization-$RULE_NAME]
+ # Operation that triggers this legitimization.
+ # Must be one of WITHDRAW, DEPOSIT, P2P-RECEIVE
+ # or WALLET-BALANCE.
+ OPERATION_TYPE = WITHDRAW
+ # Required checks to be performed.
+ # List of strings, must individually match the
+ # strings in one or more provider's PROVIDED_CHECKS.
+ REQUIRED_CHECKS = SMS GOVID
+ # Threshold amount above which the legitimization is
+ # triggered. The total must be exceeded in the given
+ # timeframe.
+ THRESHOLD = KUDOS:100
+ # Timeframe over which the amount to be compared to
+ # the THRESHOLD is calculated. Can be 'forever'.
+ # Ignored for WALLET-BALANCE.
+ TIMEFRAME = 30d
+
+
+OAuth 2.0 specifics
+-------------------
+
+In terms of configuration, the OAuth 2.0 logic requires the respective client
+credentials to be configured apriori to enable access to the legitimization
+service. The OAuth 2.0 configuration options are:
+
+.. code-block:: ini
+ :caption: /etc/taler/conf.d/exchange-oauth2.conf
+
+ [kyc-provider-example-oauth2]
+ LOGIC = oauth2
+ # (generic options omitted)
+ # How long is the KYC check valid?
+ KYC_OAUTH2_VALIDITY = forever
+
+ # URL to which we redirect the user for the login process
+ KYC_OAUTH2_AUTHORIZE_URL = "http://kyc.example.com/authorize"
+ # URL where we POST the user's authentication information
+ KYC_OAUTH2_TOKEN_URL = "http://kyc.example.com/token"
+ # URL of the user info access point.
+ KYC_OAUTH2_INFO_URL = "http://kyc.example.com/info"
+
+ # Where does the client get redirected upon completion?
+ KYC_OAUTH2_POST_URL = "http://example.com/thank-you"
+
+ # For authentication to the OAuth2.0 service
+ KYC_OAUTH2_CLIENT_ID = testcase
+ KYC_OAUTH2_CLIENT_SECRET = password
+
+ # Mustach template that converts OAuth2.0 data about the user
+ # into GNU Taler standardized attribute data.
+ KYC_OAUTH2_CONVERTER_HELPER = taler-exchange-kyc-oauth2-challenger.sh
+
+The converter helper is expected to be customized to the selected OAuth2.0
+service: different services may return different details about the user or
+business, hence there cannot be a universal converter for all purposes. The
+default shell script uses the ``jq`` tool to convert the JSON returned by the
+service into the KYC attributes (also in JSON) expected by the exchange. The
+script will need to be adjusted based on the attributes collected by the
+specific backend.
+
+The Challenger service for address validation supports OAuth2.0, but does not
+have a static AUTHORIZE_URL. Instead, the AUTHORIZE_URL must be enabled by the client
+using a special authenticated request to the Challenger's ``/setup`` endpoint.
+The exchange supports this by appending ``#setup`` to the AUTHORIZE_URL (note
+that fragments are illegal in OAuth2.0 URLs). Be careful to quote the URL,
+as ``#`` is otherwise interpreted as the beginning of a comment by the
+configuration file syntax.
+
+.. code-block:: ini
+ :caption: /etc/taler/conf.d/exchange-challenger-oauth2.conf
+
+ [kyc-provider-challenger-oauth2]
+ LOGIC = oauth2
+ KYC_OAUTH2_AUTHORIZE_URL = "http://challenger.example.com/authorize/#setup"
+ KYC_OAUTH2_TOKEN_URL = "http://challenger.example.com/token"
+ KYC_OAUTH2_INFO_URL = "http://challenger.example.com/info"
+
+When using OAuth 2.0, the *CLIENT REDIRECT URI* must be set to the
+``/kyc-proof/$PROVIDER_SECTION`` endpoint. For example, given the
+configuration above and an exchange running on the host
+``exchange.example.com``, the redirect URI would be
+``https://exchange.example.com/kyc-proof/kyc-provider-challenger-oauth2/``.
+
+
+
+Persona specifics
+-----------------
+
+We use the hosted flow. The Persona endpoints return a ``request-id``, which
+we log for diagnosis.
+
+Persona should be configured to use the ``/kyc-webhook/`` endpoint of the
+exchange to notify the exchange about the completion of KYC processes. The
+webhook is authenticated using a shared secret, which should be in the
+configuration. To use the Persona webhook, you must set the webhook URL in
+the Persona service to ``$EXCHANGE_BASE_URL/kyc-webhook/$SECTION_NAME/`` where
+``$SECTION_NAME`` is the name of the configuration section. You should also
+extract the authentication token for the webhook and put it into the
+configuration as shown above.
+
+
+.. code-block:: ini
+ :caption: /etc/taler/conf.d/exchange-persona.conf
+
+ [kyclogic-persona]
+ # Webhook authorization token. Global for all uses
+ # of the persona provider!
+ WEBHOOK_AUTH_TOKEN = wbhsec_698b5a19-c790-47f6-b396-deb572ec82f9
+
+ [kyc-provider-example-persona]
+ LOGIC = persona
+ # (generic options omitted)
+
+ # How long is the KYC check valid?
+ KYC_PERSONA_VALIDITY = 365d
+
+ # Which subdomain is used for our API?
+ KYC_PERSONA_SUBDOMAIN = taler
+
+ # Authentication token to use.
+ KYC_PERSONA_AUTH_TOKEN = persona_sandbox_42XXXX
+
+ # Form to use.
+ KYC_PERSONA_TEMPLATE_ID = itempl_Uj6Xxxxx
+
+ # Where do we redirect to after KYC finished successfully.
+ KYC_PERSONA_POST_URL = "https://taler.net/kyc-done"
+
+ # Salt to give to requests for idempotency.
+ # Optional.
+ # KYC_PERSONA_SALT = salt
+
+ # Helper to convert JSON with KYC data returned by Persona into GNU Taler
+ # internal format. Should probably always be set to some variant of
+ # "taler-exchange-kyc-persona-converter.sh".
+ KYC_PERSONA_CONVERTER_HELPER = "taler-exchange-kyc-persona-converter.sh"
+
+The converter helper is expected to be customized to the
+selected template: different templates may return different details
+about the user or business, hence there cannot be a universal converter
+for all purposes. The default shell script uses the ``jq`` tool to
+convert the JSON returned by Persona into the KYC attributes (also
+in JSON) expected by the exchange. The script will need to be adjusted
+based on the attributes collected by the specific template.
+
+
+KYC AID specifics
+-----------------
+
+We use the hosted flow.
+
+KYCAID must be configured to use the ``/kyc-webhook/$SECTION_NAME/`` endpoint
+of the exchange to notify the exchange about the completion of KYC processes.
+
+.. code-block:: ini
+ :caption: /etc/taler/conf.d/exchange-kycaid.conf
+
+ [kyc-provider-example-kycaid]
+ LOGIC = kycaid
+ # (generic options omitted)
+
+ # How long is the KYC check valid?
+ KYC_KYCAID_VALIDITY = 365d
+
+ # Authentication token to use.
+ KYC_KYCAID_AUTH_TOKEN = XXX
+
+ # Form to use.
+ KYC_KYCAID_FORM_ID = XXX
+
+ # URL to go to after the process is complete.
+ KYC_KYCAID_POST_URL = "https://taler.net/kyc-done"
+
+ # Script to convert the KYCAID data into the Taler format.
+ KYC_KYCAID_CONVERTER_HELPER = taler-exchange-kyc-kycaid-converter.sh
+
+
+The converter helper is expected to be customized to the selected template:
+different templates may return different details about the user or business,
+hence there cannot be a universal converter for all purposes. The default
+shell script uses the ``jq`` tool to convert the JSON returned by Persona into
+the KYC attributes (also in JSON) expected by the exchange. The script will
+need to be adjusted based on the attributes collected by the specific
+template.
+
+
+
+AML Configuration
+=================
+
+The AML configuration steps are used to add or remove keys of exchange
+operator staff that are responsible for anti-money laundering (AML)
+compliance. These AML officers are shown suspicious transactions and are
+granted access to the KYC data of an exchange. They can then investigate the
+transaction and decide on freezing or permitting the transfer. They may also
+request additional KYC data from the consumer and can change the threshold
+amount above which a further AML review is triggered.
+
+AML Officer Setup
+-----------------
+
+To begin the AML setup, AML staff should launch the GNU Taler
+exchange AML SPA Web interface. (FIXME-Sebastian: how?). The
+SPA will generate a public-private key pair and store it in the
+local storage of the browser. The public key will be displayed
+and must be securely transmitted to the offline system for
+approval. Using the offline system, one can then configure
+which staff has access to the AML operations:
+
+.. code-block:: shell-session
+
+ [root@exchange-offline]# taler-exchange-offline \
+ aml-enable $PUBLIC_KEY "Legal Name" rw > aml.json
+ [root@exchange-online]# taler-exchange-offline \
+ upload < aml.json
+
+The above commands would add an AML officer with the given "Legal Name" with
+read-write (rw) access to the AML officer database. Using "ro" instead of
+"rw" would grant read-only access to the data, leaving out the ability to
+actually make AML decisions. Once AML access has been granted, the AML
+officer can use the SPA to review cases and (with "rw" access) take AML
+decisions.
+
+Access rights can be revoked at any time using:
+
+.. code-block:: shell-session
+
+ [root@exchange-offline]# taler-exchange-offline \
+ aml-disable $PUBLIC_KEY "Legal Name" > aml-off.json
+ [root@exchange-online]# taler-exchange-offline \
+ upload < aml-off.json
+
+
+AML Triggers
+------------
+
+AML decision processes are automatically triggered under certain configurable
+conditions. The primary condition that *must* be configured is the
+``AML_THRESHOLD``:
+
+.. code-block:: ini
+ :caption: /etc/taler/conf.d/exchange-business.conf
+
+ [exchange]
+ # Accounts or wallets with monthly transaction volumes above this threshold
+ # are considered suspicious and are automatically flagged for AML review
+ # and put on hold until an AML officer has reached a decision.
+ AML_THRESHOLD = "EUR:1000000"
+
+Additionally, certain KYC attributes (such as the user being a
+politically exposed person) may lead to an account being
+flagged for AML review. The specific logic is configured by
+providing the exchange with an external helper program that
+makes the decision given the KYC attributes:
+
+.. code-block:: ini
+ :caption: /etc/taler/conf.d/exchange-business.conf
+
+ [exchange]
+ # Specifies a program to run on KYC attribute data to decide
+ # whether we should immediately flag an account for AML review.
+ KYC_AML_TRIGGER = taler-exchange-kyc-aml-pep-trigger.sh
+
+The given program will be given the KYC attributes in JSON format on standard
+input, and must return 0 to continue without AML and non-zero to flag the
+account for manual review. To disable this trigger, simply leave the option to
+its default value of '[/usr/bin/]true'. To flag all new users for manual
+review, simply set the program to '[/usr/bin/]false'.
+
+AML Forms
+---------
+
+AML forms are defined by the DD 54 dynamic forms.
+The shipped implementation with of the exchange is installed in
+
+.. code-block:: shell-session
+
+ ${INSTALL_PREFIX}/share/taler/exchange/spa/forms.js
+
+
+The variable ``form`` contains the list of all form available. For
+every entry in the list the next properties are expected to be present:
+
+``label``: used in the UI as the name of the form
+
+``id``: identification name, this will be saved in the exchange database
+along with the values to correctly render the form again.
+It should simple, short and without any character outside numbers,
+letters and underscore.
+
+``version``: when editing a form, instead of just replacing fields
+it will be better to create a new form with the same id and new version.
+That way old forms in the database will used old definition of the form.
+It should be a number.
+
+``impl`` : a function that returns the design and behavior of form.
+See DD 54 dynamic forms.
+
+.. attention::
+
+ do not remove a form the list if it has been used. Otherwise you
+ won't be able to see the information save in the exchange database.
+
+To add a new one you can simply copy and paste one element, and edit it.
+
+It is much easier to download ``@gnu-taler/aml-backoffice-ui`` source
+from ``https://git.taler.net/wallet-core.git/``, compile and copy the file
+from the ``dist/prod``.
+
+
+AML Programs
+------------
+
+AML programs are helper programs that can:
+
+* Generate a list of *required* context field names
+ for the helper (introspection!) using the "--required-context"
+ command-line switch. The output should use the same
+ syntax as the REQUIRES clause of ``[kyc-check-]``
+ configuration sections, except that new lines
+ MUST be used to separate fields instead of ";".
+* Generate a list of *required* attribute names
+ for the helper (introspection!) using the "--required-attributes"
+ command-line switch. The output should use the same
+ list of names as the ATTRIBUTES in the
+ ``[kyc-provider-]`` configuration section
+ (but may also include FORM field names).
+* Process an input JSON object of type
+ `AmlProgramInput` into a JSON object of
+ type `AmlOutcome`.
+ This is the default behavior if no command-line switches
+ are provided.
+
+.. ts:def:: AmlProgramInput
+
+ interface AmlProgramInput {
+
+ // JSON object that was provided as
+ // part of the *measure*. This JSON object is
+ // provided under "context" in the main JSON object
+ // input to the AML program. This "context" should
+ // satify both the REQUIRES clause of the respective
+ // check and the output of "--requires" from the
+ // AML program's command-line option.
+ context?: Object;
+
+ // JSON object that captures the
+ // output of a ``[kyc-provider-]`` or (HTML) FORM.
+ // The keys in the JSON object will be the attribute
+ // names and the values must be strings representing
+ // the data. In the case of file uploads, the data
+ // MUST be base64-encoded.
+ attributes: Object;
+
+ // JSON array with the results of historic
+ // AML desisions about the account.
+ aml_history: AmlHistoryEntry[];
+
+ // JSON array with the results of historic
+ // KYC data about the account.
+ kyc_history: KycHistoryEntry[];
+
+ }
+
+.. ts:def:: AmlHistoryEntry
+
+ interface AmlHistoryEntry {
+ // When was the AML decision taken.
+ decision_time : Timestamp;
+
+ // What was the justification given for the decision.
+ justification : string;
+
+ // Public key of the AML officer taking the decision.
+ decider_pub : AmlOfficerPublicKeyP;
+
+ // Properties associated with the account by the decision.
+ properties : Object;
+
+ // New set of legitimization rules that was put in place.
+ new_rules : LegitimizationRuleSet;
+
+ // True if the account was flagged for (further)
+ // investigation.
+ to_investigate : boolean;
+
+ // True if this is the currently active decision.
+ is_active : boolean;
+ }
+
+.. ts:def:: KycHistoryEntry
+
+ interface KycHistoryEntry {
+ // Name of the provider
+ // which was used to collect the attributes. NULL if they were
+ // just uploaded via a form by the account owner.
+ provider_name?: string;
+
+ // True if the KYC process completed.
+ finished: boolean;
+
+ // Numeric `error code <error-codes>`, if the
+ // KYC process did not succeed; 0 on success.
+ code: number;
+
+ // Human-readable description of ``code``. Optional.
+ hint?: string;
+
+ // Optional detail given when the KYC process failed.
+ error_message?: string;
+
+ // Identifier of the user at the KYC provider. Optional.
+ provider_user_id?: string;
+
+ // Identifier of the KYC process at the KYC provider. Optional.
+ provider_legitimization_id? :string;
+
+ // The collected KYC data.
+ // NULL if the attribute data could not
+ // be decrypted or was not yet collected.
+ attributes?: Object;
+
+ // Time when the KYC data was collected
+ collection_time: Timestamp;
+
+ // Time when the KYC data will expire.
+ expiration_time: Timestamp;
+ }
+
+.. ts:def:: AmlOutcome
+
+ interface AmlOutcome {
+
+ // Should the client's account be investigated
+ // by AML staff?
+ // Defaults to false.
+ to_investigate?: boolean;
+
+ // 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.
+ properties?: AccountProperties;
+
+ // Types of events to add to the KYC events table.
+ // (for statistics).
+ events?: string[];
+
+ // KYC rules to apply. Note that this
+ // overrides *all* of the default rules
+ // until the ``expiration_time`` and specifies
+ // the successor measure to apply after the
+ // expiration time.
+ new_rules: LegitimizationRuleSet;
+
+ }
+
+If the AML program fails (exits with a failure code or
+does not provide well-formed JSON output) the AML/KYC
+process continues with the FALLBACK measure. This should
+usually be one that asks AML staff to contact the
+systems administrator.
+
+AML programs are listed in the configuration file, one program per section:
+
+.. code-block:: ini
+
+ [aml-program-$PROG_NAME]
+
+ # Program to run.
+ COMMAND = taler-helper-aml-pep
+
+ # Human-readable description of what this
+ # AML helper program will do. Used to show
+ # to the AML staff.
+ DESCRIPTION = "check if the customer is a PEP"
+
+ # True if this AML program is enabled (and thus can be
+ # used in measures and exposed to AML staff).
+ # Optional, default is NO.
+ ENABLED = YES
+
+ # **original** measure to take if COMMAND fails
+ # Usually points to a measure that asks AML staff
+ # to contact the systems administrator. The fallback measure
+ # context always includes the reasons for the
+ # failure.
+ FALLBACK = MEASURE_NAME
+
+AML Measures
+------------
+
+The exchange configuration specifies a set of
+**original** *measures* one per configuration section:
+
+.. code-block:: ini
+
+ [kyc-measure-$MEASURE_NAME]
+
+ # Possible check for this measure. Optional.
+ # If not given, PROGRAM should be run immediately
+ # (on an empty set of attributes).
+ CHECK_NAME = IB_FORM
+
+ # Context for the check. The context can be
+ # just an empty JSON object if there is none.
+ CONTEXT = {"choices":["individual","business"]}
+
+ # Program to run on the context and check data to
+ # determine the outcome and next measure.
+ PROGRAM = taler-aml-program
+
+If no ``CHECK_NAME`` is provided at all, the AML ``PROGRAM`` is to be run
+immediately. This is useful if no client-interaction is required to arrive at
+a decision.
+
+.. note::
+
+ The list of *measures* is not complete: AML staff may freely define new
+ measures dynamically, usually by selecting checks, an AML program, and
+ providing context.
+
+
+
+.. _ExchangeTemplateCustomization:
+
+Template Customization
+======================
+
+The Exchange comes with various HTML templates that are shown to
+guide users through the KYC process. The Exchange uses `C implementation of mustache
+<https://gitlab.com/jobol/mustach>`__ as the templating engine. This section
+describes the various templates. In general, the templates must be installed
+to the ``share/taler/exchange/templates/`` directory. The file names must be of
+the form ``$NAME.$LANG.must`` where ``$NAME`` is the name of the template and
+``$LANG`` is the 2-letter language code of the template. English templates
+must exist and will be used as a fallback. If the browser (user-agent) has
+provided language preferences in the HTTP header and the respective language
+exists, the correct language will be automatically served.
+
+The following subsections give details about each of the templates. Most
+subsection titles are the ``$NAME`` of the respective template.
+
+
+Generic Errors Templates
+------------------------
+
+A number of templates are used for generic errors. These are:
+
+ * kyc-proof-already-done (KYC process already completed)
+ * kyc-bad-request (400 Bad Request)
+ * kyc-proof-endpoint-unknown (404 Not Found for KYC logic)
+ * kyc-proof-internal-error (500 Internal Server Error)
+ * kyc-proof-target-unknown (404 Not Found for KYC operation)
+
+All of these templates are instantiated using the following information:
+
+ * ec: Integer; numeric Taler error code, should be shown to indicate the
+ error compactly for reporting to developers
+
+ * hint: String; human-readable Taler error code, should be shown for the
+ user to understand the error
+
+ * message: String; optional, extended human-readable text provided to elaborate
+ on the error, should be shown to provide additional context
+
+
+kycaid-invalid-request
+----------------------
+
+The KYCaid plugin does not support requests to the
+``/kyc-proof/`` endpoint (HTTP 400 bad request).
+
+This template is instantiated using the following information:
+
+ * ec: Integer; numeric Taler error code, should be shown to indicate the
+ error compactly for reporting to developers
+
+ * hint: String; human-readable Taler error code, should be shown for the
+ user to understand the error
+
+ * error: String; error code from the server
+
+ * error_details: String; optional error description from the server
+
+ * error_uri: optional URI with further details about the error from the server
+
+
+
+oauth2-authentication-failure
+-----------------------------
+
+The OAuth2 server said that the request was not
+properly authenticated (HTTP 403 Forbidden).
+
+
+This template is instantiated using the following information:
+
+ * ec: Integer; numeric Taler error code, should be shown to indicate the
+ error compactly for reporting to developers
+
+ * hint: String; human-readable Taler error code, should be shown for the
+ user to understand the error
+
+
+oauth2-authorization-failure
+----------------------------
+
+The OAuth2 server refused to return the KYC data
+because the authorization code provided was
+invalid (HTTP 403 Forbidden).
+
+This template is instantiated using the following information:
+
+ * ec: Integer; numeric Taler error code, should be shown to indicate the
+ error compactly for reporting to developers
+
+ * hint: String; human-readable Taler error code, should be shown for the
+ user to understand the error
+
+ * error: String; error code from the server
+
+ * error_message: String; error message from the server
+
+
+oauth2-authorization-failure-malformed
+--------------------------------------
+
+The server refused the authorization, but then provided
+a malformed response (HTTP 502 Bad Gateway).
+
+This template is instantiated using the following information:
+
+ * ec: Integer; numeric Taler error code, should be shown to indicate the
+ error compactly for reporting to developers
+
+ * hint: string; human-readable Taler error code, should be shown for the
+ user to understand the error
+
+ * debug: boolean; true if we are running in debug mode and are allowed to return HTML with potentially sensitive information
+
+ * server_response: Object; could be NULL; this includes the (malformed) OAuth2 server response, it should be shown to the use if "debug" is true
+
+
+oauth2-bad-request
+------------------
+
+The client made an invalid request (HTTP 400 Bad Request).
+
+This template is instantiated using the following information:
+
+ * ec: Integer; numeric Taler error code, should be shown to indicate the
+ error compactly for reporting to developers
+
+ * hint: String; human-readable Taler error code, should be shown for the
+ user to understand the error
+
+ * message: String; additional error message elaborating on what was bad about the request
+
+
+oauth2-conversion-failure
+-------------------------
+
+Converting the KYC data into the exchange's internal
+format failed (HTTP 502 Bad Gateway).
+
+This template is instantiated using the following information:
+
+ * ec: Integer; numeric Taler error code, should be shown to indicate the
+ error compactly for reporting to developers
+
+ * hint: string; human-readable Taler error code, should be shown for the
+ user to understand the error
+
+ * debug: boolean; true if we are running in debug mode and are allowed to return HTML with potentially sensitive information
+
+ * converter: String; name of the conversion command that failed which was used by the Exchange
+
+ * attributes: Object; attributes returned by the conversion command, often NULL (after all, conversion failed)
+
+ * message: error message elaborating on the conversion failure
+
+
+oauth2-provider-failure
+-----------------------
+
+We did not get an acceptable response from the OAuth2
+provider (HTTP 502 Bad Gateway).
+
+This template is instantiated using the following information:
+
+ * ec: Integer; numeric Taler error code, should be shown to indicate the
+ error compactly for reporting to developers
+
+ * hint: String; human-readable Taler error code, should be shown for the
+ user to understand the error
+
+ * message: String; could be NULL; text elaborating on the details of the failure
+
+
+persona-exchange-unauthorized
+-----------------------------
+
+The Persona server refused our request (HTTP 403 Forbidden from Persona, returned as a HTTP 502 Bad Gateway).
+
+This template is instantiated using the following information:
+
+ * ec: Integer; numeric Taler error code, should be shown to indicate the
+ error compactly for reporting to developers
+
+ * hint: String; human-readable Taler error code, should be shown for the
+ user to understand the error
+
+ * data: Object; data returned from Persona service, optional
+
+ * persona_http_status: Integer; HTTP status code returned by Persona
+
+
+persona-load-failure
+--------------------
+
+The Persona server refused our request (HTTP 429 Too Many Requests from Persona, returned as a HTTP 503 Service Unavailable).
+
+This template is instantiated using the following information:
+
+ * ec: Integer; numeric Taler error code, should be shown to indicate the
+ error compactly for reporting to developers
+
+ * hint: String; human-readable Taler error code, should be shown for the
+ user to understand the error
+
+ * data: Object; data returned from Persona service, optional
+
+ * persona_http_status: Integer; HTTP status code returned by Persona
+
+
+persona-exchange-unpaid
+-----------------------
+
+The Persona server refused our request (HTTP 402 Payment REquired from Persona, returned as a HTTP 503 Service Unavailable).
+
+This template is instantiated using the following information:
+
+ * ec: Integer; numeric Taler error code, should be shown to indicate the
+ error compactly for reporting to developers
+
+ * hint: String; human-readable Taler error code, should be shown for the
+ user to understand the error
+
+ * data: Object; data returned from Persona service, optional
+
+ * persona_http_status: Integer; HTTP status code returned by Persona
+
+
+
+persona-logic-failure
+---------------------
+
+The Persona server refused our request (HTTP 400, 403, 409, 422 from Persona, returned as a HTTP 502 Bad Gateway).
+
+This template is instantiated using the following information:
+
+ * ec: Integer; numeric Taler error code, should be shown to indicate the
+ error compactly for reporting to developers
+
+ * hint: String; human-readable Taler error code, should be shown for the
+ user to understand the error
+
+ * data: Object; data returned from Persona service, optional
+
+ * persona_http_status: Integer; HTTP status code returned by Persona
+
+
+persona-invalid-response
+------------------------
+
+The Persona server refused our request in an
+unexpected way; returned as a HTTP 502 Bad Gateway.
+
+This template is instantiated using the following information:
+
+ * ec: Integer; numeric Taler error code, should be shown to indicate the
+ error compactly for reporting to developers
+
+ * hint: string; human-readable Taler error code, should be shown for the
+ user to understand the error
+
+ * debug: boolean; true if we are running in debug mode and are allowed to return HTML with potentially sensitive information
+
+ * server_response: Object; could be NULL; this includes the (malformed) OAuth2 server response, it should be shown to the use if "debug" is true
+
+
+persona-network-timeout
+-----------------------
+
+The Persona server refused our request (HTTP 408 from Persona, returned as a HTTP 504 Gateway Timeout).
+
+This template is instantiated using the following information:
+
+ * ec: Integer; numeric Taler error code, should be shown to indicate the
+ error compactly for reporting to developers
+
+ * hint: String; human-readable Taler error code, should be shown for the
+ user to understand the error
+
+ * data: Object; data returned from Persona service, optional
+
+ * persona_http_status: Integer; HTTP status code returned by Persona
+
+
+persona-kyc-failed
+------------------
+
+The Persona server indicated a problem with the KYC process, saying it was not completed.
+
+This template is instantiated using the following information:
+
+ * persona_inquiry_id: String; internal ID of the inquiry within Persona, useful for further diagnostics by staff
+
+ * data: Object; could be NULL; this includes the server response, it contains extensive diagnostics, see Persona documentation on their ``/api/v1/inquiries/$ID``.
+
+ * persona_http_status: Integer; HTTP status code returned by Persona
+
+persona-provider-failure
+------------------------
+
+The Persona server refused our request (HTTP 500 from Persona, returned as a HTTP 502 Bad Gateway).
+
+This template is instantiated using the following information:
+
+ * ec: Integer; numeric Taler error code, should be shown to indicate the
+ error compactly for reporting to developers
+
+ * hint: String; human-readable Taler error code, should be shown for the
+ user to understand the error
+
+ * data: Object; data returned from Persona service, optional
+
+ * persona_http_status: Integer; HTTP status code returned by Persona