summaryrefslogtreecommitdiff
path: root/design-documents/023-taler-kyc.rst
blob: c20844b3feff714f0c4f0fbe854db4dc45c1a1cf (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
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
DD 23: Taler KYC
################

Summary
=======

This document discusses the Know-your-customer (KYC) and Anti-Money Laundering
(AML) processes supported by Taler.


Motivation
==========

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


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

Taler needs to take *measures* based on the following primary *triggers*:

* 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

  * there are two sub-cases: PUSH and PULL 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)

* Reserve is "opened" for invoicing.

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

* Import of new sanctions lists and triggering of measures against matches of existing
  customer records against the list

For the different operation types, there can be both soft and hard
limits. Soft limits are those that the customer may raise by providing data
and passing KYC checks.  Hard limits cannot be lifted, for example because an
exchange forbids crossing those limits in its terms of service for all
customers.


Process requirements
^^^^^^^^^^^^^^^^^^^^

The key consideration here is *plausibilization*: staff needs to
check that the client-provided information is plausible. As this
is highly case-dependent, this cannot be automated.

For the different *measures*, there are various different possible KYC/AML
*checks* that could happen:

* In-person validation by AML staff
* Various forms to be filled by AML staff
* Validation involving local authorities and post-office
* Online validation, sometimes with multiple options (like KYC for multiple people):

  * Forms to be supplied by user (different types of ID)
  * Interactive video
  * Documents to be supplied (business register)
  * Address validation (e-mail or phone or postal)

Additionally, the process is dynamic and conditional upon various decisions:

* Individual vs. business
* PEP or non-PEP
* Hit on sanctions list
* Type of business (trust, foundation, listed on stock market, etc.)
* Need for plausibilization (via documents by user or staff research)
* Periodic updates (of customer data, of sanction lists) and re-assessment

There are also various *outcomes*:

* normal operation (with expiration date)
* normal operation but with AML staff investigating (new measure)
* held, requesting customer documentation (new measure)
* held, AML staff reviewing evidence for plausibilization (new measure)
* automatically frozen until certain day (due to sanctions)
* institutionally frozen until certain day (due to order by state authority)
* operation is categorically not allowed (at least above certain limits)

Outcomes may also be (partially) public, that is exposed to the client. For
example, we may want to tell a wallet that it has hit a hard withdraw limit,
but might succeed at withdrawing a smaller amount.

The outcome of a *check* can set new rules or trigger another *measure* (the
latter is conditional on reaching the expiration time of the outcome).

As a result, we largely end up in a large state machine where the AML staff has
serious flexibiltiy while the user needs guidance as to the possible next moves
and/or to the current state of their account (where some information must not be
disclosed).


Documentation requirements
^^^^^^^^^^^^^^^^^^^^^^^^^^

For each account we must:

* define risk-profile (902.4, 905.1)
* document the specific setup, likely not just the INI file
* should have some key Anti-Money-Laundering Act (AMLA)
  file attributes, such as:

  * File opened, file closed (keep data for X years afterwards!)
  * low-risk or high-risk business relationship
  * PEP status
  * business domain
  * authority notification dates (possibly multiple) with
    voluntary or mandatory notification classification

Finally, we need to produce statistics:

* There must be a page with an overview of AMLA files with opening
  and closing dates and an easy way to determine for any day the
  number of open AMLA files
* Technically, we also need a list of at-risk transactions and of
  frozen transactions, but given that we can really only freeze
  on an account-basis, I think there is nothing to do here
* number of incidents reported (voluntarily, required)
* number of business relationships at any point in time
* number of risky business relationships (PEP, etc.)
* number of frozen transactions (authority vs. sanction) with start-date and end-date
* start-data and end-date of relationships (data retained for X years after end of relationship)

For this high-level monitoring, we need certain designated critical events to
be tracked in the system statistics:

* account opened
* set to high risk
* set to low risk
* suspicious activity report filed with authority
* account frozen
* account unfrozen
* account closed
* sanction list import / update


TODO: Sanction lists
^^^^^^^^^^^^^^^^^^^^

We need to be able to import new sanction lists (whenever they are published)
and then check existing AMLA files against those lists.  Additionally, newly
created AMLA files must be checked against the current list and some "measure"
applied in case of a match.

This will primarily require us to define an endpoint to upload a sanction list
and to define a new table to track the list of sanctioned entities.  As it is
expected that sanction lists will not permit fully automated determinations in
all cases, an external "sanction check" program should be configured which
compares records against the current list and determines the correct measure,
such as no change, further manual review by AML staff, or even automatic
freeze (and report) depending on how well the records match.

Basically, the "sanction check" program takes the sanction list and an
attribute set to compute the same kind of `AmlOutcome` that an AML program
outputs given a context and an attribute set.


Security requirements
^^^^^^^^^^^^^^^^^^^^^

IBANs are predictable. We (probably) do not want random people to be able to
initate KYC processes for other parties. Similarly, the attestation API
requires us to somehow *authenticate* the user to ensure we only give out
attestation data to the data subject themselves. For P2P payments and
withdrawals, we have the reserve public key that is only known to the data
subject and thus can be used to authenticate the client via a signature. Only
pure deposits (by merchants or directly from a wallet) are a problem as the
only thing we know about the receiver is the IBAN at that time, and literally
any user could just deposit money into some bank account, so knowledge of the
IBAN is insufficient to determine that we actually are communicating with the
owner of the bank account.


Further considerations
^^^^^^^^^^^^^^^^^^^^^^

On top of all of this, we need to plan some *diagnostics* to determine when
components fail (such as scripts or external services providing malformed
results).

Optionally, in the future, the solution should support fees to be paid by the
user for *voluntary* KYC processes related to attestation (#7365).


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

The main state of an account is represented by a set of `KycRules` (the
`LegitimizationRuleSet`) which specify the current *rules* to apply to
transactions involving the account. Rules can *exposed* to the account owner,
or can be secret.  Each *rule* specifies certain *conditions* which, if met,
*trigger* a single specific *measure*.  After a *rule* was *triggered* and
before the *outcome* of the respective *measure* has been produced (say
because the user did not yet enter their data or the AML officer is still
reviewing the case), the existing rules remain in force. Rules have a display
priority, and if a second rule with a higher display priority is also
triggered, the *measures* of the higher-priority rule become the active
*measures*.  Except for the default rule set, every legitimization rule set
also has an *expiration* time after which a successor *measure* (or the
default rule set) is automatically triggered.

For any possible *measures*, we define:

* Contextual input data to be provided (with dynamic inputs,
  e.g. amount set dynamically based on the *trigger* could be
  in the context)
* A *check* to be performed (checks can be user-interactive (LINK, FORM)
  or staff-interactive (INFO))
* A fallback *measure* to take on failure of a user-interactive check
  (if the check fails, we cannot run the AML *program* as required inputs
  might be missing!)
* An (AML) *program* that uses *attribtes* from the *check* as well as
  *context* data to determine an *outcome* represented as the
  `AmlOutcome`.

"verboten" is the name of a special *measure*, which means that crossing the
respective transaction threshold is categorically not allowed (for this
account).  "verboten" with a threshold of zero can be used to freeze funds.

Possible *outcomes* of a measure include:

* The next operational state (normal, AML investigation) of the account
  (basically, whether to add it to the work list of AML staff).
* A new set of *rules* in the form of a `LegitimizationRuleSet` that
  determines custom rules to apply to transactions involving the account;
  such rules may be used to block certain transactions by using the
  "verboten" measure.  The `LegitimizationRuleSet` also must specify
  an *expiration* time by which we fall back to a successor measure
  *or* to the default rules.
* A (largely) free-form set of `AccountProperties` that AML staff can
  use to tag accounts with. Some default properties are defined, but
  the exchange does not do anything with these and AML SPAs are free to
  use any properties they like.  Account properties are only exposed
  to AML staff and never to the customer.
* A set of *events* that are to be added to the timeline of the
  operator for statistical purposes.

For the user-interactive *checks* we need a KYC SPA that is given:

* instructions to render (with either a form to fill or links to external checks);
  here the context could provide an array of choices!
* possibly an external check that was set up (if any); for cost-reasons, we
  should only do one at a time, and probably should then always redirect the
  browser to that check.

For the staff-interactive *checks* we need an AML SPA:

* to file forms and upload documentation (without state transition)
* to decide on next measure (providing context); here, the exchange needs
  to expose the list of available *measures* and required *context* for each

We need some customer-driven interactivity in KYB/KYC process, for example the
user may need to be given choices (address vs. phone, individual vs. business,
order in which to provide KYC data of beneficiaries). As a result, the
exchange needs to serve some SPA for *measures* where the user is shown the
next step(s) or choices (which person to collect KYC data on, whether to run
challenger on phone number of physical address, etc.).  The SPA should also
potentially contain a form to allow the customer to directly upload documents
to us (like business registration) instead of to some KYC provider. This is
because KYC providers may not be flexible enough.  The SPA should also allow
the customer to perform KYC checks voluntarily.

Similarly, the AML staff will need to be able to trigger rather complex
KYB/KYC processes, like "need KYC on X and Y and Z" or "phone number or
mailing address" or "please upload form A/T/S".  Here in particular it
should be possible to request not only filled forms, but arbitrary
documents.


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

* **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), P2P receive (by
  wallet) and (high) wallet balance.


