more work on auditor, towards coin/denomination key checks

This commit is contained in:
Christian Grothoff 2017-03-15 19:28:05 +01:00
parent 36195e85ea
commit 6b5bfc57b5
No known key found for this signature in database
GPG Key ID: 939E6BE1E29FC3CC
3 changed files with 318 additions and 23 deletions

View File

@ -45,6 +45,16 @@
#include "taler_signatures.h"
/**
* How many coin histories do we keep in RAM at any given point in
* time? Used bound memory consumption of the auditor. Larger values
* reduce database accesses.
*
* Set to a VERY low value here for testing. Practical values may be
* in the millions.
*/
#define MAX_COIN_SUMMARIES 4
/**
* Return value from main().
*/
@ -202,9 +212,9 @@ static struct GNUNET_CONTAINER_MultiHashMap *denominations;
* Obtain information about a @a denom_pub.
*
* @param denom_pub key to look up
* @param[out] set to the hash of @a denom_pub, may be NULL
* @param[out] dki set to detailed information about @a denom_pub, NULL if not found, must
* NOT be freed by caller
* @param[out] dh set to the hash of @a denom_pub, may be NULL
* @return #GNUNET_OK on success, #GNUNET_NO for not found, #GNUNET_SYSERR for DB error
*/
static int
@ -566,7 +576,7 @@ handle_reserve_out (void *cls,
}
if (GNUNET_NO == ret)
{
report_row_inconsistency ("reserve_out",
report_row_inconsistency ("withdraw",
rowid,
"denomination key not found (foreign key constraint violated)");
return GNUNET_OK;
@ -578,7 +588,7 @@ handle_reserve_out (void *cls,
if ( (valid_start.abs_value_us > execution_date.abs_value_us) ||
(expire_withdraw.abs_value_us < execution_date.abs_value_us) )
{
report_row_minor_inconsistency ("reserve_out",
report_row_minor_inconsistency ("withdraw",
rowid,
"denomination key not valid at time of withdrawal");
}
@ -596,9 +606,9 @@ handle_reserve_out (void *cls,
&reserve_sig->eddsa_signature,
&reserve_pub->eddsa_pub))
{
report_row_inconsistency ("reserve_out",
report_row_inconsistency ("withdraw",
rowid,
"invalid signature for reserve withdrawal");
"invalid signature for withdrawal");
return GNUNET_OK;
}
@ -917,7 +927,12 @@ struct CoinSummary
/**
* Denomination of the coin with fee structure.
*/
struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki;
const struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki;
/**
* Hash of @e coin_pub.
*/
struct GNUNET_HashCode coin_hash;
/**
* Public key of the coin.
@ -925,10 +940,9 @@ struct CoinSummary
struct TALER_CoinSpendPublicKeyP coin_pub;
/**
* Total value lost of the coin (deposits, refreshs and fees minus refunds).
* Must be smaller than the coin's total (origional) value.
* List of transactions this coin was involved in.
*/
struct TALER_Amount spent;
struct TALER_EXCHANGEDB_TransactionList *tl;
};
@ -998,6 +1012,13 @@ struct CoinContext
*/
struct GNUNET_CONTAINER_MultiHashMap *coins;
/**
* Array of the coins in @e coins. Used to expire coins
* in a circular ring-buffer like fashion (to keep the
* working set in @e coins bounded).
*/
struct CoinSummary summaries[MAX_COIN_SUMMARIES];
/**
* Map for tracking information about denominations.
*/
@ -1029,6 +1050,11 @@ struct CoinContext
*/
struct TALER_Amount risk;
/**
* Current write/replace offset in the circular @e summaries buffer.
*/
unsigned int summaries_off;
};
@ -1177,6 +1203,114 @@ sync_denomination (void *cls,
}
/**
* Release memory occupied by a coin summary. Note that
* the actual @a value is NOT allocated (comes from the
* ring buffer), only the members of the struct need to be
* freed.
*
* @param cls the `struct CoinContext`
* @param key the hash of the coin's public key
* @param value a `struct DenominationSummary`
* @return #GNUNET_OK (continue to iterate)
*/
static int
free_coin (void *cls,
const struct GNUNET_HashCode *denom_hash,
void *value)
{
struct CoinContext *cc = cls;
struct CoinSummary *cs = value;
GNUNET_assert (GNUNET_YES ==
GNUNET_CONTAINER_multihashmap_remove (cc->coins,
&cs->coin_hash,
cs));
edb->free_coin_transaction_list (edb->cls,
cs->tl);
cs->tl = NULL;
return GNUNET_OK;
}
/**
* Obtain information about the coin from the cache or the database.
*
* @param cc caching information
* @param coin_pub public key of the coin to get information about
* @return NULL on error
*/
static struct CoinSummary *
get_coin_summary (struct CoinContext *cc,
const struct TALER_CoinSpendPublicKeyP *coin_pub)
{
struct CoinSummary *cs;
struct GNUNET_HashCode chash;
struct TALER_EXCHANGEDB_TransactionList *tl;
const struct TALER_CoinPublicInfo *coin;
GNUNET_CRYPTO_hash (coin_pub,
sizeof (*coin_pub),
&chash);
cs = GNUNET_CONTAINER_multihashmap_get (cc->coins,
&chash);
if (NULL != cs)
return cs; /* cache hit */
tl = edb->get_coin_transactions (edb->cls,
esession,
coin_pub);
if (NULL == tl)
{
GNUNET_break (0);
return NULL;
}
coin = NULL;
switch (tl->type)
{
case TALER_EXCHANGEDB_TT_DEPOSIT:
coin = &tl->details.deposit->coin;
break;
case TALER_EXCHANGEDB_TT_REFRESH_MELT:
coin = &tl->details.melt->coin;
break;
case TALER_EXCHANGEDB_TT_REFUND:
coin = &tl->details.refund->coin;
break;
}
GNUNET_assert (NULL != coin); /* hard check that switch worked */
if (GNUNET_OK !=
get_denomination_info (&coin->denom_pub,
&cs->dki,
NULL))
{
GNUNET_break (0);
edb->free_coin_transaction_list (edb->cls,
tl);
return NULL;
}
/* allocate coin slot in ring buffer */
if (MAX_COIN_SUMMARIES >= cc->summaries_off)
cc->summaries_off = 0;
cs = &cc->summaries[cc->summaries_off++];
GNUNET_assert (GNUNET_OK ==
free_coin (cc,
&cs->coin_hash,
cs));
/* initialize 'cs' and add to cache */
cs->coin_pub = *coin_pub;
cs->coin_hash = chash;
cs->tl = tl;
GNUNET_assert (GNUNET_YES ==
GNUNET_CONTAINER_multihashmap_put (cc->coins,
&cs->coin_hash,
cs,
GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
return cs;
}
/**
* Function called with details about all withdraw operations.
*
@ -1186,7 +1320,7 @@ sync_denomination (void *cls,
* @param denom_pub public denomination key of the deposited coin
* @param denom_sig signature over the deposited coin
* @param reserve_pub public key of the reserve
* @param reserve_sig signature over the withdraw operation
* @param reserve_sig signature over the withdraw operation (verified elsewhere)
* @param execution_date when did the wallet withdraw the coin
* @param amount_with_fee amount that was withdrawn
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
@ -1218,11 +1352,168 @@ withdraw_cb (void *cls,
ds = get_denomination_summary (cc,
&dh);
// FIXME: use ds, dki, etc.
// FIXME: update 'cc'
return GNUNET_OK;
}
/**
* Function called with details about coins that were melted,
* with the goal of auditing the refresh's execution.
*
* @param cls closure
* @param rowid unique serial ID for the refresh session in our DB
* @param coin_pub public key of the coin
* @param coin_sig signature from the coin
* @param amount_with_fee amount that was deposited including fee
* @param num_newcoins how many coins were issued
* @param noreveal_index which index was picked by the exchange in cut-and-choose
* @param session_hash what is the session hash
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
static int
refresh_session_cb (void *cls,
uint64_t rowid,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_CoinSpendSignatureP *coin_sig,
const struct TALER_Amount *amount_with_fee,
uint16_t num_newcoins,
uint16_t noreveal_index,
const struct GNUNET_HashCode *session_hash)
{
struct CoinContext *cc = cls;
struct TALER_RefreshMeltCoinAffirmationPS rmc;
struct CoinSummary *cs;
const struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki;
cs = get_coin_summary (cc,
coin_pub);
if (NULL == cs)
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
dki = cs->dki;
rmc.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT);
rmc.purpose.size = htonl (sizeof (rmc));
rmc.session_hash = *session_hash;
TALER_amount_hton (&rmc.amount_with_fee,
amount_with_fee);
rmc.melt_fee = dki->properties.fee_refresh;
rmc.coin_pub = *coin_pub;
if (GNUNET_OK !=
GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_MELT,
&rmc.purpose,
&coin_sig->eddsa_signature,
&coin_pub->eddsa_pub))
{
report_row_inconsistency ("melt",
rowid,
"invalid signature for coin melt");
return GNUNET_OK;
}
// TODO: update risk, denomination outstanding amounts, etc.
return GNUNET_OK;
}
/**
* Function called with details about deposits that have been made,
* with the goal of auditing the deposit's execution.
*
* @param cls closure
* @param rowid unique serial ID for the deposit in our DB
* @param merchant_pub public key of the merchant
* @param coin_pub public key of the coin
* @param coin_sig signature from the coin
* @param amount_with_fee amount that was deposited including fee
* @param h_proposal_data hash of the proposal data known to merchant and customer
* @param refund_deadline by which the merchant adviced that he might want
* to get a refund
* @param wire_deadline by which the merchant adviced that he would like the
* wire transfer to be executed
* @param receiver_wire_account wire details for the merchant, NULL from iterate_matching_deposits()
* @param done flag set if the deposit was already executed (or not)
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
static int
deposit_cb (void *cls,
uint64_t rowid,
const struct TALER_MerchantPublicKeyP *merchant_pub,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_CoinSpendSignatureP *coin_sig,
const struct TALER_Amount *amount_with_fee,
const struct GNUNET_HashCode *h_proposal_data,
struct GNUNET_TIME_Absolute refund_deadline,
struct GNUNET_TIME_Absolute wire_deadline,
const json_t *receiver_wire_account,
int done)
{
struct CoinContext *cc = cls;
struct CoinSummary *cs;
const struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki;
cs = get_coin_summary (cc,
coin_pub);
if (NULL == cs)
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
dki = cs->dki;
// TODO: verify signature
// TODO: update expected amounts in 'cc'
return GNUNET_OK;
}
/**
* Function called with details about coins that were refunding,
* with the goal of auditing the refund's execution.
*
* @param cls closure
* @param rowid unique serial ID for the refund in our DB
* @param coin_pub public key of the coin
* @param merchant_pub public key of the merchant
* @param merchant_sig signature of the merchant
* @param h_proposal_data hash of the proposal data known to merchant and customer
* @param rtransaction_id refund transaction ID chosen by the merchant
* @param amount_with_fee amount that was deposited including fee
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
static int
refund_cb (void *cls,
uint64_t rowid,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_MerchantPublicKeyP *merchant_pub,
const struct TALER_MerchantSignatureP *merchant_sig,
const struct GNUNET_HashCode *h_proposal_data,
uint64_t rtransaction_id,
const struct TALER_Amount *amount_with_fee)
{
struct CoinContext *cc = cls;
struct CoinSummary *cs;
const struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki;
cs = get_coin_summary (cc,
coin_pub);
if (NULL == cs)
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
dki = cs->dki;
// TODO: verify signature
// TODO: update expected amounts in 'cc'
return GNUNET_OK;
}
/**
@ -1301,7 +1592,7 @@ analyze_coins (void *cls)
edb->select_refreshs_above_serial_id (edb->cls,
esession,
42LL, // FIXME
NULL, // FIXME
&refresh_session_cb,
&cc))
{
// FIXME...
@ -1312,7 +1603,7 @@ analyze_coins (void *cls)
edb->select_deposits_above_serial_id (edb->cls,
esession,
42LL, // FIXME
NULL, // FIXME
&deposit_cb,
&cc))
{
// FIXME...
@ -1323,7 +1614,7 @@ analyze_coins (void *cls)
edb->select_refunds_above_serial_id (edb->cls,
esession,
42LL, // FIXME
NULL, // FIXME
&refund_cb,
&cc))
{
// FIXME...
@ -1338,6 +1629,9 @@ analyze_coins (void *cls)
&sync_denomination,
&cc);
GNUNET_CONTAINER_multihashmap_destroy (cc.denominations);
GNUNET_CONTAINER_multihashmap_iterate (cc.coins,
&free_coin,
&cc);
GNUNET_CONTAINER_multihashmap_destroy (cc.coins);
if (GNUNET_YES == rret)

View File

@ -794,6 +794,7 @@ postgres_prepare (PGconn *db_conn)
",num_newcoins"
",noreveal_index"
",melt_serial_id"
",session_hash"
" FROM refresh_sessions"
" WHERE melt_serial_id>=$1"
" ORDER BY melt_serial_id ASC",
@ -4832,6 +4833,7 @@ postgres_select_refreshs_above_serial_id (void *cls,
uint16_t num_newcoins;
uint16_t noreveal_index;
uint64_t rowid;
struct GNUNET_HashCode session_hash;
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub",
@ -4846,6 +4848,8 @@ postgres_select_refreshs_above_serial_id (void *cls,
&noreveal_index),
GNUNET_PQ_result_spec_uint64 ("melt_serial_id",
&rowid),
GNUNET_PQ_result_spec_auto_from_type ("session_hash",
&session_hash),
GNUNET_PQ_result_spec_end
};
if (GNUNET_OK !=
@ -4863,7 +4867,8 @@ postgres_select_refreshs_above_serial_id (void *cls,
&coin_sig,
&amount_with_fee,
num_newcoins,
noreveal_index);
noreveal_index,
&session_hash);
GNUNET_PQ_cleanup_result (rs);
}
PQclear (result);

View File

@ -623,17 +623,12 @@ typedef int
*
* @param cls closure
* @param rowid unique serial ID for the refresh session in our DB
* @param merchant_pub public key of the merchant
* @param coin_pub public key of the coin
* @param coin_sig signature from the coin
* @param amount_with_fee amount that was deposited including fee
* @param h_proposal_data hash of the proposal data known to merchant and customer
* @param refund_deadline by which the merchant adviced that he might want
* to get a refund
* @param wire_deadline by which the merchant adviced that he would like the
* wire transfer to be executed
* @param receiver_wire_account wire details for the merchant, NULL from iterate_matching_deposits()
* @param done flag set if the deposit was already executed (or not)
* @param num_newcoins how many coins were issued
* @param noreveal_index which index was picked by the exchange in cut-and-choose
* @param session_hash what is the session hash
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
typedef int
@ -643,7 +638,8 @@ typedef int
const struct TALER_CoinSpendSignatureP *coin_sig,
const struct TALER_Amount *amount_with_fee,
uint16_t num_newcoins,
uint16_t noreveal_index);
uint16_t noreveal_index,
const struct GNUNET_HashCode *session_hash);
/**