summaryrefslogtreecommitdiff
path: root/design-documents/023-taler-kyc.rst
blob: 91489fc404b13e5e72d8d800e7fdf0f504c79c3f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
DD 023: Taler KYC
#################

Summary
=======

This document discusses the Know-your-customer (KYC) processes supported by Taler.


Motivation
==========

To legally operate, Taler has to comply with KYC regulation that requires
banks to identify parties involved in transactions at certain points.


Requirements
============

Taler needs to run KYC checks in the following circumstances:

* Customer withdraws money over a monthly threshold

  * exchange triggers KYC
  * key: IBAN (encoded as payto:// URI)

* Wallet receives (via refunds) money resulting in a balance over a threshold

  * this is a client-side restriction
  * key: reserve (=KYC account) long term public key per wallet (encoded as payto:// URI)

* Wallet receives money via P2P payments

  * key: reserve (=KYC account) long term public key per wallet (encoded as payto:// URI)

* Merchant receives money (Q: any money, or above a monthly threshold?)

  * key: IBAN (encoded as payto:// URI)



Proposed Solution
=================

Exchange modifications
^^^^^^^^^^^^^^^^^^^^^^

We introduce a new ``wire_targets`` table into the exchange database. This
table is referenced as the source or destination of payments (regular deposits
and also P2P payments).  A positive side-effect is that we reduce duplication
in the ``reserves_in``, ``wire_out`` and ``deposits`` tables as they can
reference this table.  In this table, we additionally store information
related to the KYC status of the underlying payto://-URI.

A new ``/kyc/`` endpoint is based on the ``wire_targets`` serial
number. Access is ``authenticated`` by also passing the hash of the
payto://-URI (weak authentication is acceptable, as the KYC status or the
ability to initiate a KYC process are not very sensitive).  Given this pair,
the ``/kyc/`` endpoint returns either the (positive) KYC status or redirects
the client (302) to the current stage of the KYC process.  The redirection is
offered using an HTTP-redirect for Web-based clients and a JSON body with
information for triggering App-based KYC (via intents or opening some new type
of KYC URI).

A new ``/management/kyc/`` endpoint is introduced that allows exchange
operators to reset the KYC status (using hash of payto:// as the key) of
certain account holders. Batch operations should likely be supported (where an
array of hashes is provided).  The handler should return the number of
accounts that were actually modified. The idea is that the bank would use this
to update its KYC records given an updated list of embargoed entities.
This will also require a new ``taler-exchange-offline`` subcommand.

When withdrawing, the exchange checks if the KYC status is acceptable.  If no
KYC was done and if either the amount withdrawn over the last X days exceeds
the threshold or the reserve received received a P2P transfer, then a ``202
Accepted`` is returned which redirects the consumer to the new ``/kyc/``
handler.

When depositing, the exchange checks the KYC status and if negative, returns an
additional information field that tells the merchant the ``wire_target_serial``
number needed to begin the KYC process (this is independent of the amount)
at the new ``/kyc/`` handler.

When tracking deposits, the exchange also adds the ``wire_target_serial`` to
the reply if the KYC status is negative.

The aggregator is modified to only create the ``wire_out`` record, but not
the ``prewire`` record.

A new ``taler-exchange-kyc`` process checks ``wire_out`` records for which the
``wire_target`` has the KYC status set to positive, and creates ``prewire``
records (and sets the ``wire_out`` to ``done``.

We may want to consider directly deleting prewire records instead of setting
them to ``finished`` in ``taler-exchange-transfer``.



Exchange database schema changes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Note that there is may be some slight complication in the migration as the
h_wire in deposits is salted, while the h_payto in the new wire_targets is
expected to be unsalted. So converting the existing information to create the wire_targets table will be tricky!

We can *either* not support a fully automatic migration, or do an "expensive"
migration with C logic (so not just SQL statements).

.. sourcecode:: sql

  -- Everything in one big transaction
  BEGIN;
  -- Check patch versioning is in place.
  SELECT _v.register_patch('exchange-TBD', NULL, NULL);
  --
  CREATE TABLE IF NOT EXISTS wire_targets
  (wire_target_serial_id BIGSERIAL UNIQUE
  ,h_payto BYTEA NOT NULL CHECK (LENGTH(h_payto)=64),
  ,payto_uri STRING NOT NULL
  ,kyc_ok BOOLEAN NOT NULL DEFAULT (false)
  ,PRIMARY KEY (h_wire)
  );
  COMMENT ON TABLE wire_targets
    IS 'All recipients of money via the exchange';
  COMMENT ON COLUMN wire_targets.payto_uri
    IS 'Can be a regular bank account, or also be a URI identifying a reserve-account (for P2P payments)';
  COMMENT ON COLUMN wire_targets.h_payto
    IS 'Unsalted hash of payto_uri';
  COMMENT ON COLUMN wire_targets.kyc_ok
    IS 'true if the KYC check was passed successfully';
  --
  -- NOTE: logic to fill wire_target missing, so this
  -- CANNOT work if the database contains any data!
  --
  ALTER TABLE wire_out
    ADD COLUMN wire_target_serial_id INT8 NOT NULL REFERENCES wire_targets (wire_target_serial_id),
    DROP COLUMN wire_target;
  COMMENT ON COLUMN wire_out.wire_target_serial_id
    IS 'Identifies the target bank account and KYC status';
  --
  ALTER TABLE reserves_in
    ADD COLUMN wire_source_serial_id INT8 NOT NULL REFERENCES wire_targets (wire_target_serial_id),
    DROP COLUMN sender_account_details;
  COMMENT ON COLUMN wire_out.wire_target_serial_id
    IS 'Identifies the target bank account and KYC status';
  --
  ALTER TABLE reserves_close
    ADD COLUMN wire_source_serial_id INT8 NOT NULL REFERENCES wire_targets (wire_target_serial_id),
    DROP COLUMN receiver_account;
  COMMENT ON COLUMN reserves_close.wire_target_serial_id
    IS 'Identifies the target bank account and KYC status. Note that closing does not depend on KYC.';
  --
  ALTER TABLE deposits
    ADD COLUMN wire_target_serial_id INT8 NOT NULL,
    DROP COLUMN h_wire,
    DROP COLUMN wire;
  COMMENT ON COLUMN deposits.wire_target_serial_id
    IS 'Identifies the target bank account and KYC status';
  -- Complete transaction
  COMMIT;


TODO: Check if we missed miss any tables to migrate!


Merchant modifications
^^^^^^^^^^^^^^^^^^^^^^

We introduce new ``kyc_status``, ``kyc_timestamp`` and ``kyc_serial`` fields
into a new table with primary keys ``exchange_url`` and ``account``.  This
status is updated whenever a deposit is created or tracked, or whenever the
mechant backend receives a ``/kyc/`` response from the exchange.  Initially,
``kyc_serial`` is zero, indicating that the merchant has not yet made any
deposits and thus does not have an account at the exchange.

A new private endpoint ``/kyc`` is introduced which allows frontends to
request the ``/kyc`` status of any configured account (including with long
polling).  If the KYC status is negative or the ``kyc_timestamp`` not recent
(say older than one month), the merchant backend will re-check the KYC status
at the exchange (and update its cached status).  The endpoint then returns
either that the KYC is OK, or information (same as from the exchange endpoint)
to begin the KYC process.

The merchant backend uses the new field to remember that a KYC is pending
(after ``/deposit``, or tracing deposits) and the SPA then shows a
notification whenever the staff is logged in to the system.  The notification
can be hidden for the current day (remembered in local storage).

The notification links to a (new) KYC status page. When opened, the KYC status
page first re-checks the KYC status with the exchange.  If the KYC is still
unfinished, that page contains another link to begin/resume the KYC process,
otherwise it shows that the KYC process is done. If the KYC is unfinished, the
SPA should use long-polling on the KYC status on this page to ensure it is
always up-to-date, and change to ``KYC satisfied`` should the long-poller
return with positive news.


Bank requirements
^^^^^^^^^^^^^^^^^

The exchange primarily requires two endpoints from the bank, one to initiate a
KYC process, and a second one to check if a KYC process has successfully
completed.  The latter must also be accessible to the auditor.  Here, the
exchange (or auditor) would preferably pass either the (1) hash of the
payto://-URI, (2) the payto://-URI itself, or (3) its ``kyc_serial`` into the
bank's KYC APIs.  (1) is likely best as it contains the least sensitive
information in the request and still allows the auditor to make a semantically
meaningful request to the bank's KYC API; (2) may allow the bank to extract
the IBAN, which could be helpful in certain cases but might limit the
introduction of new URI schema and may leak information; (3) is most compact
in terms of what the bank would have to persist with its KYC data, but would
theoretically allow the exchange to manipulate indices to not pay out certain
customers despite them having done KYC without easy detection by the auditor.

If possible, the endpoint to check if a KYC process has finished would
support long-polling.

If possible, the process that allows the user to perform the KYC would
allow us to trigger the exchange's ``/kyc/`` endpoint at the end, to
allow the exchange to immediately learn about the completed process.



Alternatives
============

We could have some (new?) component periodically re-check the KYC status of
all targets for which the status is currently negative (and for which we have
pending outgoing transactions). However, this may just put undue burden on the
bank's KYC API.

The bank's KYC API could return a unique identifier to the exchange whenever
it initiates a KYC process, and the exchange could store that ID. However,
this may create issues if multiple KYC processes are initiated for the same
entity (and this is likely not detectable by the bank at that time). Also,
this information would again not be reasonably verifiable by the auditor.

The exchange could periodically check the KYC status with the bank instead of
relying on the proposed explicit management KYC revocation API. This may make
revocation simpler for the bank, but OTOH may create many additional useless
API requests to the bank's KYC infrastructure.



Drawbacks
=========

If the bank fails to trigger the ``/kyc/`` handler of the exchange after
completion of the KYC process, the exchange will not notice the status change
until the merchant backend makes the next inquiry (which would usually have to
be triggered manually by the merchant).

The solution may require changes to the bank's existing KYC implementation to
store the KYC status under the (hash of the) payto://-URI.


Discussion / Q&A
================

(This should be filled in with results from discussions on mailing lists / personal communication.)