8. Exchange KYC/AML Operator Manual

8.1. Introduction

8.1.1. About GNU Taler

GNU Taler is an open protocol for an electronic payment system with a free software reference implementation. GNU Taler offers secure, fast and easy payment processing using well understood cryptographic techniques. GNU Taler allows customers to remain anonymous, while ensuring that merchants can be held accountable by governments. Hence, GNU Taler is compatible with anti-money-laundering (AML) and know-your-customer (KYC) regulation, as well as data protection regulation (such as GDPR).

8.1.2. About this manual

This chapter describes how to setup certain compliance aspects of a GNU Taler exchange. Users that just want to set up an exchange as an experiment without legal or regulatory requirements can safely skip this chapter.

This manual targets compliance experts working with system administrators and developers to configure legitimization (KYC/KYB) and anti-money laundering (AML) processes for a GNU Taler exchange. Expertise in all three domains is required, as Taler’s KYC and AML processes are highly configurable and programmable.

8.1.4. Terms of Service

The service has an endpoint “/terms” to return the terms of service (in legal language) of the service operator. Client software show these terms of service to the user when the user is first interacting with the service. Terms of service are optional for experimental deployments, if none are configured, the service will return a simple statement saying that there are no terms of service available.

To configure the terms of service response, there are two options in the configuration file for the service:

  • TERMS_ETAG: The current “Etag” to return for the terms of service. This value must be changed whenever the terms of service are updated. A common value to use would be a version number. Note that if you change the TERMS_ETAG, you MUST also provide the respective files in TERMS_DIR (see below).

  • TERMS_DIR: The directory that contains the terms of service. The files in the directory must be readable to the service process.

8.1.5. Privacy Policy

The service has an endpoint “/pp” to return the terms privacy policy (in legal language) of the service operator. Clients should show the privacy policy to the user when the user explicitly asks for it, but it should not be shown by default. Privacy policies are optional for experimental deployments, if none are configured, the service will return a simple statement saying that there is no privacy policy available.

To configure the privacy policy response, there are two options in the configuration file for the service:

  • PRIVACY_ETAG: The current “Etag” to return for the privacy policy. This value must be changed whenever the privacy policy is updated. A common value to use would be a version number. Note that if you change the PRIVACY_ETAG, you MUST also provide the respective files in PRIVACY_DIR (see below).

  • PRIVACY_DIR: The directory that contains the privacy policy. The files in the directory must be readable to the service process.

8.1.8. Adding translations

Translations must be available in subdirectories locale/$LANGUAGE/LC_MESSAGES/$ETAG.po. To start translating, you first need to add a new language:

$ taler-terms-generator -i $ETAG -l $LANGUAGE

Here, $LANGUAGE should be a two-letter language code like de or fr. The command will generate a file locale/$LANGUAGE/LC_MESSAGES/$ETAG.po which contains each English sentence or paragraph in the original document and an initially empty translation. Translators should update the .po file. Afterwards, simply re-run

$ taler-terms-generator -i $ETAG

to make the current translation(s) available to the service.

Note

You must restart the service whenever adding or updating legal documents or their translations.

8.2. Know-Your-Customer Setup

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 know-your-customer (KYC) or know-your-business (KYB) 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

  • Merchant deposits digital cash over a threshold (planned feature, bug 9040)

  • Reserve is “opened” for invoicing (planned feature)

Any of the above requests can trigger the KYC process, which can be illustrated as follows:

_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.