Account owner authentication
^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Access to the KYC SPA (or rather, its account-specific state) is controlled by
a *target token* (which is effectively like a bearer token, except passed
inside the URL).  The *target token* ensures that only the account owner has
access to the KYC processes.  It can be obtained by authenticating using
either the merchant private key or reserve private key, depending on the type
of the account (IBAN or wallet-reserve respectively).

When we need to authenticate a bank account owner, we will simply require them
to make an outgoing wire transfer into the exchange bank account with a public
key in the wire transfer subject (just like when withdrawing), but augmented
with the string "KYC" so we can distinguish the wire transfer from a regular
withdrawal. Typically, we would put the merchant public key into the wire
transfer subject; wallets MAY put their long-term reserve public key instead.
The amount to be transferred is the *KYC fee*.

This has several advantages:

* Only the account owner can provide us with the public key, so we already
  have also one super-hard piece of KYC evidence.
* If the account owner looses their public key, it's not a problem: they
  would just have to do the transfer again with a new key. No need for
  us to do any kind of intervention for key management.
* We could theoretically get paid to do the KYC process, or just "charge" a
  nominal amount.
* This also somewhat addresses the payment for voluntary KYC processes where
  a merchant wants to do KYC to get us to attest their identity for their
  customers even if we do not yet have a legal need.  The only issue here
  is that this does not work if voluntary KYC is invoiced while mandatory
  KYC is gratis. But, that kind of configuration is a business decision
  and there is no hard need to support it immediately.
* This definitively addresses the need for authentication to access the
  attestation API, which so far was only available for P2P payments as
  we could not authenticate merchants.
* The "KYC" string allows us to distinguish the authentication transfers from
  withdrawal transfers; by keeping the KYC fee at or below the closing fee,
  we can even deploy this without fully updating the logic everywhere to
  distinguish KYC transfers

TODO: update wire gateway specification, update/new tables for KYC wire
transfers, update API spec for attestation, update exchange API (below) to
signal need for auth-payment via wire transfer, update merchant logic to
expose merchant public key to SPA for wire transfer if needed for KYC.


451 Response
^^^^^^^^^^^^

When KYC operations are required, various endpoints may respond with a
``451 Unavailable for Legal Reasons`` status code and a `KycNeededRedirect`
body.

  .. ts:def:: KycNeededRedirect

    interface KycNeededRedirect {

      // Numeric `error code <error-codes>` unique to the condition.
      // Should always be ``TALER_EC_EXCHANGE_GENERIC_KYC_REQUIRED``.
      code: number;

      // Human-readable description of the error, i.e. "missing parameter",
      // "commitment violation", ...  Should give a human-readable hint
      // about the error's nature. Optional, may change without notice!
      hint?: string;

      // Hash of the payto:// account URI for which KYC
      // is required.
      h_payto: PaytoHash;

      // Public key associated with the account. The client must sign
      // the initial request for the KYC status using the corresponding
      // private key.  Will be either a reserve public key or a merchant
      // (instance) public key.
      //
      // Absent if no public key is currently associated
      // with the account and the client MUST thus first
      // credit the exchange via an inbound wire transfer
      // to associate a public key with the debited account.
      account_pub?: EddsaPublicKey;

      // Identifies a set of measures that were triggered and that are
      // now preventing this operation from proceeding.  Gives the
      // account holder a starting point for understanding why the
      // transaction was blocked and how to lift it. The account holder
      // should use the number to check for the account's AML/KYC status
      // using the ``/kyc-check/$REQUIREMENT_ROW`` endpoint.
      requirement_row: Integer;

    }


New endpoints
^^^^^^^^^^^^^

.. http:get:: /kyc-check/$REQUIREMENT_ROW

  Checks the KYC status of a particular payment target and possibly begins a
  KYC process by allowing the customer to choose the next KYC measure to
  satisfy. This endpoint is typically used by wallets or merchants that
  have been told that a transaction is not happening because it triggered
  some KYC/AML measure and now want to check how the KYC/AML
  requirement could be fulfilled (or whether it already has been
  statisfied and the operation can now proceed).  Long-polling may be used
  to instantly observe a change in the KYC requirement status.

  The requirement row of the ``/kyc-check/`` endpoint encodes the
  legitimization measure's serial number.  It is returned in
  `KycNeededRedirect` responses via the ``requirement_row`` field.

  Given a valid pair of requirement row and account owner signature, the
  ``/kyc-check/`` endpoint returns either just the KYC status or redirects the
  client (202) to the next required stage of the KYC process.  The redirection
  must be for an HTTP(S) endpoint to be triggered via a simple HTTP GET.  It
  must always be the same endpoint for the same client, as the wallet/merchant
  backend are not required to check for changes to this endpoint.  Clients
  that received a 202 status code may repeat the request and use long-polling
  to detect a change of the HTTP status.

  **Request:**

  *Account-Owner-Signature*:

    The client must provide Base-32 encoded EdDSA signature with
    ``$ACCOUNT_PRIV``, affirming the desire to obtain KYC data.  Note that
    this is merely a simple authentication mechanism, the details of the
    request are not protected by the signature.  The ``$ACCOUNT_PRIV`` is
    either the (wallet long-term) reserve private key or the merchant instance
    private key.

  :query timeout_ms=NUMBER: *Optional.*  If specified, the exchange will
    wait up to ``timeout_ms`` milliseconds if the requirement continues
    to be mandatory provisioning of KYC data by the client.
    Ignored if the HTTP status code is already ``200 Ok``.  Note that
    clients cannot long-poll for AML staff actions, so status information
    about an account being under AML review needs to be requested
    periodically.

  **Response:**

  :http:statuscode:`200 Ok`:
    No mandatory KYC actions are required by the client at this time.
    The client *may* still visit the KYC URL to initiate voluntary checks.
    The response will be an `AccountKycStatus` object which specifies
    restrictions that currently apply to the account. If the
    client attempts to exceed *soft* limits, the status may change
    to a ``202 Accepted``.  Hard limits cannot be lifted by passing KYC checks.
  :http:statuscode:`202 Accepted`:
    The account holder performed an operation that would have crossed
    *soft* limits and must be redirected to the provided location to perform
    the required KYC checks to satisfy the legal requirements. Afterwards, the
    ``/kyc-check/`` request should be repeated to check whether the
    user has completed the process.
    The response will be an `AccountKycStatus` object.
  :http:statuscode:`204 No content`:
    The exchange is not configured to perform KYC and thus
    the legal requirements are already satisfied.
  :http:statuscode:`403 Forbidden`:
    The provided signature is not acceptable for the requirement row.
  :http:statuscode:`404 Not found`:
    The requirement row is unknown.

  **Details:**

  .. ts:def:: AccountKycStatus

    interface AccountKycStatus {

      // Current AML state for the target account.  True if
      // operations are not happening due to staff processing
      // paperwork *or* due to legal requirements (so the
      // client cannot do anything but wait).
      //
      // Note that not every AML staff action may be legally
      // exposed to the client, so this is merely a hint that
      // a client should be told that AML staff is currently
      // reviewing the account.  AML staff *may* review
      // accounts without this flag being set!
      aml_review: boolean;

      // Access token needed to construct the ``/kyc-spa/``
      // URL that the user should open in a browser to
      // proceed with the KYC process (optional if the status
      // type is ``200 Ok``, mandatory if the HTTP status
      // is ``202 Accepted``).
      access_token: AccountAccessToken;

      // Array with limitations that currently apply to this
      // account and that may be increased or lifted if the
      // KYC check is passed.
      // Note that additional limits *may* exist and not be
      // communicated to the client. If such limits are
      // reached, this *may* be indicated by the account
      // going into ``aml_review`` state. However, it is
      // also possible that the exchange may legally have
      // to deny operations without being allowed to provide
      // any justification.
      // The limits should be used by the client to
      // possibly structure their operations (e.g. withdraw
      // what is possible below the limit, ask the user to
      // pass KYC checks or withdraw the rest after the time
      // limit is passed, warn the user to not withdraw too
      // much or even prevent the user from generating a
      // request that would cause it to exceed hard limits).
      limits?: AccountLimit[];

    }

  .. ts:def:: AccountLimit

    interface AccountLimit {

      // Operation that is limited.
      // Must be one of "WITHDRAW", "DEPOSIT", "P2P-RECEIVE"
      // or "WALLET-BALANCE".
      operation_type: string;

      // Timeframe during which the limit applies.
      timeframe: RelativeTime;

      // Maximum amount allowed during the given timeframe.
      // Zero if the operation is simply forbidden.
      threshold: Amount;

      // True if this is a soft limit that could be raised
      // by passing KYC checks.  Clients *may* deliberately
      // try to cross limits and trigger measures resulting
      // in 451 responses to begin KYC processes.
      // Clients that are aware of hard limits *should*
      // inform users about the hard limit and prevent flows
      // in the UI that would cause violations of hard limits.
      soft_limit: boolean;
    }


