expand DB API with AML functions, fix purse refund calculations in libtalerexchange

This commit is contained in:
Christian Grothoff 2022-12-29 11:48:57 +01:00
parent fa840f7071
commit 26aa9d985e
No known key found for this signature in database
GPG Key ID: 939E6BE1E29FC3CC
10 changed files with 382 additions and 42 deletions

View File

@ -57,7 +57,7 @@ BEGIN
,partition_suffix
);
PERFORM comment_partitioned_column(
'0 for AML decision required, 1 for AML is OK, -1 for account is frozen (prevents further transactions)'
'0 for all OK, 1 for AML decision required, 2 for account is frozen (prevents further transactions)'
,'new_status'
,table_name
,partition_suffix

View File

@ -21,6 +21,7 @@ CREATE TABLE aml_staff
,master_sig BYTEA CHECK (LENGTH(master_sig)=64)
,decider_name VARCHAR NOT NULL
,is_active BOOLEAN NOT NULL
,read_only BOOLEAN NOT NULL
,last_change INT8 NOT NULL
);
COMMENT ON TABLE aml_staff
@ -33,5 +34,7 @@ COMMENT ON COLUMN aml_staff.decider_name
IS 'Name of the staff member.';
COMMENT ON COLUMN aml_staff.is_active
IS 'true if we are currently supporting the use of this AML staff member.';
COMMENT ON COLUMN aml_staff.is_active
IS 'true if the member has read-only access.';
COMMENT ON COLUMN aml_staff.last_change
IS 'Latest time when active status changed. Used to detect replays of old messages.';

View File