8.2.1. KYC Terminology

  • Attributes: Attributes are used to represent KYC data obtained about an account holder. Attributes include passport images, address data, business registration documents, and indeed arbitrary forms filed by AML staff or the customer themselves. Attribute data is considered sensitive private information and is thus stored encrypted within the exchange database.

  • 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. Checks may be given context (such as whether a customer is an individual or a business) to run correctly. Checks can also be AML staff inserting information for plausibilization. Checks result in attributes about the account’s owner which are given to an external AML program together with the context to determine an outcome. KYC checks are always specified with a fallback measure to be taken if the check fails.

  • 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).

  • Configuration: The configuration determines the legitimization rules, and specifies which providers offer which checks.

  • Context: Context is information provided as input into a check and program to customize their execution. The context is initially set by the measure (possibly including data from the trigger). Naturally, the program may use its AmlProgramInput which includes context and attribute data to compute an update context for the next set of measures that it specifies in the LegitimizationRuleSet as part of the AmlOutcome. Thus, context is something that typically evolves as the account undergoes measures. Context is lost if an account transitions to default legitimization rules due to expiration.

  • Decision: AML decisions are these as taken by AML officers (humans). AML outcomes are the results of AML programs (code). Legitimization outcomes (DB table) can arise from either.

  • Display priority: Every rule has a display priority. If a second rule is triggered before the outcome of a rule could be determined, the rule with the larger display priority becomes the requirement that the account owner has to satisfy (and that thus will be displayed by the KYC SPA).

  • Expiration: Except for the default rules, any set of KYC rules is subject to expiration. This can be because attributes become outdated or because sanctions have a time limit. The expiration time thus determines when a new measure is triggered in the absence of a transaction crossing thresholds in the current set of legtimization rules.

  • Legitimization rules: The legitimization rules determine under which conditions which measures will be taken. A LegitimizationRuleSet always also includes an expiration time period for (custom, non-default) legitimization rules after which a fallback measure* will automatically apply. Legitimization rules may be exposed to the client (for example, to allow a wallet to stay below hard withdraw thresholds) or could be secret.

  • 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.

  • Measure: Describes the possible outgoing edges from one state in the state machine (including how to show the current state). Each edge is given some context and a check to be performed as well as an AML program which determines the outcome. We generally distinguish between “original” measures (defined globally in the exchange configuration) and “custom” measures (defined specifically for an account by AML staff).

  • Outcome: An AmlOutcome describes the account state that an account ends up in due to either an AML staff action or an AML program doing some computation over the attributes resulting from a check. Outcomes can be that certain types of transactions are “verboten”, that the account is (or remains) under investigation by AML staff, that the account is given certain properties, and/or that certain events are to be logged. Outcomes also include a new set of legitimization rules to apply (and an expiration time at which point a successor measure will be automatically taken).

  • Provider: A provider performs a specific set of checks at a certain cost. Interaction with a provider is performed by provider-specific logic.

  • Program: An AML helper program is given context about the current state of an account and the attribute data from a check to compute the outcome. For example, a program may look at the “PEP” field of a KYC check and decide if the outcome is to put the account into normal or held-for-manual-review state. AML programs are always specified with a fallback measure to be taken if the program fails.

  • Trigger: A specific transaction that satisfies a Condition.

  • 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), aggregate transfer (to merchant), P2P receive (by wallet) and (high) wallet balance.

8.2.2. Configuration of possible KYC/AML providers

The KYC configuration determines the legitimization rules, and specifies which providers offer which checks.

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:

/etc/taler/conf.d/exchange-kyc-providers.conf
[kyc-provider-$PROVIDER_ID]

# Which plugin is responsible for this provider?
# Choices include "oauth2", "kycaid" and "persona".
LOGIC = oauth2

# Plus additional logic-specific options, e.g.:
AUTHORIZATION_TOKEN = superdupersecret

# Other logic-specific internal options (example):
FORM_ID = business_legi_form

8.2.2.1. 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:

/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.

/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/.

8.2.2.2. 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.

/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.

8.2.2.3. 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.

/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.

8.2.3. Configuration of possible KYC/AML checks

The configuration specifies a set of possible KYC checks offered by external providers, one per configuration section. The names of the configuration sections must being with kyc-check- followed by an arbitrary $CHECK_NAME.

[kyc-check-$CHECK_NAME]

# Which type of check is this? Also determines
# the SPA form to show to the user for this check.
#
# INFO: wait for staff or contact staff out-of band
#          (only information shown, no SPA action)
# FORM: SPA should show an inline (HTML) form
# LINK: SPA may start external KYC process or upload
#
TYPE = INFO|LINK|FORM