.. http:get:: /kyc-spa/$ACCESS_TOKEN
.. http:get:: /kyc-spa/$FILENAME

  A set of ``/kyc-spa/$ACCESS_TOKEN`` GET endpoints is created per account
  hash that serves the KYC SPA.  This is where the ``/kyc-check/`` endpoint
  will in principle redirect clients.  The KYC SPA will use the
  ``$ACCESS_TOKEN`` of its URL to initialize itself via the
  ``/kyc-info/$ACCESS_TOKEN`` endpoint family.  The KYC SPA may download
  additional resources via ``/kyc-spa/$FILENAME``. The filenames must not
  match base32-encoded 256-bit values.

.. http:get:: /kyc-info/$ACCESS_TOKEN

  The ``/kyc-info/$ACCESS_TOKEN`` endpoints are created per client
  account hash (but access controlled via a unique target token)
  to return information about the state of the KYC or AML process
  to the KYC SPA.  The SPA uses this information to show the user an
  appropriate dialog. The SPA should also long-poll this endpoint for changes
  to the AML/KYC state. Note that this is a client-facing endpoint, so it will
  only provide a restricted amount of information to the customer (as some
  laws may forbid us to inform particular customers about their true status).
  The endpoint will typically inform the SPA about possible choices to
  proceed, such as directly uploading files, contacting AML staff, or
  proceeding with a particular KYC process at an external provider (such as
  Challenger).  If the user chooses to initate a KYC process at an external
  provider, the SPA must request the respective process to be set-up by the
  exchange via the ``/kyc-start/`` endpoint.

  **Request:**

  *If-None-Match*:
    The client MAY provide an ``If-None-Match`` header with an ETag.

  :query timeout_ms=MILLISECONDS:
    *Optional.* If specified, the exchange will wait up to MILLISECONDS for
    a change to a more recent legitimization measure before returning a 304
    Not Modified status.

  **Response:**

  :http:statuscode:`200 OK`:
    The body is a `KycProcessClientInformation`.

  *Etag*: Will be set to the serial ID of the measure. Used for long-polling.

  .. ts:def:: KycProcessClientInformation

    interface KycProcessClientInformation {

      // List of requirements.
      requirements?: { name : KycRequirementInformation};

      // True if the client is expected to eventually satisfy all requirements.
      // Default (if missing) is false.
      is_and_combinator?: boolean

      // List of available voluntary checks the client could pay for.
      // Since **vATTEST**.
      voluntary_checks?: { name : KycCheckInformation};
    }

  .. ts:def:: KycRequirementInformation

    interface KycRequirementInformation {

      // Which form should be used? Common values include "INFO"
      // (to just show the descriptions but allow no action),
      // "LINK" (to enable the user to obtain a link via
      // ``/kyc-start/``) or any build-in form name supported
      // by the SPA.
      form: string;

      // English description of the requirement.
      description: string;

      // Map from IETF BCP 47 language tags to localized
      // description texts.
      description_i18n ?: { [lang_tag: string]: string };

      // ID of the requirement, useful to construct the
      // ``/kyc-upload/$ID`` or ``/kyc-start/$ID`` endpoint URLs.
      // Present if and only if "form" is not "INFO".  The
      // ``$ID`` value may itself contain ``/`` or ``?`` and
      // basically encode any URL path (and optional arguments).
      id?: string;

    }

  .. ts:def:: KycCheckInformation

    // Since **vATTEST**.
    interface KycCheckInformation {

      // English description of the check.
      description: string;

      // Map from IETF BCP 47 language tags to localized
      // description texts.
      description_i18n ?: { [lang_tag: string]: string };

    }

  :http:statuscode:`204 No Content`:
    There are no open KYC requirements or possible voluntary checks
    the client might perform.

  :http:statuscode:`304 Not Modified`:
    The KYC requirements did not change.


.. http:post:: /kyc-upload/$ID

  The ``/kyc-upload/$ID`` POST endpoint allows the SPA to upload
  client-provided evidence.  The ``$ID`` will be provided as part of the
  ``/kyc-info`` body.  This is for checks of type ``FORM``.  In practice,
  ``$ID`` will encode both the ``$ACCESS_TOKEN`` and the index of the selected
  measure (but this should be irrelevant for the client).

  **Request:**

  Basically oriented along the possible formats of a HTTP form being
  POSTed. Details will depend on the form. The server will try to decode the
  uploaded body from whatever format it is provided in.

  **Response:**

  :http:statuscode:`204 No Content`:
    The information was successfully uploaded. The SPA should fetch
    an updated ``/kyc-info/``.
  :http:statuscode:`404 Not Found`:
    The ``$ID`` is unknown to the exchange.
  :http:statuscode:`409 Conflict`:
    The upload conflicts with a previous upload.
  :http:statuscode:`413 Content Too Large`:
    The body is too large.

.. http:post:: /kyc-start/$ID

  The ``/kyc-start/$ID`` POST endpoint allows the SPA to set up a new external
  KYC process. It will return the URL that the client must GET to begin the
  KYC process. The SPA should probably open this URL in a new window or tab.
  The ``$ID`` will be provided as part of the ``/kyc-info`` body.  In
  practice, ``$ID`` will encode both the ``$ACCESS_TOKEN`` and the index of
  the selected measure (but this should be irrelevant for the client).

  **Request:**

  Use empty JSON body for now.

  **Response:**

  :http:statuscode:`200 Ok`:
    The KYC process was successfully initiated. The URL is in a
    `KycProcessStartInformation` object.

  **Details:**

  .. ts:def:: KycProcessStartInformation

    interface KycProcessStartInformation {

      // URL to open.
      redirect_url: string;
    }

  :http:statuscode:`404 Not Found`:
    The ``$ID`` is unknown to the exchange.

  .. note::

    As this endpoint is involved in every KYC check at the beginning, this
    is also the place where we could integrate the payment process for the KYC fee
    in the future (since **vATTEST**).


.. http:get:: /kyc-proof/$PROVIDER_SECTION?state=$H_PAYTO

  Upon completion of the process at the external KYC provider, the provider
  must redirect the client (browser) to trigger a GET request to a new
  ``/kyc-proof/$H_PAYTO/$PROVIDER_SECTION`` endpoint.  Once this endpoint is
  triggered, the exchange will pass the received arguments to the respective
  logic plugin.  The logic plugin will then (asynchronously) update the KYC
  status of the user.  The logic plugin should redirect the user to the KYC
  SPA. This endpoint deliberately does not use the ``$ACCESS_TOKEN`` as the
  external KYC provider should not learn that token.

  This endpoint is thus accessed from the user's browser at the *end* of a KYC
  process, possibly providing the exchange with additional credentials to
  obtain the results of the KYC process.  Specifically, the URL arguments
  should provide information to the exchange that allows it to verify that the
  user has completed the KYC process. The details depend on the logic, which
  is selected by the "$PROVIDER_SECTION".

  While this is a GET (and thus safe, and idempotent), the operation may
  actually trigger significant changes in the exchange's state.  In
  particular, it may update the KYC status of a particular payment target.

  **Request:**

  Details on the request depend on the specific KYC logic that was used.

  If the KYC plugin logic is OAuth 2.0, the query parameters are:

  :query code=CODE:
     OAuth 2.0 code argument.
  :query state=STATE:
     OAuth 2.0 state argument with the H_PAYTO.

  .. note::

    Depending on the OAuth variant used, additional
    query parameters may need to be passed here.

  **Response:**

  Given that the response is returned to a user using a browser and **not** to
  a Taler wallet, the response format is in human-readable HTML and not in
  machine-readable JSON.

  :http:statuscode:`302 Found`:
    The KYC operation succeeded and the
    payment target is now authorized to transact.
    The browser is redirected to a human-readable
    page configured by the exchange operator.
  :http:statuscode:`401 Unauthorized`:
    The provided authorization token is invalid.
  :http:statuscode:`404 Not found`:
    The payment target is unknown.
  :http:statuscode:`502 Bad Gateway`:
    The exchange received an invalid reply from the
    legitimization service.
  :http:statuscode:`504 Gateway Timeout`:
    The exchange did not receive a reply from the legitimization
    service within a reasonable time period.