@ -53,7 +53,7 @@ BEGIN
,partition_suffix
);
PERFORM comment_partitioned_column(
'0 for AML decision required, 1 for AML is OK, -1 for account is frozen (prevents further transactions)'
'0 for all OK, 1 for AML decision required, 2 for account is frozen (prevents further transactions)'
,'status'
,table_name
,partition_suffix

View File

@ -25,6 +25,7 @@
#include "pg_lookup_kyc_process_by_account.h"
#include "pg_helper.h"
enum GNUNET_DB_QueryStatus
TEH_PG_lookup_kyc_process_by_account (
void *cls,

View File

@ -66,20 +66,20 @@ TEH_PG_select_purse (
PREPARE (pg,
"select_purse",
"SELECT "
" merge_pub"
",purse_creation"
",purse_expiration"
",h_contract_terms"
",amount_with_fee_val"
",amount_with_fee_frac"
",balance_val"
",balance_frac"
",merge_timestamp"
",purse_sig IS NOT NULL AS purse_deleted"
" FROM purse_requests"
" LEFT JOIN purse_merges USING (purse_pub)"
" LEFT JOIN purse_deletion USING (purse_pub)"
" WHERE purse_pub=$1;");
" pr.merge_pub"
",pr.purse_creation"
",pr.purse_expiration"
",pr.h_contract_terms"
",pr.amount_with_fee_val"
",pr.amount_with_fee_frac"
",pr.balance_val"
",pr.balance_frac"
",pm.merge_timestamp"
",pd.purse_sig IS NOT NULL AS purse_deleted"
" FROM purse_requests pr"
" LEFT JOIN purse_merges pm ON (pm.purse_pub = pr.purse_pub)"
" LEFT JOIN purse_deletion pd ON (pd.purse_pub = pr.purse_pub)"
" WHERE pr.purse_pub=$1;");
*merge_timestamp = GNUNET_TIME_UNIT_FOREVER_TS;
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"select_purse",

View File

@ -146,6 +146,18 @@ struct TALER_ReserveSignatureP
};
/**
* (Symmetric) key used to encrypt KYC attribute data in the database.
*/
struct TALER_AttributeKeyP
{
/**
* Actual key material.
*/
struct GNUNET_HashCode key;
};
/**
* @brief Type of public keys to for merchant authorizations.
* Merchants can issue refunds using the corresponding
@ -536,6 +548,30 @@ struct TALER_AmlOfficerSignatureP
};
/**
* Bitmask with possible AML decision states.
*/
enum TALER_AmlDecisionState
{
/**
* All AML requirements are currently satisfied.
*/
TALER_AML_NONE = 0,
/**
* An AML investigation is pending.
*/
TALER_AML_PENDING = 1,
/**
* An AML decision has concluded that the funds must be frozen.
*/
TALER_AML_FROZEN = 2
};
/**
* @brief Type of blinding keys for Taler.
* must be 32 bytes (DB)
@ -4597,6 +4633,7 @@ TALER_exchange_online_purse_status_verify (
* @param officer_name name of the officer
* @param change_date when to affect the status change
* @param is_active true to enable the officer
* @param read_only true to only allow read-only access
* @param master_priv private key to sign with
* @param[out] master_sig where to write the signature
*/
@ -4606,6 +4643,7 @@ TALER_exchange_offline_aml_officer_status_sign (
const char *officer_name,
struct GNUNET_TIME_Timestamp change_date,
bool is_active,
bool read_only,
const struct TALER_MasterPrivateKeyP *master_priv,
struct TALER_MasterSignatureP *master_sig);
@ -4617,6 +4655,7 @@ TALER_exchange_offline_aml_officer_status_sign (
* @param officer_name name of the officer
* @param change_date when to affect the status change
* @param is_active true to enable the officer
* @param read_only true to only allow read-only access
* @param master_pub public key to verify against
* @param master_sig the signature the signature
* @return #GNUNET_OK if the signature is valid
@ -4627,6 +4666,7 @@ TALER_exchange_offline_aml_officer_status_verify (
const char *officer_name,
struct GNUNET_TIME_Timestamp change_date,
bool is_active,
bool read_only,
const struct TALER_MasterPublicKeyP *master_pub,
const struct TALER_MasterSignatureP *master_sig);

View File

@ -2244,6 +2244,31 @@ typedef void
size_t buf_size);
/**
* Callback with KYC attributes about a particular user.
*
* @param cls closure
* @param h_payto account for which the attribute data is stored
* @param provider_section provider that must be checked
* @param birthdate birthdate of user, in format YYYY-MM-DD; can be NULL;
* digits can be 0 if exact day, month or year are unknown
* @param collection_time when was the data collected
* @param expiration_time when does the data expire
* @param enc_attributes_size number of bytes in @a enc_attributes
* @param enc_attributes encrypted attribute data
*/
typedef void
(*TALER_EXCHANGEDB_AttributeCallback)(
void *cls,
const struct TALER_PaytoHashP *h_payto,
const char *provider_section,
const char *birthdate,
struct GNUNET_TIME_Timestamp collection_time,
struct GNUNET_TIME_Timestamp expiration_time,
size_t enc_attributes_size,
const void *enc_attributes);
/**
* Function called with details about deposits that have been made,
* with the goal of auditing the deposit's execution.
@ -3100,6 +3125,46 @@ typedef void
const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue);
/**
* Return AML status.
*
* @param cls closure
* @param row_id current row in AML status table
* @param h_payto account for which the attribute data is stored
* @param threshold currently monthly threshold that would trigger an AML check
* @param decision_time when was the last decision made
*/
typedef void
(*TALER_EXCHANGEDB_AmlStatusCallback)(
void *cls,
uint64_t row_id,
const struct TALER_PaytoHashP *h_payto,
const struct TALER_Amount *threshold,
enum TALER_AmlDecisionState status);
/**
* Return historic AML decision.
*
* @param cls closure
* @param new_threshold new monthly threshold that would trigger an AML check
* @param new_status AML decision status
* @param decision_time when was the decision made
* @param justification human-readable text justifying the decision
* @param decider_pub public key of the staff member
* @param decider_sig signature of the staff member
*/
typedef void
(*TALER_EXCHANGEDB_AmlHistoryCallback)(
void *cls,
const struct TALER_Amount *new_threshold,
enum TALER_AmlDecisionState new_status,
struct GNUNET_TIME_Absolute decision_time,
const char *justification,
const struct TALER_AmlOfficerPublicKeyP *decider_pub,
const struct TALER_AmlOfficerSignatureP *decider_sig);
/**
* @brief The plugin API, returned from the plugin's "init" function.
* The argument given to "init" is simply a configuration handle.
@ -6435,6 +6500,248 @@ struct TALER_EXCHANGEDB_Plugin
void *kac_cls);
// FIXME: functions below here not yet implemented!
/**
* Store KYC attribute data.
*
* @param cls closure
* @param h_payto account for which the attribute data is stored
* @param kyc_prox key for similarity search
* @param provider_section provider that must be checked
* @param birthdate birthdate of user, in format YYYY-MM-DD; can be NULL;
* digits can be 0 if exact day, month or year are unknown
* @param collection_time when was the data collected
* @param expiration_time when does the data expire
* @param enc_attributes_size number of bytes in @a enc_attributes
* @param enc_attributes encrypted attribute data
* @return database transaction status
*/
enum GNUNET_DB_QueryStatus
(*insert_kyc_attributes)(
void *cls,
const struct TALER_PaytoHashP *h_payto,
const struct GNUNET_ShortHashCode *kyc_prox,
const char *provider_section,
const char *birthdate,
struct GNUNET_TIME_Timestamp collection_time,
struct GNUNET_TIME_Timestamp expiration_time,
size_t enc_attributes_size,
const void *enc_attributes);
/**
* Update KYC attribute data.
*
* @param cls closure
* @param h_payto account for which the attribute data is stored
* @param kyc_prox key for similarity search
* @param provider_section provider that must be checked
* @param birthdate birthdate of user, in format YYYY-MM-DD; can be NULL;
* digits can be 0 if exact day, month or year are unknown
* @param collection_time when was the data collected
* @param expiration_time when does the data expire
* @param enc_attributes_size number of bytes in @a enc_attributes
* @param enc_attributes encrypted attribute data
* @return database transaction status
*/
enum GNUNET_DB_QueryStatus
(*update_kyc_attributes)(
void *cls,
const struct TALER_PaytoHashP *h_payto,
const struct GNUNET_ShortHashCode *kyc_prox,
const char *provider_section,
const char *birthdate,
struct GNUNET_TIME_Timestamp collection_time,
struct GNUNET_TIME_Timestamp expiration_time,
size_t enc_attributes_size,
const void *enc_attributes);
/**
* Lookup similar KYC attribute data.
*
* @param cls closure
* @param h_payto account for which the attribute data is stored
* @param kyc_prox key for similarity search
* @param cb callback to invoke on each match
* @param cb_cls closure for @a cb
* @return database transaction status
*/
enum GNUNET_DB_QueryStatus
(*select_similar_kyc_attributes)(
void *cls,
const struct GNUNET_ShortHashCode *kyc_prox,
TALER_EXCHANGEDB_AttributeCallback cb,
void *cb_cls);
/**
* Lookup KYC attribute data for a specific account.
*
* @param cls closure
* @param h_payto account for which the attribute data is stored
* @param cb callback to invoke on each match
* @param cb_cls closure for @a cb
* @return database transaction status
*/
enum GNUNET_DB_QueryStatus
(*select_kyc_attributes)(
void *cls,
const struct TALER_PaytoHashP *h_payto,
TALER_EXCHANGEDB_AttributeCallback cb,
void *cb_cls);
/**
* Insert AML staff record.
*
* @param cls closure
* @param decider_pub public key of the staff member
* @param master_sig offline signature affirming the AML officer
* @param decider_name full name of the staff member
* @param is_active true to enable, false to set as inactive
* @param read_only true to set read-only access
* @param last_change when was the change made effective
* @return database transaction status
*/
enum GNUNET_DB_QueryStatus
(*insert_aml_officer)(
void *cls,
const struct TALER_AmlOfficerPublicKeyP *decider_pub,
const struct TALER_MasterSignatureP *master_sig,
const char *decider_name,
bool is_active,
bool read_only,
struct GNUNET_TIME_Absolute last_change);
/**
* Update AML staff record.
*
* @param cls closure
* @param decider_pub public key of the staff member
* @param master_sig offline signature affirming the AML officer
* @param decider_name full name of the staff member
* @param is_active true to enable, false to set as inactive
* @param read_only true to set read-only access
* @param last_change when was the change made effective
* @return database transaction status
*/
enum GNUNET_DB_QueryStatus
(*update_aml_officer)(
void *cls,
const struct TALER_AmlOfficerPublicKeyP *decider_pub,
const struct TALER_MasterSignatureP *master_sig,
const char *decider_name,
bool is_active,
bool read_only,
struct GNUNET_TIME_Absolute last_change);
/**
* Fetch AML staff record.
*
* @param cls closure
* @param decider_pub public key of the staff member
* @param[out] master_sig offline signature affirming the AML officer
* @param[out] decider_name full name of the staff member
* @param[out] is_active true to enable, false to set as inactive
* @param[out] read_only true to set read-only access
* @param[out] last_change when was the change made effective
* @return database transaction status
*/
enum GNUNET_DB_QueryStatus
(*lookup_aml_officer)(
void *cls,
const struct TALER_AmlOfficerPublicKeyP *decider_pub,
struct TALER_MasterSignatureP *master_sig,
char **decider_name,
bool *is_active,
bool *read_only,
struct GNUNET_TIME_Absolute *last_change);
/**
* Trigger AML process, an account has crossed the threshold. Inserts or
* updates the AML status.
*
* @param cls closure
* @param h_payto account for which the attribute data is stored
* @param threshold_crossed existing threshold that was crossed
* @return database transaction status
*/
enum GNUNET_DB_QueryStatus
(*trigger_aml_process)(
void *cls,
const struct TALER_PaytoHashP *h_payto,
const struct TALER_Amount *threshold_crossed);
/**
* Lookup AML decisions that have a particular state.
*
* @param cls closure
* @param decision which decision states to filter by
* @param row_off offset to start from
* @param forward true to go forward in time, false to go backwards
* @param cb callback to invoke on each match
* @param cb_cls closure for @a cb
* @return database transaction status
*/
enum GNUNET_DB_QueryStatus
(*select_aml_processes)(
void *cls,
enum TALER_AmlDecisionState decision,
uint64_t row_off,
bool forward,
TALER_EXCHANGEDB_AmlStatusCallback cb,
void *cb_cls);
/**
* Lookup AML decision history for a particular account.
*
* @param cls closure
* @param h_payto which account should we return the AML decision history for
* @param cb callback to invoke on each match
* @param cb_cls closure for @a cb
* @return database transaction status
*/
enum GNUNET_DB_QueryStatus
(*select_aml_history)(
void *cls,
const struct TALER_PaytoHashP *h_payto,
TALER_EXCHANGEDB_AmlHistoryCallback cb,
void *cb_cls);
/**
* Insert an AML decision. Inserts into AML history and insert or updates AML
* status.
*
* @param cls closure
* @param h_payto account for which the attribute data is stored
* @param new_threshold new monthly threshold that would trigger an AML check
* @param new_status AML decision status
* @param decision_time when was the decision made
* @param justification human-readable text justifying the decision
* @param decider_pub public key of the staff member
* @param decider_sig signature of the staff member
* @return database transaction status
*/
enum GNUNET_DB_QueryStatus
(*insert_aml_decision)(
void *cls,
const struct TALER_PaytoHashP *h_payto,
const struct TALER_Amount *new_threshold,
enum TALER_AmlDecisionState new_status,
struct GNUNET_TIME_Absolute decision_time,
const char *justification,
const struct TALER_AmlOfficerPublicKeyP *decider_pub,
const struct TALER_AmlOfficerSignatureP *decider_sig);
};
#endif /* _TALER_EXCHANGE_DB_H */