# Optional. Set to YES to allow this check be
# done voluntarily by a client (they may then
# still have to pay for it). Used to offer the
# SPA to display checks even if they are
# not required. Default is NO.
# Since **vATTEST**.
VOLUNTARY = YES/NO

# Provider id, present only if type is LINK.
# Refers to a ``kyc-provider-$PROVIDER_ID`` section.
PROVIDER_ID = id

# Name of the SPA form, if type is FORM
# "INFO" and "LINK" are reserved and must not be used.
# The exchange server and the SPA must agree on a list
# of supported forms and the resulting attributes.
#
# The SPA should include a JSON resource file
# "forms.json" mapping form names to arrays of
# attribute names each form provides.
FORM_NAME = name

# Descriptions to use in the SPA to display the check.
DESCRIPTION = "Upload your passport picture"
DESCRIPTION_I18N = "{"en":"Upload scan of your passport"}"

# ';'-separated list of fields that the CONTEXT must
# provide as inputs to this check. For example,
# for a FORM of type CHOICE, this might state
# ``choices: string[];``. The type after the ":"
# is for now purely for documentation and is
# not checked. However, it may be shown to AML staff
# when they configure measures.
REQUIRES = requirement;

# Description of the outputs provided by the check.
# Basically, the check's output is expected to
# provide the following fields as attribute inputs into
# a subsequent AML program.
# INFO never has any outputs.
OUTPUTS = "business_name street city country registration"

# **original** measure to take if the check fails
# (for any reason, e.g. provider or form fail to
# satisfy constraints or provider signals user error)
# Usually should point to a measure that requests
# AML staff to investigate.  The fallback measure
# context always includes the reasons for the
# failure.
FALLBACK = MEASURE_NAME

The list of possible FORM names is fixed in the SPA for a particular exchange release.

The “check_name” value “skip” is reserved and must not be defined. It can be used in measures where the AML program must be run immediately without any input.

The outcome of any check is stored encrypted in the kyc_attributes table. It MUST include an expiration_time.

8.2.3.1. The INFO Type

When using KYC checks of type “INFO”, the KYC-SPA will simply show the given DESCRIPTION (or translations from DESCRIPTION_I18N) to the user.

8.2.3.2. The FORM Type

When using KYC checks of type “FORM”, the KYC-SPA will show different forms based on “FORM_NAME” while also showing the user the text from DESCRIPTION as instructions.

Some of the forms may be further parameterized via the context in which the form is executed.

Note

For build-in forms, it should in the future not be necessary to specify the context requirements via REQUIRES as the KYC SPA should inform the exchange about the requirements of each form automatically. However, this is not yet implemented, see #9187.

When forms are submitted, the exchange converts the form data into key-value pairs where the key is the form field name and the value is of type KycStructuredFormData. The respective AML program can then evaluate the data from the form submission from attributes.

interface KycStructuredFormData {

  // Content type. Missing if unknown.
  content_type?: string;

  // Name of the uploaded file. Missing if unknown
  // or this was not a file upload.
  filename?: string;

  // Base32-encoded binary form value. Only present
  // if form data was determined to be binary data.
  data?: string;

  // Text form value. Only present if the form data
  // was determined to be in textual format.
  text?: string;

}

8.2.4. Configuration of legitimization rules

The configuration also must specify a set of legitimization rules, one per configuration section. Each rule specifies the condition and the measure the condition triggers:

[kyc-rule-$RULE_NAME]

# Operation that triggers this rule.
# Must be one of WITHDRAW, DEPOSIT, MERGE,
# AGGREGATE or BALANCE.
OPERATION_TYPE = WITHDRAW

# Space-separated list of next measures to be performed.
# The SPA should display *all* of these measures to the user.
# (They have a choice of either which ones, or in
# which order they are to be performed.)
# A special measure name "verboten" is used if the
# specified threshold may never be crossed
# (under this set of rules).
NEXT_MEASURES = SWISSNESS KYB

# "YES" if all NEXT_MEASURES will eventually need
# to be satisfied, "NO" if the user has a choice between
# them. Not actually enforced by the exchange, but
# primarily used to inform the user whether this is
# an "and" or "or". YES for "and".
IS_AND_COMBINATOR = YES

