summaryrefslogtreecommitdiff
path: root/design-documents/023-taler-kyc.rst
diff options
context:
space:
mode:
Diffstat (limited to 'design-documents/023-taler-kyc.rst')
-rw-r--r--design-documents/023-taler-kyc.rst497
1 files changed, 380 insertions, 117 deletions
diff --git a/design-documents/023-taler-kyc.rst b/design-documents/023-taler-kyc.rst
index 3e67bff2..bfc6e514 100644
--- a/design-documents/023-taler-kyc.rst
+++ b/design-documents/023-taler-kyc.rst
@@ -1,5 +1,5 @@
-DD 023: Taler KYC
-#################
+DD23: Taler KYC
+###############
Summary
=======
@@ -17,6 +17,8 @@ banks to identify parties involved in transactions at certain points.
Requirements
============
+The solution should support fees to be paid by the user for the KYC process (#7365).
+
Taler needs to run KYC checks in the following circumstances:
* Customer withdraws money over a monthly threshold
@@ -31,150 +33,254 @@ Taler needs to run KYC checks in the following circumstances:
* 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 or tipping.
+
+ * key: reserve (=KYC account) long term public key per wallet (encoded as payto:// URI)
+
Proposed Solution
=================
-Exchange modifications
-^^^^^^^^^^^^^^^^^^^^^^
+Terminology
+^^^^^^^^^^^
-We introduce a new ``wire_targets`` table into the exchange database. This
-table is referenced as the source or destination of payments (regular deposits
-and also P2P payments). A positive side-effect is that we reduce duplication
-in the ``reserves_in``, ``wire_out`` and ``deposits`` tables as they can
-reference this table. In this table, we additionally store information
-related to the KYC status of the underlying payto://-URI.
-
-The new ``/kyc-check/`` endpoint is based on the ``wire_targets`` serial
-number. Access is ``authenticated`` by also passing the hash of the
-payto://-URI (weak authentication is acceptable, as the KYC status or the
-ability to initiate a KYC process are not very sensitive). Given this pair,
-the ``/kyc-check/`` endpoint returns either the (positive) KYC status or
-redirects the client (202) to the current stage of the KYC process. (The
-endpoint may have to create and store a nonce to be used during
-``/kyc-proof/``, depending on the OAuth variant used.) The redirection is
-offered using an HTTP-redirect for Web-based clients and a JSON body with
-information for triggering a browser-based KYC process using OAuth 2.0.
-
-The OAuth 2.0 process is setup to end at a new ``/kyc-proof/`` endpoint. This
-endpoint then updates the KYC table of the exchange with the legitimization
-status (which is checked using OAuth 2.0). The endpoint also wakes up any
-long-polling ``/kyc-check/`` requests. Naturally, the exchange's OAuth 2.0
-client credentials must be configured apriori with the legitimization service.
+* **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.
-When withdrawing, the exchange checks if the KYC status is acceptable. If no
-KYC was done and if either the amount withdrawn over the last X days exceeds
-the threshold or the reserve received received a P2P transfer, then a ``202
-Accepted`` is returned which redirects the consumer to the new ``/kyc-check/``
-handler.
+* **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).
-When depositing, the exchange checks the KYC status and if negative, returns an
-additional information field that tells the merchant the ``wire_target_serial``
-number needed to begin the KYC process (this is independent of the amount)
-at the new ``/kyc-check/`` handler.
+* **Configuration**: The configuration determines the *legitimization rules*, and specifies which providers offer which *checks* at what *cost*.
-When tracking deposits, the exchange also adds the ``wire_target_serial`` to
-the reply if the KYC status is negative.
+* **Cost**: Metric for the business expense for a KYC check at a certain *provider*. Not in any currency, costs are simply relative and non-negative values. Costs are considered when multiple choices are allowed by the *configuration*.
-The aggregator is modified to only SELECT deposits where the ``wire_target``
-has the KYC status set to positive (unless KYC is disabled in the exchange
-configuration).
+* **Expiration**: KYC legitimizations may be outdated. Expiration rules determine when *checks* have to be performed again.
+* **Legitimization rules**: The legitimization rules determine under which *conditions* which *checks* must be performend and the *expiration* time period for the *checks*.
- ..note::
+* **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.
- Unrelated: We may want to consider directly deleting prewire records
- instead of setting them to ``finished`` in ``taler-exchange-transfer``.
+* **Provider**: A provider performs a specific set of *checks* at a certain *cost*. Interaction with a provider is performed by provider-specific *logic*.
+* **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.
-Exchange database schema changes
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+New Endpoints
+^^^^^^^^^^^^^
-Note that there is may be some slight complication in the migration as the
-h_wire in deposits is salted, while the h_payto in the new wire_targets is
-expected to be unsalted. So converting the existing information to create the
-wire_targets table will be tricky!
+We 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.
+
+The new ``/kyc-check/`` endpoint is based on the legitimization requirements
+serial number and receives the business vs. individual status from the client.
+Access is ``authenticated`` by also passing the hash of the payto://-URI.
+(Weak authentication is acceptable, as the KYC status or the ability to
+initiate a KYC process are not very sensitive.) Given this triplet, the
+``/kyc-check/`` endpoint returns either the (positive) 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. As this endpoint is involved in every KYC check at the beginning, this is also the place where we can
+integrate the payment process for the KYC fee.
+
+The specific KYC provider to be executed depends on the configuration (see
+below) which 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.
+
+Upon completion of the process at the KYC provider, the provider must trigger
+a GET request to a new ``/kyc-proof/$H_PAYTO/$PROVIDER_SECTION`` endpoint.
+This may be done either by redirecting the browser of the user to that
+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
+return a human-readable HTML page with the KYC result to the user.
+
+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.
+
+
+Legitimization Hooks
+^^^^^^^^^^^^^^^^^^^^
-We can *either* not support a fully automatic migration, or do an "expensive"
-migration with C logic (so not just SQL statements).
+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 ``/wallet-kyc`` endpoint,
+providing its long-term reserve-account public key and a signature requesting
+permission to exceed the account limit. 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.
+
+
+
+Configuration Options
+^^^^^^^^^^^^^^^^^^^^^
+
+The configuration specifies a set of providers, one per configuration section:
+
+[kyc-provider-$PROVIDER_ID]
+# How expensive is it to use this provider?
+# Used to pick the cheapest provider possible.
+COST = NUMBER
+# Which plugin is responsible for this provider?
+LOGIC = PLUGIN_NAME
+# Which type of user does this provider handle?
+# Either INDIVIDUAL or BUSINESS.
+USER_TYPE = INDIVIDUAL
+# Which checks does this provider provide?
+# List of strings, no specific semantics.
+PROVIDED_CHECKS = SMS GOVID PHOTO
+# Plus additional logic-specific options, e.g.:
+AUTHORIZATION_TOKEN = superdupersecret
+FORM_ID = business_legi_form
+# How long is the check considered valid?
+EXPIRATION = DURATION
+
+The configuration also specifies a set of legitimization
+requirements, one per configuration section:
+
+[kyc-legitimization-$RULE_NAME]
+# Operation that triggers this legitimization.
+# Must be one of WITHDRAW, DEPOSIT, P2P-RECEIVE
+# or WALLET-BALANCE.
+OPERATION_TYPE = WITHDRAW
+# Required checks to be performed.
+# List of strings, must individually match the
+# strings in one or more provider's PROVIDED_CHECKS.
+REQUIRED_CHECKS = SMS GOVID
+# Threshold amount above which the legitimization is
+# triggered. The total must be exceeded in the given
+# timeframe. Can be 'forever'.
+THRESHOLD = AMOUNT
+# Timeframe over which the amount to be compared to
+# the THRESHOLD is calculated.
+# Ignored for WALLET-BALANCE.
+TIMEFRAME = DURATION
+
+
+
+Exchange Database Schema
+^^^^^^^^^^^^^^^^^^^^^^^^
.. sourcecode:: sql
- -- Everything in one big transaction
- BEGIN;
- -- Check patch versioning is in place.
- SELECT _v.register_patch('exchange-TBD', NULL, NULL);
- --
CREATE TABLE IF NOT EXISTS wire_targets
(wire_target_serial_id BIGSERIAL UNIQUE
,h_payto BYTEA NOT NULL CHECK (LENGTH(h_payto)=64),
,payto_uri STRING NOT NULL
- ,kyc_ok BOOLEAN NOT NULL DEFAULT (false)
- ,oauth_username STRING NOT NULL
- ,PRIMARY KEY (h_wire)
- );
+ ,PRIMARY KEY (h_payto)
+ ) SHARD BY (h_payto);
COMMENT ON TABLE wire_targets
IS 'All recipients of money via the exchange';
COMMENT ON COLUMN wire_targets.payto_uri
IS 'Can be a regular bank account, or also be a URI identifying a reserve-account (for P2P payments)';
COMMENT ON COLUMN wire_targets.h_payto
IS 'Unsalted hash of payto_uri';
- COMMENT ON COLUMN wire_targets.kyc_ok
- IS 'true if the KYC check was passed successfully';
- COMMENT ON COLUMN wire_targets.oauth_username
- IS 'Name of the user that was used for OAuth 2.0-based legitimization';
- --
- -- NOTE: logic to fill wire_target missing, so this
- -- CANNOT work if the database contains any data!
- --
- ALTER TABLE wire_out
- ADD COLUMN wire_target_serial_id INT8 NOT NULL REFERENCES wire_targets (wire_target_serial_id),
- DROP COLUMN wire_target;
- COMMENT ON COLUMN wire_out.wire_target_serial_id
- IS 'Identifies the target bank account and KYC status';
- --
- ALTER TABLE reserves_in
- ADD COLUMN wire_source_serial_id INT8 NOT NULL REFERENCES wire_targets (wire_target_serial_id),
- DROP COLUMN sender_account_details;
- COMMENT ON COLUMN wire_out.wire_target_serial_id
- IS 'Identifies the target bank account and KYC status';
- --
- ALTER TABLE reserves_close
- ADD COLUMN wire_source_serial_id INT8 NOT NULL REFERENCES wire_targets (wire_target_serial_id),
- DROP COLUMN receiver_account;
- COMMENT ON COLUMN reserves_close.wire_target_serial_id
- IS 'Identifies the target bank account and KYC status. Note that closing does not depend on KYC.';
- --
- ALTER TABLE deposits
- ADD COLUMN wire_target_serial_id INT8 NOT NULL,
- ADD COLUMN salt BYTEA NOT NULL CHECK (LENGTH(salt)=64),
- DROP COLUMN h_wire,
- DROP COLUMN wire;
- COMMENT ON COLUMN deposits.wire_target_serial_id
- IS 'Identifies the target bank account and KYC status';
- -- Complete transaction
- --
- -- FIXME: 512-bit SALT is likely not specified/checked
- -- anywhere in the code (salt==string), and we probably
- -- should move to a 128-bit salt anyway!
- --
- COMMIT;
-
-
-TODO: Check if we missed miss any tables to migrate!
+
+ CREATE TABLE IF NOT EXISTS legitimization_requirements
+ (legitimization_requirement_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY
+ ,h_payto BYTEA NOT NULL CHECK (LENGTH(h_payto)=32)
+ ,required_checks VARCHAR NOT NULL
+ ,UNIQUE (h_payto, required_checks);
+ ) PARTITION BY HASH (h_payto);
+
+ CREATE TABLE IF NOT EXISTS legitimization_processes
+ (legitimization_serial_id BIGSERIAL UNIQUE
+ ,h_payto BYTEA NOT NULL CHECK (LENGTH(h_payto)=64)
+ ,expiration_time INT8 NOT NULL DEFAULT (0)
+ ,provider_section VARCHAR NOT NULL
+ ,provider_user_id VARCHAR DEFAULT NULL
+ ,provider_legitimization_id VARCHAR DEFAULT NULL
+ ) PARTITION BY HASH (h_payto);
+
+ COMMENT ON COLUMN legitimizations.legitimization_serial_id
+ IS 'unique ID for this legitimization process at the exchange';
+ COMMENT ON COLUMN legitimizations.h_payto
+ IS 'foreign key linking the entry to the wire_targets table, NOT a primary key (multiple legitimizations are possible per wire target)';
+ COMMENT ON COLUMN legitimizations.expiration_time
+ IS 'in the future if the respective KYC check was passed successfully';
+ COMMENT ON COLUMN legitimizations.provider_section
+ IS 'Configuration file section with details about this provider';
+ COMMENT ON COLUMN legitimizations.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 legitimizations.provider_legitimization_id
+ IS 'Identifier for the specific legitimization process at the provider. NULL if legitimization was not started.';
Merchant modifications
^^^^^^^^^^^^^^^^^^^^^^
+A new setting is required where the merchant backend
+can be configured for a business (default) or individual.
+
+.. note::
+
+ This still needs to be done!
+
We introduce new ``kyc_status``, ``kyc_timestamp`` and ``kyc_serial`` fields
into a new table with primary keys ``exchange_url`` and ``account``. This
status is updated whenever a deposit is created or tracked, or whenever the
@@ -206,33 +312,190 @@ long-poller return with positive news.
..note::
- Semi-related: The TMH_setup_wire_account() should be changed to use
+ 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".
+An additional complication will arise once the exchange can trigger a KYC
+fee (402) on ``/kyc-check/``. In this case, the merchant SPA must show the QR
+code to the merchant to allow the merchant to pay the KYC fee with a wallet.
+
Bank requirements
^^^^^^^^^^^^^^^^^
-The exchange primarily requires an OAuth 2.0 login page where the user
-can either login (and share an access token that grants access to only
-the username) or register to initiate the KYC process.
+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).
-Alternatives
-============
+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)
+ + individual or business user
+ + 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``):
-We may not need the oauth_username, but it seems saner to store it to
-provide a link to the legitimization resource server.
+ - 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
-We could also store the access token, but that seems slightly more
-dangerous and given the close business relationship is unnecessary.
+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.
-We may want to store some additional "permission level" obtained from the
-resource server to say for which of the operations (see requirements section)
-the legitimization is sufficient.
+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.
+
+
+
+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