View File

@ -1336,15 +1336,11 @@ help_purse_deposit (struct CoinHistoryParseContext *pc,
}
if (refunded)
{
/* We add the amount to refunds here, the original
deposit will be added to the balance later because
we still return GNUNET_YES, thus effectively
cancelling out this operation with respect to
the final balance. */
/* We wave the deposit fee. */
if (0 >
TALER_amount_add (&pc->rtotal,
&pc->rtotal,
amount))
&pc->dk->fees.deposit))
{
/* overflow in refund history? inconceivable! Bad exchange! */
GNUNET_break_op (0);
@ -1415,15 +1411,6 @@ help_purse_refund (struct CoinHistoryParseContext *pc,
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
if (0 >
TALER_amount_add (&pc->rtotal,
&pc->rtotal,
amount))
{
/* overflow in refund history? inconceivable! Bad exchange! */
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
return GNUNET_NO;
}

View File

@ -174,17 +174,17 @@ run (void *cls,
TALER_TESTING_cmd_purse_create_with_deposit (
"purse-with-deposit",
MHD_HTTP_OK,
"{\"amount\":\"EUR:1\",\"summary\":\"ice cream\"}",
"{\"amount\":\"EUR:0.99\",\"summary\":\"ice cream\"}",
true, /* upload contract */
GNUNET_TIME_UNIT_MINUTES, /* expiration */
"withdraw-coin-1",
"EUR:1.01",
"EUR:1.00",
NULL),
TALER_TESTING_cmd_purse_poll (
"push-poll-purse-before-merge",
MHD_HTTP_OK,
"purse-with-deposit",
"EUR:1",
"EUR:0.99",
true,
GNUNET_TIME_UNIT_MINUTES),
TALER_TESTING_cmd_contract_get (
@ -206,13 +206,13 @@ run (void *cls,
TALER_TESTING_cmd_status (
"push-check-post-merge-reserve-balance-get",
"create-reserve-1",
"EUR:1.03",
"EUR:1.02",
MHD_HTTP_OK),
/* POST history doesn't yet support P2P transfers */
TALER_TESTING_cmd_reserve_status (
"push-check-post-merge-reserve-balance-post",
"create-reserve-1",
"EUR:1.03",
"EUR:1.02",
MHD_HTTP_OK),
/* Test conflicting merge */
TALER_TESTING_cmd_purse_merge (
@ -261,12 +261,12 @@ run (void *cls,
TALER_TESTING_cmd_status (
"pull-check-post-merge-reserve-balance-get",
"create-reserve-1",
"EUR:2.02",
"EUR:2.01",
MHD_HTTP_OK),
TALER_TESTING_cmd_reserve_status (
"push-check-post-merge-reserve-balance-post",
"create-reserve-1",
"EUR:2.02",
"EUR:2.01",
MHD_HTTP_OK),
/* create 2nd purse for a deposit conflict */
TALER_TESTING_cmd_purse_create_with_reserve (

View File

@ -54,7 +54,7 @@ struct TALER_MasterAmlOfficerStatusPS
struct GNUNET_HashCode h_officer_name GNUNET_PACKED;
/**
* 1 if enabled, 0 if disabled, in NBO.
* Bitmask: 1 if enabled; 2 for read-only access. in NBO.
*/
uint32_t is_active GNUNET_PACKED;
};
@ -67,6 +67,7 @@ TALER_exchange_offline_aml_officer_status_sign (
const char *officer_name,
struct GNUNET_TIME_Timestamp change_date,
bool is_active,
bool read_only,
const struct TALER_MasterPrivateKeyP *master_priv,
struct TALER_MasterSignatureP *master_sig)
{
@ -75,7 +76,7 @@ TALER_exchange_offline_aml_officer_status_sign (
.purpose.size = htonl (sizeof (as)),
.change_date = GNUNET_TIME_timestamp_hton (change_date),
.officer_pub = *officer_pub,
.is_active = htonl (is_active ? 1 : 0)
.is_active = htonl ((is_active ? 1 : 0) + (read_only ? 2 : 0))
};
GNUNET_CRYPTO_hash (officer_name,
@ -93,6 +94,7 @@ TALER_exchange_offline_aml_officer_status_verify (
const char *officer_name,
struct GNUNET_TIME_Timestamp change_date,
bool is_active,
bool read_only,
const struct TALER_MasterPublicKeyP *master_pub,
const struct TALER_MasterSignatureP *master_sig)
{
@ -101,7 +103,7 @@ TALER_exchange_offline_aml_officer_status_verify (
.purpose.size = htonl (sizeof (as)),
.change_date = GNUNET_TIME_timestamp_hton (change_date),
.officer_pub = *officer_pub,
.is_active = htonl (is_active ? 1 : 0)
.is_active = htonl ((is_active ? 1 : 0) + (read_only ? 2 : 0))
};
GNUNET_CRYPTO_hash (officer_name,