.. http:get:: /kyc-webhook/$PROVIDER_SECTION/*
.. http:post:: /kyc-webhook/$PROVIDER_SECTION/*
.. http:get:: /kyc-webhook/$LOGIC/*
.. http:post:: /kyc-webhook/$LOGIC/*

  Alternatively, the KYC confirmation may be triggered by a ``/kyc-webhook``
  request. As KYC **providers** do not necessarily support passing detailed
  information in the URL arguments, the ``/kyc-webhook`` only needs to specify
  either the ``PROVIDER_SECTION`` *or* the ``LOGIC`` (the name of the plugin
  implementing the KYC API).  The API-specific webhook logic must then figure
  out what exactly the webhook is about on its own.  The ``/kyc-webhook/``
  endpoint works for GET or POST, again as details depend on the KYC provider.
  In contrast to ``kyc-proof``, the response does NOT go to the end-users'
  browser and should thus only indicate success or failure.

  **Request:**

  Details on the request depend on the specific KYC logic that was used.

  **Response:**

  :http:statuscode:`204 No content`:
    The operation succeeded.
  :http:statuscode:`404 Not found`:
    The specified logic is unknown.


.. http:post:: /kyc-wallet

  The ``/wallet-kyc`` POST endpoint allows a wallet to notify an exchange if
  it will cross a balance threshold.  Here, the ``balance`` specified should be
  the threshold (from the ``wallet_balance_limit_without_kyc`` array) that the
  wallet would cross, and *not* the *exact* balance of the wallet.  The exchange
  will respond with a wire target UUID. The wallet can then use this UUID to
  being the KYC process at ``/kyc-check/``. The wallet must only proceed to
  obtain funds exceeding the threshold after the KYC process has concluded.
  While wallets could be "hacked" to bypass this measure (we cannot
  cryptographically enforce this), such modifications are a terms of service
  violation which may have legal consequences for the user.

  Setup KYC identification for a wallet.  Returns the KYC UUID.  This endpoint
  is used by compliant Taler wallets when they are about to hit the balance
  threshold and thus need to have the customer provide their personal details
  to the exchange.  The wallet is identified by its long-lived reserve public
  key (which is used for P2P payments, not for withdrawals).

  **Request:**

  The request body must be a `WalletKycRequest` object.

  **Response:**

  :http:statuscode:`204 No Content`:
    KYC is disabled at this exchange, or the balance is below the
    threshold that requires KYC, or this wallet already satisfied
    the KYC check for the given balance.
  :http:statuscode:`403 Forbidden`:
    The provided signature is invalid.
    This response comes with a standard `ErrorDetail` response.
  :http:statuscode:`451 Unavailable for Legal Reasons`:
    The wallet must undergo a KYC check. A KYC ID was created.
    The response will be a `KycNeededRedirect` object.

  **Details:**

  .. ts:def:: WalletKycRequest

     interface WalletKycRequest {

      // Balance threshold (not necessarily exact balance)
      // to be crossed by the wallet that (may) trigger
      // additional KYC requirements.
      balance: Amount;

      // EdDSA signature of the wallet affirming the
      // request, must be of purpose
      // ``TALER_SIGNATURE_WALLET_ACCOUNT_SETUP``
      reserve_sig: EddsaSignature;

      // long-term wallet reserve-account
      // public key used to create the signature.
      reserve_pub: EddsaPublicKey;
    }


.. http:get:: /aml/$OFFICER_PUB/measures

  To enable the AML staff SPA to give AML staff a choice of possible measures, a
  new endpoint ``/aml/$OFFICER_PUB/measures`` is added that allows the AML SPA
  to dynamically GET the list of available measures.  It returns a list of known
  KYC checks (by name) with their descriptions and a list of AML programs with
  information about the required context.

  **Request:**

  *Taler-AML-Officer-Signature*:
    The client must provide Base-32 encoded EdDSA signature with
    ``$OFFICER_PRIV``, affirming the desire to obtain AML data.  Note that
    this is merely a simple authentication mechanism, the details of the
    request are not protected by the signature.

  **Response:**

  :http:statuscode:`200 Ok`:
    Information about possible measures is returned in a
    `AvailableMeasureSummary` object.

  **Details:**

  .. ts:def:: AvailableMeasureSummary

    interface AvailableMeasureSummary {

      // Available original measures that can be
      // triggered directly by default rules.
      roots: { "$measure_name" : MeasureInformation; };

      // Available AML programs.
      programs: { "$prog_name" : AmlProgramRequirement; };

      // Available KYC checks.
      checks: { "$check_name" : KycCheckInformation; };

    }

  .. ts:def:: MeasureInformation

    interface MeasureInformation {

      // Name of a KYC check.
      check_name: string;

      // Name of an AML program.
      prog_name: string;

      // Context for the check. Optional.
      context?: Object;

    }

  .. ts:def:: AmlProgramRequirement

    interface AmlProgramRequirement {

      // Description of what the AML program does.
      description: string;

      // List of required field names in the context to run this
      // AML program. SPA must check that the AML staff is providing
      // adequate CONTEXT when defining a measure using this program.
      context: string[];

      // List of required attribute names in the
      // input of this AML program.  These attributes
      // are the minimum that the check must produce
      // (it may produce more).
      inputs: string[];

    }

  .. ts:def:: KycCheckInformation

    interface KycCheckInformation {

      // Description of the KYC check.  Should be shown
      // to the AML staff but will also be shown to the
      // client when they initiate the check in the KYC SPA.
      description: string;
      description_i18n: {};

      // Names of the fields that the CONTEXT must provide
      // as inputs to this check.
      // SPA must check that the AML staff is providing
      // adequate CONTEXT when defining a measure using
      // this check.
      requires: string[];

      // Names of the attributes the check will output.
      // SPA must check that the outputs match the
      // required inputs when combining a KYC check
      // with an AML program into a measure.
      outputs: string[];

      // Name of a root measure taken when this check fails.
      fallback: string;
    }

.. http:get:: /aml/$OFFICER_PUB/kyc-statistics/$NAME

  Returns the number of KYC events matching the given event type ``$NAME`` in
  the specified time range.  Note that this query can be slow as the
  statistics are computed on-demand. (This is OK as such requests should be
  rare.)

  **Request:**

  *Taler-AML-Officer-Signature*:
    The client must provide Base-32 encoded EdDSA signature with
    ``$OFFICER_PRIV``, affirming the desire to obtain AML data.  Note that this
    is merely a simple authentication mechanism, the details of the request are
    not protected by the signature.

  :query start_date=TIMESTAMP:
    *Optional*. Specifies the date when to
    start looking (inclusive). If not given, the start time of the
    exchange operation is used.
  :query end_date=TIMESTAMP:
    *Optional*. Specifies the date when to
    stop looking (exclusive). If not given, the current date is used.

  **Response:**

  .. ts:def:: EventCounter

    interface EventCounter {
      // Number of events of the specified type in
      // the given range.
      counter: Integer;
    }

.. http:get:: /aml/$OFFICER_PUB/decisions

  **Request:**

  *Taler-AML-Officer-Signature*:
    The client must provide Base-32 encoded EdDSA signature with
    ``$OFFICER_PRIV``, affirming the desire to obtain AML data.  Note that
    this is merely a simple authentication mechanism, the details of the
    request are not protected by the signature.

  :query limit:
    *Optional*. takes value of the form ``N (-N)``, so that at
    most ``N`` values strictly older (younger) than ``start`` are returned.
    Defaults to ``-20`` to return the last 20 entries (before ``start``).
  :query offset:
    *Optional*. Row number threshold, see ``delta`` for its
    interpretation.  Defaults to ``INT64_MAX``, namely the biggest row id
    possible in the database.
  :query h_payto:
    *Optional*. Account selector. All matching accounts are returned if this
    filter is absent, otherwise only decisions for this account.
  :query active:
    *Optional*. If set to yes, only return active decisions, if no only
    decisions that have been superceeded. Do not give (or use "all") to
    see all decisions regardless of activity status.
  :query investigation:
    *Optional*. If set to yes, only return accounts that are under
    AML investigation, if no only accounts that are not under investigation.
    Do not give (or use "all") to see all accounts regardless of
    investigation status.

  **Response:**

  :http:statuscode:`200 OK`:
    The responds will be an `AmlDecisions` message.
  :http:statuscode:`204 No content`:
    There are no matching AML records.
  :http:statuscode:`403 Forbidden`:
    The signature is invalid.
  :http:statuscode:`404 Not found`:
    The designated AML account is not known.
  :http:statuscode:`409 Conflict`:
    The designated AML account is not enabled.

  **Details:**

  .. ts:def:: AmlDecisions

    interface AmlDecisions {

      // Array of AML decisions matching the query.
      records: AmlDecisions[];
    }

  .. ts:def:: AmlRecord

    interface AmlRecord {

      // Which payto-address is this record about.
      // Identifies a GNU Taler wallet or an affected bank account.
      h_payto: PaytoHash;

      // Row ID of the record.  Used to filter by offset.
      rowid: Integer;

      // FIXME: more fields here!
    }


.. http:get:: /aml/$OFFICER_PUB/attributes/$H_PAYTO

  Obtain attributes obtained as part of AML/KYC processes for a
  given account.

  **Request:**

  *Taler-AML-Officer-Signature*:
    The client must provide Base-32 encoded EdDSA signature with
    ``$OFFICER_PRIV``, affirming the desire to obtain AML data.  Note that
    this is merely a simple authentication mechanism, the details of the
    request are not protected by the signature.

  :query limit:
    *Optional*. takes value of the form ``N (-N)``, so that at
    most ``N`` values strictly older (younger) than ``start`` are returned.
    Defaults to ``-20`` to return the last 20 entries (before ``start``).
  :query offset:
    *Optional*. Row number threshold, see ``delta`` for its
    interpretation.  Defaults to ``INT64_MAX``, namely the biggest row id
    possible in the database.

  **Response:**

  :http:statuscode:`200 OK`:
    The responds will be an `KycAttributes` message.
  :http:statuscode:`204 No content`:
    There are no matching KYC attributes.
  :http:statuscode:`403 Forbidden`:
    The signature is invalid.
  :http:statuscode:`404 Not found`:
    The designated AML account is not known.
  :http:statuscode:`409 Conflict`:
    The designated AML account is not enabled.

  .. ts:def:: KycAttributes

    interface KycAttributes {

      // Matching KYC attribute history of the account.
      details: KycDetail[];

    }

  .. ts:def:: KycDetail

    // FIXME: bad name?
    interface KycDetail {

      // Row ID of the record.  Used to filter by offset.
      rowid: Integer;

      // Name of the configuration section that specifies the provider
      // which was used to collect the attributes. NULL if they were
      // just uploaded via a form by the account owner.
      provider_section?: string;

      // The collected KYC data.  NULL if the attribute data could not
      // be decrypted (internal error of the exchange, likely the
      // attribute key was changed).
      attributes?: Object;

      // Time when the KYC data was collected
      collection_time: Timestamp;

    }


  .. http:post:: /aml/$OFFICER_PUB/decision

  Make an AML decision. Triggers the respective action and
  records the justification.

  **Request:**

  The request body must be an `AmlDecision` message.

  **Response:**

  :http:statuscode:`204 No content`:
    The AML decision has been executed and recorded successfully.
  :http:statuscode:`403 Forbidden`:
    The signature is invalid.
  :http:statuscode:`404 Not found`:
    The address the decision was made upon is unknown to the exchange or
    the designated AML account is not known.
  :http:statuscode:`409 Conflict`:
    The designated AML account is not enabled or a more recent
    decision was already submitted.

  **Details:**

  .. ts:def:: AmlDecision

    interface AmlDecision {

      // Human-readable justification for the decision.
      justification: string;

      // Which payto-address is the decision about?
      // Identifies a GNU Taler wallet or an affected bank account.
      h_payto: PaytoHash;

      // What are the new rules?
      new_rules: LegitimizationRuleSet;

      // True if the account should remain under investigation by AML staff.
      bool keep_investigating;

      // When was the decision made?
      decision_time: Timestamp;

      // Signature by the AML officer over a `TALER_AmlDecisionPS`.
      // Must have purpose ``TALER_SIGNATURE_MASTER_AML_KEY``.
      officer_sig: EddsaSignature;

    }


Modifications to existing endpoints
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

When withdrawing, the exchange checks if the KYC status is acceptable.  If no
KYC was done and if either the amount withdrawn over a particular timeframe
exceeds the threshold or the reserve received received a P2P transfer, then a
``451 Unavailable for Legal Reasons`` is returned which redirects the consumer
to the new ``/kyc-check/`` handler.

When depositing, the exchange aggregator (!) checks the KYC status and if
negative, returns an additional information field via the
``aggregation_transient`` table which is returned via GET ``/deposts/`` to the
merchant.  This way, the merchant learns the ``requirement_row`` needed to
begin the KYC process (this is independent of the amount) at the new
``/kyc-check/`` handler.

When merging into a reserve, the KYC status is checked and again the
merge fails with ``451 Unavailable for Legal Reasons`` to trigger the
KYC process.

To allow the wallet to do the KYC check if it is about to exceed a set balance
threshold, we modify the ``/keys`` response to add an optional array
``wallet_balance_limit_without_kyc`` of threshold amounts is returned.
Whenever the wallet crosses one of these thresholds for the first time, it
should trigger the KYC process.  If this field is absent, there is no limit.
If the field is provided, a correct wallet must create a long-term
account-reserve key pair. This should be the same key that is also used to
receive wallet-to-wallet payments. Then, *before* a wallet performs an
operation that would cause it to exceed the balance threshold in terms of
funds held from a particular exchange, it *should* first request the user to
complete the KYC process.  For that, the wallet should POST to the new
``/kyc-wallet`` endpoint, providing its long-term reserve-account public key
and a signature requesting permission to exceed the account limit.


Configuration of external KYC providers
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

For each KYC provider that could contribute to checks the configuration
specifies a ``$PROVIDER_SECTION`` for each authentication procedure.  For each
(enabled) provider, the exchange has a logic plugin which (asynchronously)
determines the redirect URL for a given wire target. See below for a
description of the high-level process for different providers.

.. code-block:: ini

  [kyc-provider-$PROVIDER_ID]

  # Which plugin is responsible for this provider?
  LOGIC = PLUGIN_NAME

  # Optional cost, useful if clients want to voluntarily
  # trigger authentication procedures for attestation.
  # Since **vATTEST**.
  COST = EUR:5

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

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

  # Name of a program to run on the output of the plugin
  # to convert the result into the desired set of attributes.
  # The converter must create a log for the system administrator
  # if the provided inputs do not match expectations.
  # Note that the converter will be expected to output the
  # set of attributes listed under the respective ``[kyc-check-*]``
  # sections. Calling the converter with ``--list-outputs``
  # should generate a (newline-separated) list of attributes
  # the converter promises to generate in its JSON output
  # (when run regularly).
  CONVERTER = taler-exchange-helper-$NAME


Configuration of possible KYC/AML checks
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The configuration specifies a set of possible KYC checks offered by external
providers, one per configuration section:

.. code-block:: ini

  [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.
  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
  # provided 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 inputs into
  # a subsequent AML program.
  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 outcome of *any* check should always be uploaded encrypted into the
``kyc_attributes`` table.  It MUST include an ``expiration_time``.


Configuration of legitimization requirement triggers
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The configuration also specifies a set of legitimization rules including the
condition and the measure the condition triggers, one condition per
configuration section:

.. code-block:: ini

  [kyc-rule-$RULE_NAME]

  # Operation that triggers this legitimization.
  # Must be one of WITHDRAW, DEPOSIT, P2P-RECEIVE
  # or WALLET-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 legitimization is
  # triggered.  The total must be exceeded in the given
  # timeframe.
  THRESHOLD = KUDOS:100

  # Timeframe over which the amount to be compared to
  # the THRESHOLD is calculated.
  # Ignored for WALLET-BALANCE.  Can be 'forever'.
  TIMEFRAME = 30 days

  # Enabled (default is NO)
  ENABLED = NO


AML programs
^^^^^^^^^^^^

AML programs are helper programs that can:

* Generate a list of *required* context field names
  for the helper (introspection!) using the "--required-context"
  command-line switch. The output should use the same
  syntax as the REQUIRES clause of ``[kyc-check-]``
  configuration sections, except that new lines
  MUST be used to separate fields instead of ";".
* Generate a list of *required* attribute names
  for the helper (introspection!) using the "--required-attributes"
  command-line switch. The output should use the same
  list of names as the ATTRIBUTES in the
  ``[kyc-provider-]`` configuration section
  (but may also include FORM field names).
* Process an input JSON object of type
  `AmlProgramInput` into a JSON object of
  type `AmlOutcome`.
  This is the default behavior if no command-line switches
  are provided.

.. ts:def:: AmlProgramInput

  interface AmlProgramInput {

    // JSON object that was provided as
    // part of the *measure*.  This JSON object is
    // provided under "context" in the main JSON object
    // input to the AML program.  This "context" should
    // satify both the REQUIRES clause of the respective
    // check and the output of "--requires" from the
    // AML program's command-line option.
    context?: Object;

    // JSON object that captures the
    // output of a ``[kyc-provider-]`` or (HTML) FORM.
    // The keys in the JSON object will be the attribute
    // names and the values must be strings representing
    // the data. In the case of file uploads, the data
    // MUST be base64-encoded.
    attributes: Object;

    // JSON array with the results of historic
    // AML desisions about the account.
    aml_history: AmlDecisionDetail[];

    // JSON array with the results of historic
    // KYC data about the account.
    kyc_history: KycDetail[];

  }

.. ts:def:: AmlOutcome

  interface AmlOutcome {

    // Should the client's account be investigated
    // by AML staff?
    // Defaults to false.
    to_investigate?: boolean;

    // Free-form properties about the account.
    // Can be used to store properties such as PEP,
    // risk category, type of business, hits on
    // sanctions lists, etc.
    properties?: AccountProperties;

    // Types of events to add to the KYC events table.
    // (for statistics).
    events?: string[];

    // KYC rules to apply.  Note that this
    // overrides *all* of the default rules
    // until the ``expiration_time`` and specifies
    // the successor measure to apply after the
    // expiration time.
    new_rules: LegitimizationRuleSet;

  }

.. ts:def:: KycRule

  interface KycRule {

    // Type of operation to which the rule applies.
    operation_type: string;

    // The measures will be taken if the given
    // threshold is crossed over the given timeframe.
    threshold: Amount;

    // Over which duration should the ``threshold`` be
    // computed.  All amounts of the respective
    // ``operation_type`` will be added up for this
    // duration and the sum compared to the ``threshold``.
    timeframe: RelativeTime;

    // Array of names of measures to apply.
    // Names listed can be original measures or
    // custom measures from the `AmlOutcome`.
    // A special measure "verboten" is used if the
    // threshold may never be crossed.
    measures: string[];

    // True if the rule (specifically, operation_type,
    // threshold, timeframe) and the general nature of
    // the measures (verboten or approval required)
    // should be exposed to the client.
    // Defaults to "false" if not set.
    exposed?: boolean;

    // True if all the measures will eventually need to
    // be satisfied, false if any of the measures should
    // do.  Primarily used by the SPA to indicate how
    // the measures apply when showing them to the user;
    // in the end, AML programs will decide after each
    // measure what to do next.
    // Default (if missing) is false.
    is_and_combinator?: boolean;

    // If multiple rules apply to the same account
    // at the same time, the number with the highest
    // rule determines which set of measures will
    // be activated and thus become visible for the
    // user.
    display_priority: integer;
  }

If the AML program fails (exits with a failure code or
does not provide well-formed JSON output) the AML/KYC
process continues with the FALLBACK measure. This should
usually be one that asks AML staff to contact the
systems administrator.

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

.. code-block:: ini

  [aml-program-$PROG_NAME]

  # Program to run.
  COMMAND = taler-helper-aml-pep

  # Human-readable description of what this
  # AML helper program will do. Used to show
  # to the AML staff.
  DESCRIPTION = "check if the customer is a PEP"

  # True if this AML program is enabled (and thus can be
  # used in measures and exposed to AML staff).
  # Optional, default is NO.
  ENABLED = YES

  # **original** measure to take if COMMAND fails
  # Usually points to a measure that asks AML staff
  # to contact the systems administrator. The fallback measure
  # context always includes the reasons for the
  # failure.
  FALLBACK = MEASURE_NAME


Configuration of measures
^^^^^^^^^^^^^^^^^^^^^^^^^

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

.. code-block:: ini

  [kyc-measure-$MEASURE_NAME]

  # Possible check for this measure.  Optional.
  # If not given, PROGRAM should be run immediately
  # (on an empty set of attributes).
  CHECK_NAME = IB_FORM

  # Context for the check. The context can be
  # just an empty JSON object if there is none.
  CONTEXT = {"choices":["individual","business"]}

  # Program to run on the context and check data to
  # determine the outcome and next measure.
  PROGRAM = taler-aml-program

If no ``CHECK_NAME`` is provided at all, the AML ``PROGRAM`` is to be run
immediately.  This is useful if no client-interaction is required to arrive at
a decision.

.. note::

  The list of *measures* is not complete: AML staff may freely define new
  measures dynamically, usually by selecting checks, an AML program, and
  providing context.


Sanity checking
^^^^^^^^^^^^^^^

On start-up, ``taler-exchange-httpd`` should sanity-check its
configuration. Specifically, it should validate that for all AML programs the
input requirements (attributes and context) are claimed to be satisfied by the
respective checks that may trigger those programs, and similarly that for all
checks the original measures satisfy the context requirements for their KYC
checks.

As a result, any component (AML program, form or external check) is warranted
to be always called with the declared required inputs. Furthermore, we can
detect if a component fails to produce the required output and the
configuration contains (presumably safe) FALLBACKs to address this case.  The
exchange *MUST* detect circular failures, like when a FALLBACK triggers a
measure that itself immediately triggers again the same FALLBACK.


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

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.

We introduce a new ``legitimization_processes`` table that tracks the status
of a legitimization process at a provider, including the configuration section
name, the user/account name at the provider, and some legitimization
identifier for the process at the provider.  In this table, we additionally
store information related to the KYC status of the underlying payto://-URI, in
particular when the KYC expires (0 if it was never done).

Finally, we introduce a new ``legitimization_requirements`` table that
contains a list of checks required for a particular wire target.  When KYC is
triggered (say when some endpoint returns an HTTP status code of 451) a
new requirement is first put into the requirements table. Then, when the
client identifies as business or individual the specific legitimization
process is started.  When the taler-exchange-aggregator triggers a KYC check
the merchant can observe this when a 202 (Accepted) status code is returned
on GET ``/deposits/`` with the respective legitimization requirement row.


.. sourcecode:: sql

  CREATE TABLE wire_targets
    (wire_target_serial_id BIGSERIAL UNIQUE
    ,wire_target_h_payto BYTEA PRIMARY KEY CHECK (LENGTH(wire_target_h_payto)=32),
    ,access_token BYTEA UNIQUE CHECK (LENGTH(access_token)=32) DEFAULT gen_random_bytes(32)
    ,target_pub BYTEA CHECK (LENGTH(target_pub)=32) DEFAULT NULL
    ,payto_uri STRING NOT NULL
    )
    PARTITION BY HASH (wire_target_h_payto);

  COMMENT ON TABLE wire_targets
    IS 'All recipients of money via the exchange';
  COMMENT ON COLUMN wire_targets.h_payto
    IS 'Unsalted hash of payto_uri';
  COMMENT ON COLUMN wire_targets.access_token
    IS 'high-entropy random value that is used as a token to authorize access to the KYC process (without requiring a signature by target_priv)';
  COMMENT ON COLUMN wire_targets.target_pub
    IS 'Public key (reserve_pub or merchant_pub) associated with the account; NULL if KYC is not allowed for the account (if there was no incoming KYC wire transfer yet); updated, thus NOT available to the auditor';
  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)';

  CREATE TABLE IF NOT EXISTS legitimization_measures
    (legitimization_measure_serial_id INT8 GENERATED BY DEFAULT AS IDENTITY
    ,access_token BYTEA NOT NULL UNIQUE CHECK (LENGTH(access_token)=32)
     REFERENCES wire_targets (access_token)
    ,start_time INT8 NOT NULL
    ,jmeasures TEXT NOT NULL
    ,display_priority INT4 NOT NULL
    ,is_finished BOOL NOT NULL DEFAULT(FALSE)
    )
    PARTITION BY HASH (access_token);

  COMMENT ON COLUMN legitimization_measures.access_token
    IS 'Used to uniquely identify the account and as a symmetric access control mechanism for the SPA';
  COMMENT ON COLUMN legitimization_measures.start_time
    IS 'Time when the measure was triggered (by decision or rule)';
  COMMENT ON COLUMN legitimization_measures.jmeasures
    IS 'JSON object of type LegitimizationMeasures with KYC/AML measures for the account encoded';
  COMMENT ON COLUMN legitimization_measures.display_priority
    IS 'Display priority of the rule that triggered this measure; if in the meantime another rule also triggers, the measure is only replaced if the new rule has a higher display priority';
  COMMENT ON COLUMN legitimization_measures.is_finished
    IS 'Set to TRUE if this set of measures was processed; used to avoid indexing measures that are done';

  CREATE INDEX ON legitimization_measures (access_token)
    WHERE NOT is_finished;

  CREATE TABLE legitimization_outcomes
    (outcome_serial_id INT8 GENERATED BY DEFAULT AS IDENTITY
    ,h_payto BYTEA CHECK (LENGTH(h_payto)=32)
     REFERENCES wire_targets (wire_target_h_payto)
    ,decision_time INT8 NOT NULL DEFAULT(0)
    ,expiration_time INT8 NOT NULL DEFAULT(0)
    ,jproperties TEXT,
    ,to_investigate BOOL NOT NULL
    ,is_active BOOL NOT NULL DEFAULT(TRUE)
    ,jnew_rules TEXT NOT NULL
    )
    PARTITION BY HASH (h_payto);

  COMMENT ON TABLE legitimization_outcomes
    IS 'Outcomes can come from AML programs';
  COMMENT ON COLUMN legitimization_outcomes.h_payto
    IS 'hash of the payto://-URI this outcome is about';
  COMMENT ON COLUMN legitimization_outcomes.decision_time
    IS 'when was this outcome decided';
  COMMENT ON COLUMN legitimization_outcomes.expiration_time
    IS 'time when the decision expires and the expiration jnew_rules should be applied';
  COMMENT ON COLUMN legitimization_outcomes.jproperties
    IS 'JSON object of type AccountProperties, such as PEP status, business domain, risk assessment, etc.';
  COMMENT ON COLUMN legitimization_outcomes.to_investigate
    IS 'AML staff should investigate the activity of this account';
  COMMENT ON COLUMN legitimization_outcomes.is_active
    IS 'TRUE if this is the current authoritative legitimization outcome';
  COMMENT ON COLUMN legitimization_outcomes.jnew_rules
    IS 'JSON object of type LegitimizationRuleSet with rules to apply to the various operation types for this account; all KYC checks should first check if active new rules for a given account exist in this table (and apply specified measures); if not, it should check the default rules to decide if a measure is required';

  CREATE INDEX legitimization_outcomes_active
    ON legitimization_outcomes(h_payto)
    WHERE is_active;

  CREATE TABLE legitimization_processes
    (legitimization_process_serial_id BIGSERIAL UNIQUE
    ,h_payto BYTEA NOT NULL CHECK (LENGTH(h_payto)=64)
     REFERENCES wire_targets (wire_target_h_payto)
    ,start_time INT8 NOT NULL
    ,expiration_time INT8 NOT NULL DEFAULT (0)
    ,legitimization_measure_serial_id INT8
     REFERENCES legitimization_measures (legitimization_measure_serial_id)
    ,measure_index INT4
    ,provider_section TEXT NOT NULL
    ,provider_user_id TEXT DEFAULT NULL
    ,provider_legitimization_id TEXT DEFAULT NULL
    ,redirect_url TEXT DEFAULT NULL
    ,finished BOOLEAN DEFAULT (FALSE)
    )
    PARTITION BY HASH (h_payto);

  COMMENT ON TABLE legitimization_processes
    IS 'here we track KYC processes we initiated with external providers; the main reason is so that we do not initiate a second process when an equivalent one is still active; note that h_payto, provider_section, jcontext must match and the process must not be finished or expired for an existing redirect_url to be re-used; given that clients may voluntarily initiate KYC processes, there may not always be a legitimization_measure that triggered the setup';
  COMMENT ON COLUMN legitimization_processes.h_payto
    IS 'foreign key linking the entry to the wire_targets table, NOT a primary key (multiple KYC setups are possible per wire target)';
  COMMENT ON COLUMN legitimization_processes.start_time
    IS 'when was the legitimization process initiated';
  COMMENT ON COLUMN legitimization_processes.expiration_time
    IS 'when does the process expire (and needs to be manually set up again)';
  COMMENT ON COLUMN legitimization_processes.measure_index
    IS 'index of the measure in legitimization_measures that was selected for this KYC setup; NULL if legitimization_measure_serial_id is NULL; enables determination of the context data provided to the external process';
  COMMENT ON COLUMN legitimization_processes.provider_section
    IS 'Configuration file section with details about this provider';
  COMMENT ON COLUMN legitimization_processes.provider_user_id
    IS 'Identifier for the user at the provider that was used for the legitimization. NULL if provider is unaware.';
  COMMENT ON COLUMN legitimization_processes.provider_legitimization_id
    IS 'Identifier for the specific legitimization process at the provider. NULL if legitimization was not started.';
  COMMENT ON COLUMN legitimization_processes.legitimization_measure_serial_id
    IS 'measure that enabled this setup, NULL if client voluntarily initiated the process';
  COMMENT ON COLUMN legitimization_processes.redirect_url
    IS 'Where the user should be redirected for this external KYC process';
  COMMENT ON COLUMN legitimization_processes.finished
    IS 'set to TRUE when the specific legitimization process is finished';

  CREATE TABLE kyc_attributes
    (kyc_attributes_serial_id INT8 GENERATED BY DEFAULT AS IDENTITY
    ,h_payto BYTEA PRIMARY KEY CHECK (LENGTH(h_payto)=32)
     REFERENCES wire_targets (wire_target_h_payto)
    ,legitimization_process_serial_id INT8
     REFERENCES legitimization_processes (legitimization_process_serial_id)
     DEFAULT NULL
    ,collection_time INT8 NOT NULL
    ,expiration_time INT8 NOT NULL
    ,trigger_outcome_serial INT8 NOT NULL
     REFERENCES legitimization_outcomes(outcome_serial_id)
    ,encrypted_attributes BYTEA NOT NULL
    ) PARTITION BY HASH (h_payto);

  COMMENT ON COLUMN kyc_attributes.h_payto
    IS 'identifies the account this is about';
  COMMENT ON COLUMN kyc_attributes.legitimization_process_serial_id
    IS 'serial ID of the legitimization process that resulted in these attributes, NULL if the attributes are from a form directly supplied by the account owner via a form';
  COMMENT ON COLUMN kyc_attributes.collection_time
    IS 'when were these attributes collected';
  COMMENT ON COLUMN kyc_attributes.expiration_time
    IS 'when are these attributes expected to expire';
  COMMENT ON COLUMN kyc_attributes.trigger_outcome_serial
    IS 'ID of the outcome that was returned by the AML program based on the KYC data collected';
  COMMENT ON COLUMN kyc_attributes.encrypted_attributes
    IS 'encrypted JSON object with the attribute data the check provided';

  CREATE TABLE aml_history
    (aml_history_serial_id INT8 GENERATED BY DEFAULT AS IDENTITY
    ,h_payto BYTEA CHECK (LENGTH(h_payto)=32)
     REFERENCES wire_targets (wire_target_h_payto)
    ,outcome_serial_id INT8 NOT NULL
     REFERENCES legitimization_outcomes (outcome_serial_id)
    ,justification TEXT NOT NULL
    ,decider_pub BYTEA CHECK (LENGTH(decider_pub)=32)
    ,decider_sig BYTEA CHECK (LENGTH(decider_sig)=64);

  COMMENT ON TABLE aml_history
    IS 'Records decisions by AML staff with the respective signature and free-form justification.';
  COMMENT ON COLUMN aml_history.outcome_serial_id
    IS 'Actual outcome for the account (included in what decider_sig signs over)';
  COMMENT ON COLUMN aml_history.decider_sig
    IS 'Signature key of the staff member affirming the AML decision; of type AML_DECISION';

  CREATE TABLE kyc_events
    (kyc_event_serial_id INT8 GENERATED BY DEFAULT AS IDENTITY
    ,event_timestamp INT8 NOT NULL
    ,event_type TEXT NOT NULL);

  COMMENT ON TABLE kyc_events
    IS 'Records of key events for statistics. Populated via triggers.';
  COMMENT ON COLUMN kyc_events.event_type
    IS 'Name of the event, such as account-open or sar-filed';

  CREATE INDEX kyc_event_index
    ON kyc_events(event_type,event_timestamp);


The ``jmeasures`` JSON in the ``legitimization_measures``
table has is of type `LegitimizationMeasures`:

.. ts:def:: LegitimizationMeasures

  interface LegitimizationMeasures {

    // Array of legitimization measures that
    // are to be applied.
    measures: MeasureInformation[];

    // True if the client is expected to eventually satisfy all requirements.
    // Default (if missing) is false.
    is_and_combinator?: boolean;
  }


The ``jnew_rules`` JSON in the ``legitimization_outcomes``
table has is of type `LegitimizationRuleSet`:

.. ts:def:: LegitimizationRuleSet

  interface LegitimizationRuleSet {

    // When does this set of rules expire and
    // we automatically transition to the successor
    // measure?
    expiration_time: Timestamp;

    // Name of the measure to apply when the expiration time is
    // reached.  If not set, we refer to the default
    // set of rules (and the default account state).
    successor_measure?: string;

    // Legitimization rules that are to be applied
    // to this account.
    rules: KycRule[];

    // Custom measures that KYC rules and the
    // ``successor_measure`` may refer to.
    custom_measures: { "$measure_name" : MeasureInformation; };
  }


The ``jproperties`` JSON in the ``legitimization_outcomes`` table has is of
type `AccountProperties`. All fields in this object are optional. The actual
properties collected depend fully on the discretion of the exchange operator;
however, some common fields are standardized and thus described here.

.. ts:def:: AccountProperties

  interface AccountProperties {

    // True if this is a politically exposed account.
    // Rules for classifying accounts as politically
    // exposed are country-dependent.
    pep?: boolean;

    // True if this is a sanctioned account.
    // Rules for classifying accounts as sanctioned
    // are country-dependent.
    sanctioned?: boolean;

    // True if this is a high-risk account.
    // Rules for classifying accounts as at-risk
    // are exchange operator-dependent.
    high_risk?: boolean;

    // Business domain of the account owner.
    // The list of possible business domains is
    // operator- or country-dependent.
    business_domain?: string;

    // Is the client's account currently frozen?
    is_frozen?: boolean;

    // Was the client's account reported to the authorities?
    was_reported?: boolean;

  }



KYC forms
^^^^^^^^^

The KYC SPA run by clients needs to support three TYPEs of checks. INFO is
only about displaying the provided information, LINK is about setting up an
exteral KYC check and redirecting there. FORM is about displaying a particular
(HTML) form to the user and POSTing the entered information directly with the
exchange.  Here we describe the forms that must be supported:

* **CHOICE**: Asks the client a multiple-choice question.  The context must
  include "choices: string[]" with a list of choices to show.  Used, for
  example, to ask a client if they are an individual or a business.  The
  resulting HTML FORM field name must be "choice" and it must be mapped to
  strings from the choices list.

* **UPLOAD**: Asks the client to upload a single file.
  The context must include a ``validity_duration`` which
  will be converted to the ``expiration_time`` for
  the uploaded data.  The context may furthermore include
  ``extensions?: string[]`` with a list of allowed file extensions the client's
  file must end with (e.g. "png", "pdf", "gif").  In the absence of this
  context, any file may be uploaded.  The context may also include a
  ``size_limit?: Integer`` with the maximum file size in bytes that can be
  uploaded.  The resulting HTTP POST should provide at least two fields, "filename" and
  "filedata".  "filename" must be set to the basename of the original file (to
  the extend that it is available), and "filedata" to the base64-encoding of
  the uploaded data.

As with other SPA checks, the KYC form should also show
the description of the check.


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

A new setting is required where the merchant backend can be configured for a
business (default) or individual.

We introduce new ``kyc_ok``, ``aml_decision``, ``kyc_timestamp`` and
``exchange_kyc_serial`` fields into a new table ``merchant_kyc`` with primary
keys ``exchange_url`` and ``account_serial``.  This status is updated whenever
a deposit is created or tracked, or whenever the mechant backend receives a
``/kyc-check/`` response from the exchange.  Initially,
``exchange_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 detection in ``taler-merchant-depositcheck``) 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 SPA
first re-checks the KYC status with the exchange.  If the KYC is still
unfinished, that SPA will show forms, links or contact information to begin
the KYC process (for example, redirecting to the OAuth 2.0 login page of the
legitimization resource server), otherwise it shows that the KYC process is
done. If the KYC is unfinished, the merchant 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.

  ..note::

    Semi-related: The TMH_setup_wire_account() is changed to use
    128-bit salt values (to keep ``deposits`` table small) and checks for salt
    to be well-formed should be added "everywhere".



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

The exchange primarily requires a KYC provider to be operated by the
bank that offers an endpoint for with an API implemented by one of
the logic plugins (and the respective legitimization configuration).


Logic plugins
^^^^^^^^^^^^^

The ``$PROVIDER_SECTION`` is based on the name of the configuration section,
not on the name of the logic plugin (that we call ``$LOGIC``).  Using the
configuration section, the exchange then determines the logic plugin to use.

This section describes the general API for all of the supported KYC providers,
as well as some details of how this general API could be implemented by the
logic for different APIs.


General KYC Logic Plugin API
----------------------------

This section provides a sketch of the proposed API for the KYC logic plugins.

* initiation of KYC check (``kyc-check``):

  - inputs:
    + provider_section (for additional configuration)
    + h_payto
  - outputs:
    + success/provider-failure
    + redirect URL (or NULL)
    + provider_user_id (or NULL)
    + provider_legitimization_id (or NULL)

* KYC status check (``kyc-proof``):

  - inputs:
    + provider_section (for additional configuration)
    + h_payto
    + provider_user_id (or NULL)
    + provider_legitimization_id (or NULL)
  - outputs:
    + success/pending/user-aborted/user-failure/provider-failure status code
    + HTML response for end-user

* Webhook notification handler (``kyc-webhook``):

  - inputs:
    + HTTP method (GET/POST)
    + rest of URL (after provider_section)
    + HTTP body (if applicable!)
  - outputs:
    + success/pending/user-aborted/user-failure/provider-failure status code
    + h_payto (for DB status update)
    + HTTP response to be returned to KYC provider

The plugins do not directly interact with the database, the caller sets the
expiration on ``success`` and also updates ``provider_user_id`` and
``provider_legitimization_id`` in the tables as required.


For the webhook, we need a way to lookup ``h_payto`` by other data, so the
KYC logic plugin API should be provided a method lookup with:

  - inputs:
    + ``provider_section``
    + ``provider_legitimization_id``
  - outputs:
    + ``h_payto``
    + ``legitimization_process_row``


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.

For the ``/kyc-check/`` endpoint, the OAuth 2.0 logic may need to create and
store a nonce to be used during ``/kyc-proof/``, depending on the OAuth
variant used.  This may require another exchange table.  The OAuth 2.0 process
must then be set up to end at the new ``/kyc-proof/$PROVIDER_ID/`` endpoint.

This ``/kyc-proof/oauth2/`` endpoint must query the OAuth 2.0 server using the
``code`` argument provided as a query parameter. Based on the result, it then
updates the KYC table of the exchange with the legitimization status and
returns a human-readable KYC status page.

The ``/kyc-webhook/`` is not applicable.


Persona specifics
-----------------

We would use the hosted flow. Endpoints return a ``request-id``, which we should
log for diagnosis.

For ``/kyc-check/``:

* Post to ``/api/v1/accounts`` using ``reference-id`` set to our ``h_payto``.
  Returns ``id`` (account_id).

* Create ``/verify`` endpoint using ``template-id`` (from configuration),
  and ``account_id`` (from previous step) and a ``reference-id`` (use
  the ``legitimization_serial_id`` for the new process). Set
  ``redirect-uri`` to ``/kyc-proof/$PROVIDER_ID/``.  However, we cannot
  rely on the user clicking this, so we must also configure a webhook.
  The request returns a '``verification-id``.  That we store under
  the ``provider_legitimization_id`` in the database.

For ``/kyc-proof/``:

* Use the ``/api/v1/verifications`` endpoint to get the verification
  status. Requires the ``verification-id`` from the previous step.
  Results include: created/pending/completed/expired (aborted)/failed.

For ``/kyc-webhook/``:

* The webhook is authenticated using a shared secret, which should
  be in the configuration.  So all we should have to do is parse
  the POSTed body to find the status and the ``verification-id`` to
  lookup ``h_payto`` and return the result.


KYC AID specifics
-----------------

For ``/kyc-check/``:

* Post to ``/applicants`` with a type (person or company) to
  obtain ``applicant_id``. Store that under ``provider_user_id``.
  ISSUE: *we* need to get the company_name, business_activity_id
  and registration_country before this somehow!

* start with create form URL ``/forms/$FORM_ID/urls``
  providing our ``h_payto`` as the ``external_applicant_id``,
  using the ``applicant_id`` from above,
  and the ``/kyc-proof/$PROVIDER_ID`` for the ``redirect_url``.

* redirect customer to the ``form_url``,
  store the ``verification_id`` under ``provider_legitimization_id``
  in the database.

For ``/kyc-proof/``:

* Not needed, just return an error.

For ``/kyc-webhook/``:

* For security, we should probably simply trigger the GET on
  ``/verifications/{verification_id}`` to not trust an unsigned POST
  to tell us anything for sure.  The result is then returned.


Types of KYC events
^^^^^^^^^^^^^^^^^^^

The ``/aml/$OFFICER_PUB/kyc-statistics`` endpoint exposes statistics for
various KYC event types.

We will initially support the use of the following types of KYC events in the
SPA (and have a dialog to show the total number of any of these for any
specified time range):

* account-open
* account-closed
* voluntary-sar
* mandatory-sar
* pep-started
* pep-ended
* risky-started
* risky-ended
* account-frozen
* account-unfrozen

Based on these, the SPA should also be albe to show active
statistics (for any given timestamp) on the total number of:

* open accounts
* frozen accounts
* high-risk accounts
* PEPs served

.. note::

   This can be done by simply running the queries with
   a start time of zero and subtracting.


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

We could also store the access token (returned by OAuth 2.0), but that seems
slightly more dangerous and given the close business relationship is
unnecessary. Furthermore, not all APIs offer this.

We could extend the KYC logic API to return key attributes about the user
(such as legal name, phone number, address, etc.) which we could then sign and
return to the user.  This would be useful in P2P payments to identify the
origin of an invoice.  However, we might want to be careful to not disclose
the key attributes via the API by accident.  This could likely be done by
limiting access to the respective endpoint to messages with a signature by the
reserve private key (which is the only case where we care to certify things
anyway).


Drawbacks
=========


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

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