/*
This file is part of TALER
Copyright (C) 2015-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
TALER; see the file COPYING. If not, see
*/
/**
* @file lib/exchange_api_common.c
* @brief common functions for the exchange API
* @author Christian Grothoff
*/
#include "platform.h"
#include "taler_json_lib.h"
#include
#include "exchange_api_common.h"
#include "exchange_api_handle.h"
#include "taler_signatures.h"
const struct TALER_EXCHANGE_SigningPublicKey *
TALER_EXCHANGE_get_signing_key_info (
const struct TALER_EXCHANGE_Keys *keys,
const struct TALER_ExchangePublicKeyP *exchange_pub)
{
for (unsigned int i = 0; inum_sign_keys; i++)
{
const struct TALER_EXCHANGE_SigningPublicKey *spk
= &keys->sign_keys[i];
if (0 == GNUNET_memcmp (exchange_pub,
&spk->key))
return spk;
}
return NULL;
}
enum GNUNET_GenericReturnValue
TALER_EXCHANGE_check_purse_create_conflict_ (
const struct TALER_PurseContractSignatureP *cpurse_sig,
const struct TALER_PurseContractPublicKeyP *purse_pub,
const json_t *proof)
{
struct TALER_Amount amount;
uint32_t min_age;
struct GNUNET_TIME_Timestamp purse_expiration;
struct TALER_PurseContractSignatureP purse_sig;
struct TALER_PrivateContractHashP h_contract_terms;
struct TALER_PurseMergePublicKeyP merge_pub;
struct GNUNET_JSON_Specification spec[] = {
TALER_JSON_spec_amount_any ("amount",
&amount),
GNUNET_JSON_spec_uint32 ("min_age",
&min_age),
GNUNET_JSON_spec_timestamp ("purse_expiration",
&purse_expiration),
GNUNET_JSON_spec_fixed_auto ("purse_sig",
&purse_sig),
GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
&h_contract_terms),
GNUNET_JSON_spec_fixed_auto ("merge_pub",
&merge_pub),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (proof,
spec,
NULL, NULL))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
TALER_wallet_purse_create_verify (purse_expiration,
&h_contract_terms,
&merge_pub,
min_age,
&amount,
purse_pub,
&purse_sig))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
if (0 ==
GNUNET_memcmp (&purse_sig,
cpurse_sig))
{
/* Must be the SAME data, not a conflict! */
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
return GNUNET_OK;
}
enum GNUNET_GenericReturnValue
TALER_EXCHANGE_check_purse_merge_conflict_ (
const struct TALER_PurseMergeSignatureP *cmerge_sig,
const struct TALER_PurseMergePublicKeyP *merge_pub,
const struct TALER_PurseContractPublicKeyP *purse_pub,
const char *exchange_url,
const json_t *proof)
{
struct TALER_PurseMergeSignatureP merge_sig;
struct GNUNET_TIME_Timestamp merge_timestamp;
const char *partner_url = NULL;
struct TALER_ReservePublicKeyP reserve_pub;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_mark_optional (
TALER_JSON_spec_web_url ("partner_url",
&partner_url),
NULL),
GNUNET_JSON_spec_timestamp ("merge_timestamp",
&merge_timestamp),
GNUNET_JSON_spec_fixed_auto ("merge_sig",
&merge_sig),
GNUNET_JSON_spec_fixed_auto ("reserve_pub",
&reserve_pub),
GNUNET_JSON_spec_end ()
};
char *payto_uri;
if (GNUNET_OK !=
GNUNET_JSON_parse (proof,
spec,
NULL, NULL))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
if (NULL == partner_url)
partner_url = exchange_url;
payto_uri = TALER_reserve_make_payto (partner_url,
&reserve_pub);
if (GNUNET_OK !=
TALER_wallet_purse_merge_verify (
payto_uri,
merge_timestamp,
purse_pub,
merge_pub,
&merge_sig))
{
GNUNET_break_op (0);
GNUNET_free (payto_uri);
return GNUNET_SYSERR;
}
GNUNET_free (payto_uri);
if (0 ==
GNUNET_memcmp (&merge_sig,
cmerge_sig))
{
/* Must be the SAME data, not a conflict! */
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
return GNUNET_OK;
}
enum GNUNET_GenericReturnValue
TALER_EXCHANGE_check_purse_coin_conflict_ (
const struct TALER_PurseContractPublicKeyP *purse_pub,
const char *exchange_url,
const json_t *proof,
struct TALER_DenominationHashP *h_denom_pub,
struct TALER_AgeCommitmentHash *phac,
struct TALER_CoinSpendPublicKeyP *coin_pub,
struct TALER_CoinSpendSignatureP *coin_sig)
{
const char *partner_url = NULL;
struct TALER_Amount amount;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
h_denom_pub),
GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
phac),
GNUNET_JSON_spec_fixed_auto ("coin_sig",
coin_sig),
GNUNET_JSON_spec_fixed_auto ("coin_pub",
coin_pub),
GNUNET_JSON_spec_mark_optional (
TALER_JSON_spec_web_url ("partner_url",
&partner_url),
NULL),
TALER_JSON_spec_amount_any ("amount",
&amount),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (proof,
spec,
NULL, NULL))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
if (NULL == partner_url)
partner_url = exchange_url;
if (GNUNET_OK !=
TALER_wallet_purse_deposit_verify (
partner_url,
purse_pub,
&amount,
h_denom_pub,
phac,
coin_pub,
coin_sig))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
return GNUNET_OK;
}
enum GNUNET_GenericReturnValue
TALER_EXCHANGE_check_purse_econtract_conflict_ (
const struct TALER_PurseContractSignatureP *ccontract_sig,
const struct TALER_PurseContractPublicKeyP *purse_pub,
const json_t *proof)
{
struct TALER_ContractDiffiePublicP contract_pub;
struct TALER_PurseContractSignatureP contract_sig;
struct GNUNET_HashCode h_econtract;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("h_econtract",
&h_econtract),
GNUNET_JSON_spec_fixed_auto ("econtract_sig",
&contract_sig),
GNUNET_JSON_spec_fixed_auto ("contract_pub",
&contract_pub),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (proof,
spec,
NULL, NULL))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
TALER_wallet_econtract_upload_verify2 (
&h_econtract,
&contract_pub,
purse_pub,
&contract_sig))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
if (0 ==
GNUNET_memcmp (&contract_sig,
ccontract_sig))
{
/* Must be the SAME data, not a conflict! */
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
return GNUNET_OK;
}
// FIXME: should be used...
enum GNUNET_GenericReturnValue
TALER_EXCHANGE_check_coin_denomination_conflict_ (
const json_t *proof,
const struct TALER_DenominationHashP *ch_denom_pub)
{
struct TALER_DenominationHashP h_denom_pub;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
&h_denom_pub),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (proof,
spec,
NULL, NULL))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
if (0 ==
GNUNET_memcmp (ch_denom_pub,
&h_denom_pub))
{
GNUNET_break_op (0);
return GNUNET_OK;
}
/* indeed, proof with different denomination key provided */
return GNUNET_OK;
}
enum GNUNET_GenericReturnValue
TALER_EXCHANGE_get_min_denomination_ (
const struct TALER_EXCHANGE_Keys *keys,
struct TALER_Amount *min)
{
bool have_min = false;
for (unsigned int i = 0; inum_denom_keys; i++)
{
const struct TALER_EXCHANGE_DenomPublicKey *dk = &keys->denom_keys[i];
if (! have_min)
{
*min = dk->value;
have_min = true;
continue;
}
if (1 != TALER_amount_cmp (min,
&dk->value))
continue;
*min = dk->value;
}
if (! have_min)
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
return GNUNET_OK;
}
enum GNUNET_GenericReturnValue
TALER_EXCHANGE_verify_deposit_signature_ (
const struct TALER_EXCHANGE_DepositContractDetail *dcd,
const struct TALER_ExtensionPolicyHashP *ech,
const struct TALER_MerchantWireHashP *h_wire,
const struct TALER_EXCHANGE_CoinDepositDetail *cdd,
const struct TALER_EXCHANGE_DenomPublicKey *dki)
{
if (GNUNET_OK !=
TALER_wallet_deposit_verify (&cdd->amount,
&dki->fees.deposit,
h_wire,
&dcd->h_contract_terms,
&dcd->wallet_data_hash,
&cdd->h_age_commitment,
ech,
&cdd->h_denom_pub,
dcd->wallet_timestamp,
&dcd->merchant_pub,
dcd->refund_deadline,
&cdd->coin_pub,
&cdd->coin_sig))
{
GNUNET_break_op (0);
TALER_LOG_WARNING ("Invalid coin signature on /deposit request!\n");
TALER_LOG_DEBUG ("... amount_with_fee was %s\n",
TALER_amount2s (&cdd->amount));
TALER_LOG_DEBUG ("... deposit_fee was %s\n",
TALER_amount2s (&dki->fees.deposit));
return GNUNET_SYSERR;
}
/* check coin signature */
{
struct TALER_CoinPublicInfo coin_info = {
.coin_pub = cdd->coin_pub,
.denom_pub_hash = cdd->h_denom_pub,
.denom_sig = cdd->denom_sig,
.h_age_commitment = cdd->h_age_commitment,
};
if (GNUNET_YES !=
TALER_test_coin_valid (&coin_info,
&dki->key))
{
GNUNET_break_op (0);
TALER_LOG_WARNING ("Invalid coin passed for /deposit\n");
return GNUNET_SYSERR;
}
}
/* Check coin does make a contribution */
if (0 < TALER_amount_cmp (&dki->fees.deposit,
&cdd->amount))
{
GNUNET_break_op (0);
TALER_LOG_WARNING ("Deposit amount smaller than fee\n");
return GNUNET_SYSERR;
}
return GNUNET_OK;
}
/**
* Parse account restriction in @a jrest into @a rest.
*
* @param jresta array of account restrictions in JSON
* @param[out] resta_len set to length of @a resta
* @param[out] resta account restriction array to set
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
parse_restrictions (const json_t *jresta,
unsigned int *resta_len,
struct TALER_EXCHANGE_AccountRestriction **resta)
{
size_t alen;
if (! json_is_array (jresta))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
alen = json_array_size (jresta);
if (0 == alen)
{
/* no restrictions, perfectly OK */
*resta = NULL;
return GNUNET_OK;
}
*resta_len = (unsigned int) alen;
GNUNET_assert (alen == *resta_len);
*resta = GNUNET_new_array (*resta_len,
struct TALER_EXCHANGE_AccountRestriction);
for (unsigned int i = 0; i<*resta_len; i++)
{
const json_t *jr = json_array_get (jresta,
i);
struct TALER_EXCHANGE_AccountRestriction *ar = &(*resta)[i];
const char *type = json_string_value (json_object_get (jr,
"type"));
if (NULL == type)
{
GNUNET_break (0);
goto fail;
}
if (0 == strcmp (type,
"deny"))
{
ar->type = TALER_EXCHANGE_AR_DENY;
continue;
}
if (0 == strcmp (type,
"regex"))
{
const char *regex;
const char *hint;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string (
"payto_regex",
®ex),
GNUNET_JSON_spec_string (
"human_hint",
&hint),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_json (
"human_hint_i18n",
&ar->details.regex.human_hint_i18n),
NULL),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (jr,
spec,
NULL, NULL))
{
/* bogus reply */
GNUNET_break_op (0);
goto fail;
}
ar->type = TALER_EXCHANGE_AR_REGEX;
ar->details.regex.posix_egrep = GNUNET_strdup (regex);
ar->details.regex.human_hint = GNUNET_strdup (hint);
continue;
}
/* unsupported type */
GNUNET_break (0);
return GNUNET_SYSERR;
}
return GNUNET_OK;
fail:
GNUNET_free (*resta);
*resta_len = 0;
return GNUNET_SYSERR;
}
enum GNUNET_GenericReturnValue
TALER_EXCHANGE_parse_accounts (
const struct TALER_MasterPublicKeyP *master_pub,
const json_t *accounts,
unsigned int was_length,
struct TALER_EXCHANGE_WireAccount was[static was_length])
{
memset (was,
0,
sizeof (struct TALER_EXCHANGE_WireAccount) * was_length);
GNUNET_assert (was_length ==
json_array_size (accounts));
for (unsigned int i = 0;
imaster_sig),
GNUNET_JSON_spec_end ()
};
json_t *account;
account = json_array_get (accounts,
i);
if (GNUNET_OK !=
GNUNET_JSON_parse (account,
spec_account,
NULL, NULL))
{
/* bogus reply */
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
if ( (NULL != master_pub) &&
(GNUNET_OK !=
TALER_exchange_wire_signature_check (
payto_uri,
conversion_url,
debit_restrictions,
credit_restrictions,
master_pub,
&wa->master_sig)) )
{
/* bogus reply */
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
if ( (GNUNET_OK !=
parse_restrictions (credit_restrictions,
&wa->credit_restrictions_length,
&wa->credit_restrictions)) ||
(GNUNET_OK !=
parse_restrictions (debit_restrictions,
&wa->debit_restrictions_length,
&wa->debit_restrictions)) )
{
/* bogus reply */
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
wa->payto_uri = GNUNET_strdup (payto_uri);
wa->priority = priority;
if (NULL != conversion_url)
wa->conversion_url = GNUNET_strdup (conversion_url);
if (NULL != bank_label)
wa->bank_label = GNUNET_strdup (bank_label);
} /* end 'for all accounts */
return GNUNET_OK;
}
/**
* Free array of account restrictions.
*
* @param ar_len length of @a ar
* @param[in] ar array to free contents of (but not @a ar itself)
*/
static void
free_restrictions (unsigned int ar_len,
struct TALER_EXCHANGE_AccountRestriction ar[static ar_len])
{
for (unsigned int i = 0; itype)
{
case TALER_EXCHANGE_AR_INVALID:
GNUNET_break (0);
break;
case TALER_EXCHANGE_AR_DENY:
break;
case TALER_EXCHANGE_AR_REGEX:
GNUNET_free (ar->details.regex.posix_egrep);
GNUNET_free (ar->details.regex.human_hint);
json_decref (ar->details.regex.human_hint_i18n);
break;
}
}
}
void
TALER_EXCHANGE_free_accounts (
unsigned int was_len,
struct TALER_EXCHANGE_WireAccount was[static was_len])
{
for (unsigned int i = 0; ipayto_uri);
GNUNET_free (wa->conversion_url);
GNUNET_free (wa->bank_label);
free_restrictions (wa->credit_restrictions_length,
wa->credit_restrictions);
GNUNET_array_grow (wa->credit_restrictions,
wa->credit_restrictions_length,
0);
free_restrictions (wa->debit_restrictions_length,
wa->debit_restrictions);
GNUNET_array_grow (wa->debit_restrictions,
wa->debit_restrictions_length,
0);
}
}
/* end of exchange_api_common.c */