# YES if the rule (specifically, operation type,
# threshold, timeframe) and the general nature of
# the next measure (verboten or approval required)
# should be exposed to the client.
# Defaults to NO if not set.
EXPOSED = YES

# Threshold amount above which the rule 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.
# Ignored for WALLET-BALANCE.  Can be 'forever'.
TIMEFRAME = 30 days

# Set to YES to enable the rule (default is NO)
ENABLED = NO

8.2.5. Configuration of AML programs

AML decision processes are automatically executed under certain configurable conditions. Basically, any AML program that is run can flag an account for investigation by setting the respective flag in the AmlOutcome. Once this happens, the respective account will be highlighted to AML staff. The AML program can also limit the operations of the account by setting arbitrary thresholds for the various operations (including freezing the account by setting the thresholds to zero). AML staff investigating the account can then request further documentation or set new rules, including new thresholds.

AML programs can base their decisions on arbitrary attributes created by the KYC check (such as the user being a politically exposed person).

AML programs will be given the KYC attributes in JSON format on standard input, and must output the AmlOutcome. If AML programs fail (return non-zero status codes), a FALLBACK measure is automatically triggered.

AML programs are listed in the configuration file, one program per section:

[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

8.2.5.1. Implementing your own AML programs

When implementing an AML program, developers must ensure to provide three main execution paths:

  • Generate a list of required context field names for the helper (introspection!) when given the “-r” 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!) when given the “-a” 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.

AML programs will be given the exchange’s configuration filename with the “-c FILENAME” command-line option. They may or may not use the configuration file as they see fit. AML programs should support the canonical “-h” (help) and “-v” (version) command-line options to display a help text and their version number respectively. AML programs may be given the “-V” option to put them into verbose mode, suggesting that they should log additional information to standard error.

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.

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 "-r" from the
  // AML program's command-line option.
  context?: Object;

  // JSON object that captures the
  // output of a [kyc-provider-] or (HTML) FORM.
  // In the case of KYC data provided by providers,
  // 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.
  // In the case of KYC data provided by HTML FORMs, the
  // keys will match the HTML FORM field names and
  // the values will use the KycStructuredFormData
  // encoding.
  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[];

}
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;
}
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, 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;
}
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.

8.2.6. Configuration of measures

Finally, the configuration specifies a set of original measures one per configuration section:

[kyc-measure-$MEASURE_NAME]

# Possible check for this measure.  Optional.
# If not given (or set to "SKIP"), 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 name to run on the context and check data to
# determine the outcome and next measure.
# Refers to a ``[aml-program-$PROG_NAME]`` section name.
PROGRAM = taler-aml-program

If CHECK_NAME is set to “SKIP” (or is not 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 in the configuration is not complete: AML staff may freely define new measures dynamically, usually by selecting checks, an AML program, and providing context. The measures specified in the configuration are called the original measures and only those can be used as FALLBACK measures.

8.3. 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 new rules for the respective user. They may request additional KYC data from the consumer, can change the thresholds up to which amounts transactions are allowed, and associate properties with the account that AML programs (and AML officers) may interpret for arbitrary future actions.

8.3.1. AML Officer Setup

To begin the AML setup, AML staff should launch the GNU Taler exchange AML SPA Web interface by going to the /aml-spa/ endpoint of the exchange. This is generally a public endpoint, but of course an operator may restrict access via the reverse proxy. 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:

[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:

[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

8.3.2. AML Forms

AML forms are defined by the dynamic forms design document. The shipped implementation with of the exchange is installed in

${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.

8.3.3. AML Measures

The exchange configuration specifies a set of original measures one per configuration section:

[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.

8.4. KYC Process 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 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.

8.4.1. 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

8.4.2. 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

8.4.3. 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

8.4.4. 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

8.4.5. 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

8.4.6. 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

8.4.7. 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

8.4.8. 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

8.4.9. 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

8.4.10. 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

8.4.11. 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

8.4.12. 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

8.4.13. 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

8.4.14. 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

8.4.15. 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

8.4.16. 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