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.3. Legal conditions for using the service¶
The service has well-known API endpoints to return its legal conditions to the user in various languages and various formats. This section describes how to setup and configure the legal conditions.
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 theTERMS_ETAG
, you MUST also provide the respective files inTERMS_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 thePRIVACY_ETAG
, you MUST also provide the respective files inPRIVACY_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.6. Legal policies directory layout¶
The TERMS_DIR
and PRIVACY_DIR
directory structures must follow a
particular layout. You may use the same directory for both the terms of
service and the privacy policy, as long as you use different ETAGs. Inside of
the directory, there should be sub-directories using two-letter language codes
like “en”, “de”, or “jp”. Each of these directories would then hold
translations of the current terms of service into the respective language.
Empty directories are permitted in case translations are not available.
Then, inside each language directory, files with the name of the value set as
the TERMS_ETAG
or PRIVACY_ETAG
must be provided. The extension of each
of the files should be typical for the respective mime type. The set of
supported mime types is currently hard-coded in the service, and includes
“.epub”, “.html”, “.md”, “.pdf” and “.txt” files. If other files are present,
the service may show a warning on startup.
8.1.6.1. Example¶
A sample file structure for a TERMS_ETAG
of “tos-v0” would be:
TERMS_DIR/en/tos-v0.txt
TERMS_DIR/en/tos-v0.html
TERMS_DIR/en/tos-v0.pdf
TERMS_DIR/en/tos-v0.epub
TERMS_DIR/en/tos-v0.md
TERMS_DIR/de/tos-v0.txt
TERMS_DIR/de/tos-v0.html
TERMS_DIR/de/tos-v0.pdf
TERMS_DIR/de/tos-v0.epub
TERMS_DIR/de/tos-v0.md
If the user requests an HTML format with language preferences “fr” followed by
“en”, the service would return TERMS_DIR/en/tos-v0.html
lacking a version in
French.
8.1.7. Generating the Legal Terms¶
The taler-terms-generator
script can be used to generate directories with
terms of service and privacy policies in multiple languages and all required
data formats from a single source file in .rst
format and GNU gettext
translations in .po
format.
To use the tool, you need to first write your legal conditions in English in
reStructuredText (rst). You should find a templates in
$PREFIX/share/terms/*.rst
where $PREFIX
is the location where you
installed the service to. Whenever you make substantive changes to the legal
terms, you must use a fresh filename and change the respective ETAG
. The
resulting file must be called $ETAG.rst
and the first line of the file should be the title of the document.
Once you have written the $ETAG.rst
file in English, you can
generate the first set of outputs:
$ taler-terms-generator -i $ETAG
Afterwards, you should find the terms in various formats for all configured
languages (initially only English) in $PREFIX/share/terms/
. The generator
has a few options which are documented in its man page.
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.1.9. Updating legal documents¶
When making minor changes without legal implications, edit the .rst
file,
then re-run the step to add a new language for each existing translation to
produce an updated .po
file. Translate the sentences that have changed and
finally run the generator (without -l
) on the ETAG (-i $ETAG
) to
create the final files.
When making major changes with legal implications, you should first rename (or
copy) the existing .rst
file and the associated translation files to a new
unique name. Afterwards, make the major changes, update the .po
files,
complete the translations and re-create the final files. Finally, do not
forget to update the ETAG
configuration option to the new name and to
restart the service.
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:
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
orheld-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
:
[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:
[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.
[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.
[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.
[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.3.3. The LINK Type¶
When using KYC checks of type “FORM”, the KYC-SPA will show a link that allows the user to begin the KYC process at an external provider under the given DESCRIPTION.
The external providers are expected to yield KYC attributes in the form of
key-value pairs where the list of key is defined in the GANA
gnu-taler-kyc-attributes
registry, which also defines the format of each
attribute. External providers may not directly yield attributes using the
correct encodings, thus converter helper programs are typically used to convert
external attribute data into the standardized format.
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.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.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