diff options
Diffstat (limited to 'src/exchange-lib')
34 files changed, 11245 insertions, 0 deletions
diff --git a/src/exchange-lib/Makefile.am b/src/exchange-lib/Makefile.am new file mode 100644 index 00000000..f5e58b40 --- /dev/null +++ b/src/exchange-lib/Makefile.am @@ -0,0 +1,62 @@ +# This Makefile.am is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include + +if USE_COVERAGE +  AM_CFLAGS = --coverage -O0 +  XLIB = -lgcov +endif + +lib_LTLIBRARIES = \ +  libtalerexchange.la + +libtalerexchange_la_LDFLAGS = \ +  -version-info 0:0:0 \ +  -no-undefined + +libtalerexchange_la_SOURCES = \ +  exchange_api_common.c exchange_api_common.h \ +  exchange_api_context.c exchange_api_context.h \ +  exchange_api_json.c exchange_api_json.h \ +  exchange_api_handle.c exchange_api_handle.h \ +  exchange_api_admin.c \ +  exchange_api_deposit.c \ +  exchange_api_deposit_wtid.c \ +  exchange_api_refresh.c \ +  exchange_api_refresh_link.c \ +  exchange_api_reserve.c \ +  exchange_api_wire.c \ +  exchange_api_wire_deposits.c + +libtalerexchange_la_LIBADD = \ +  -lgnunetutil \ +  -ljansson \ +  $(XLIB) + +if HAVE_LIBCURL +libtalerexchange_la_LIBADD += -lcurl +else +if HAVE_LIBGNURL +libtalerexchange_la_LIBADD += -lgnurl +endif +endif + +check_PROGRAMS = \ +  test_exchange_api + +TESTS = \ +  $(check_PROGRAMS) + +test_exchange_api_SOURCES = \ +  test_exchange_api.c +test_exchange_api_LDADD = \ +  libtalerexchange.la \ +  $(LIBGCRYPT_LIBS) \ +  $(top_builddir)/src/util/libtalerutil.la \ +  -lgnunetutil \ +  -ljansson + +EXTRA_DIST = \ +  test-exchange-home/config/exchange-common.conf \ +  test-exchange-home/master.priv \ +  test-exchange-home/denomkeys/ \ +  test-exchange-home/signkeys/ diff --git a/src/exchange-lib/afl-generate.sh b/src/exchange-lib/afl-generate.sh new file mode 100644 index 00000000..6ae83308 --- /dev/null +++ b/src/exchange-lib/afl-generate.sh @@ -0,0 +1,34 @@ +#!/bin/sh +# +# This file is part of TALER +# Copyright (C) 2015 GNUnet e.V. +# +#  TALER is free software; you can redistribute it and/or modify it under the +#  terms of the GNU Affero 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 Affero General Public License for more details. +# +#  You should have received a copy of the GNU Affero General Public License along with +#  TALER; see the file COPYING.  If not, If not, see <http://www.gnu.org/licenses/> +# +# +# This will generate testcases in a directory 'afl-tests', which can then +# be moved into src/exchange/afl-tests/ to be run during exchange-testing. +# +# This script uses American Fuzzy Loop (AFL) to fuzz the exchange to +# automatically create tests with good coverage.  You must install +# AFL and set AFL_HOME to the directory where AFL is installed +# before running.  Also, a directory "baseline/" should exist with +# templates for inputs for AFL to fuzz.  These can be generated +# by running wireshark on loopback while running 'make check' in +# this directory.  Save each HTTP request to a new file. +# +# Note that you want to switch 'TESTRUN = NO' and pre-init the +# database before running this, otherwise it will be awfully slow. +# +# Must be run from this directory. +# +$AFL_HOME/afl-fuzz -i baseline/ -m 250 -o afl-tests/ -f /tmp/afl-input taler-exchange-httpd -f /tmp/afl-input -d test-exchange-home/ -C diff --git a/src/exchange-lib/baseline/admin_add_incoming.req b/src/exchange-lib/baseline/admin_add_incoming.req new file mode 100644 index 00000000..677678b5 --- /dev/null +++ b/src/exchange-lib/baseline/admin_add_incoming.req @@ -0,0 +1,7 @@ +POST /admin/add/incoming HTTP/1.1 +Host: localhost:8081 +Accept: */* +Content-Type: application/json +Content-Length: 220 + +{"reserve_pub":"TMZCK5CFM1KZQGY1WTF0CEZZPGA0670G94969RF79PA5106ARTK0","amount":{"currency":"EUR","value":5,"fraction":10000},"execution_date":"/Date(1442821651)/","wire":{"type":"TEST","bank":"source bank","account":42}}
\ No newline at end of file diff --git a/src/exchange-lib/baseline/deposit.req b/src/exchange-lib/baseline/deposit.req new file mode 100644 index 00000000..f50d83e4 --- /dev/null +++ b/src/exchange-lib/baseline/deposit.req @@ -0,0 +1,8 @@ +POST /deposit HTTP/1.1 +Host: localhost:8081 +Accept: */* +Content-Type: application/json +Content-Length: 1658 +Expect: 100-continue + +{"ub_sig":"51SPJSSDESGPR80A40M74WV140520818ECG26E9M8S0M6CSH6X334GSN8RW30D9G8MT46CA660W34GSG6MT4AD9K8GT3ECSH6MVK0E2374V38H1M8MR4CDJ66MWK4E1S6MR3GCT28CV32H1Q8N23GCHG70S36C1K8MS3GCSN8RV36D9S710KGD9K6GWKEGJ28GRM4CJ56X1K6DJ18D2KGHA46D13GDA66GVK4GHJ8N13AE9J8RVK6GT184S48E1K6X336G9Q8N142CJ4692M6EA16GRKJD9N6523ADA36X13GG9G70TK6DHN68R36CT18GR4CDSJ6CW3GCT364W46CSR8RV42GJ474SMADSH851K4H9Q8GS42CHS8RV3GCSJ64V46DSN8RSM6HHN6N246D9S6934AH9P6X23JGSH652K0DJ5612KJGA26N242CH35452081918G2J2G0","timestamp":"/Date(1442821652)/","f":{"currency":"EUR","value":5,"fraction":0},"wire":{"type":"TEST","bank":"dest bank","account":42},"coin_pub":"JXWK4NS0H2W4V4BETQ90CCEDADP6QQ3MV3YZ7RV2KXEM8PWXE8Q0","H_wire":"YQED9FDYPKK2QQYB3FS19Y15ZMKBAXJP2C73CXASAF1KM6ZYY723TEJ3HBR6D864A7X5W58G92QJ0A9PFMZNB81ZP9NJAQQCCABM4RG","H_contract":"1CMEEFQ5S4QJGGAMVYFV07XQRHQA311CR2MTRNC5M9KZV6ETDV1SY00WJFEV2CG9BXQTEQPZAF8A54C2HX32TZCN20VBGPFPS2Z16B0","merchant_pub":"C36TEXQXFW00170C2EJ66ZR0000CX9VPZNZG00109NX020000000","denom_pub":"51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GT58S2K2HJ16H336C9N8CVK4E9N6H1MADHH61330HHM6N1K8E1H8RVKJH256D1M6E1K8RWKJGSH8S2M6DJ170TK2H266GTK8DSS64RKJDJ26D144DJ474SK0GHQ711MAD9G752M2CJ58S1KJDA570SK2E9G8N23GCJ28S146DHH610K2H1Q8CW3GGA16S146H9G68TKACSQ6914CE1H691K2E9N6RWM8H9P8CWM2H9S8GSK0H9P6D1K6H9G6X0M4C2171144HJ46N334H9J692M4H9M8MR4CCJ46GRKEGA46533CDJ38MV4CH9K892MAH1P8S2K6D9K6N246E256H244G9Q6D346GJ56S23JGHJ690KADHJ8H242H2575132CSM6X1M4G9N6RR48E9H8MVM8E9354520818CMG26C1H60R30C935452081918G2J2G0","transaction_id":1,"refund_deadline":"/Date(0)/","coin_sig":"X16E0DP8C2BJNVNX09G24FFC5GA4W7RN2YXZP9WJTAN9BY6B4GMA39QNYR51XNNEZ3H1J7TP0K9G55JZ8V7WS7CZMD7E64HWYBFWM00"}
\ No newline at end of file diff --git a/src/exchange-lib/baseline/keys.req b/src/exchange-lib/baseline/keys.req new file mode 100644 index 00000000..a9503a86 --- /dev/null +++ b/src/exchange-lib/baseline/keys.req @@ -0,0 +1,7 @@ +GET /keys HTTP/1.1
 +User-Agent: Wget/1.16.3 (linux-gnu)
 +Accept: */*
 +Accept-Encoding: identity
 +Host: 127.0.0.1:8081
 +Connection: Keep-Alive
 +
 diff --git a/src/exchange-lib/baseline/refresh_link.req b/src/exchange-lib/baseline/refresh_link.req new file mode 100644 index 00000000..acf3dff5 --- /dev/null +++ b/src/exchange-lib/baseline/refresh_link.req @@ -0,0 +1,5 @@ +GET /refresh/link?coin_pub=WQHES0X5XK43VBG1Y8FXR2YEJM04HQVMDTCS07MH691XWADG8QCG HTTP/1.1 +Host: localhost:8081 +Accept: */* +Content-Type: application/json + diff --git a/src/exchange-lib/baseline/refresh_melt.req b/src/exchange-lib/baseline/refresh_melt.req new file mode 100644 index 00000000..98b5b638 --- /dev/null +++ b/src/exchange-lib/baseline/refresh_melt.req @@ -0,0 +1,8 @@ +POST /refresh/melt HTTP/1.1 +Host: localhost:8081 +Accept: */* +Content-Type: application/json +Content-Length: 34136 +Expect: 100-continue + +{"new_denoms":["51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSN8CV38D9G8MT46CSG650M4GHQ6N0M6HHR6X142EA26H13JG9N6RV3AD1P6GV34HHK8S0M6CT28RTMAH9R8H0K2GHN68W46G9K8RT32CT560RKCC268RRKJCSJ70S30CA564SKAD9J84VM8DSG611MAG9R6H2MAG9M8MT4CDHG8CWM8HHH84T3AD1N6X0KEH9P8RRMCCSQ8MT30CSK6MVK0GA48CW30D9J6WTK0CSN650MAD1R70TM4CHG850K2G9H89142DT488S42DT36RVM6DSS6GTK2CSJ84WMCDHP8CV3GDSP6GTM4G9H6N246GHH6GWKGD1N70R34HA689148GHP8RVK2E9M8MRKJCJ28D0KEGT26CS3EH256RVKEDSJ74WKGGT16RSM6H9M6GVKGDS354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSN8CV38D9G8MT46CSG650M4GHQ6N0M6HHR6X142EA26H13JG9N6RV3AD1P6GV34HHK8S0M6CT28RTMAH9R8H0K2GHN68W46G9K8RT32CT560RKCC268RRKJCSJ70S30CA564SKAD9J84VM8DSG611MAG9R6H2MAG9M8MT4CDHG8CWM8HHH84T3AD1N6X0KEH9P8RRMCCSQ8MT30CSK6MVK0GA48CW30D9J6WTK0CSN650MAD1R70TM4CHG850K2G9H89142DT488S42DT36RVM6DSS6GTK2CSJ84WMCDHP8CV3GDSP6GTM4G9H6N246GHH6GWKGD1N70R34HA689148GHP8RVK2E9M8MRKJCJ28D0KEGT26CS3EH256RVKEDSJ74WKGGT16RSM6H9M6GVKGDS354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSN8CV38D9G8MT46CSG650M4GHQ6N0M6HHR6X142EA26H13JG9N6RV3AD1P6GV34HHK8S0M6CT28RTMAH9R8H0K2GHN68W46G9K8RT32CT560RKCC268RRKJCSJ70S30CA564SKAD9J84VM8DSG611MAG9R6H2MAG9M8MT4CDHG8CWM8HHH84T3AD1N6X0KEH9P8RRMCCSQ8MT30CSK6MVK0GA48CW30D9J6WTK0CSN650MAD1R70TM4CHG850K2G9H89142DT488S42DT36RVM6DSS6GTK2CSJ84WMCDHP8CV3GDSP6GTM4G9H6N246GHH6GWKGD1N70R34HA689148GHP8RVK2E9M8MRKJCJ28D0KEGT26CS3EH256RVKEDSJ74WKGGT16RSM6H9M6GVKGDS354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSP8MSM2E1J70T36G9Q750K8D9P692KCHA36MSM4GHS6MWK6DJ564S32DHQ8MT3EHHG84W32G9M88RKGG9J74W3GC1R8H1M6DHQ891K8C9M84TM4CSQ6124CC1G60T30H9Q6CVK2DSH74T4CGT1751MAC1P88RK2GSR8GTKJEA46N0M2DT48CV4CDSP6MSKJE9K64SM6CSS6GVMCD9G6CT3EDT6652K2GJ46RT36GHJ85138H1Q6114AC1Q6H23ACHS8RR46H9M60VKECT688VKGC9S8CW44E1G612K6H1K70VKADSN68T34D1K70R32G9R6934AE246GRKCCJ66N142G9K8CVK8GHS6WS3GH9J74S4ADHM8MSKGD248D330CHK6MVM2DA48GSKCGHH88TKCC9354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSP8MSM2E1J70T36G9Q750K8D9P692KCHA36MSM4GHS6MWK6DJ564S32DHQ8MT3EHHG84W32G9M88RKGG9J74W3GC1R8H1M6DHQ891K8C9M84TM4CSQ6124CC1G60T30H9Q6CVK2DSH74T4CGT1751MAC1P88RK2GSR8GTKJEA46N0M2DT48CV4CDSP6MSKJE9K64SM6CSS6GVMCD9G6CT3EDT6652K2GJ46RT36GHJ85138H1Q6114AC1Q6H23ACHS8RR46H9M60VKECT688VKGC9S8CW44E1G612K6H1K70VKADSN68T34D1K70R32G9R6934AE246GRKCCJ66N142G9K8CVK8GHS6WS3GH9J74S4ADHM8MSKGD248D330CHK6MVM2DA48GSKCGHH88TKCC9354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSP8MSM2E1J70T36G9Q750K8D9P692KCHA36MSM4GHS6MWK6DJ564S32DHQ8MT3EHHG84W32G9M88RKGG9J74W3GC1R8H1M6DHQ891K8C9M84TM4CSQ6124CC1G60T30H9Q6CVK2DSH74T4CGT1751MAC1P88RK2GSR8GTKJEA46N0M2DT48CV4CDSP6MSKJE9K64SM6CSS6GVMCD9G6CT3EDT6652K2GJ46RT36GHJ85138H1Q6114AC1Q6H23ACHS8RR46H9M60VKECT688VKGC9S8CW44E1G612K6H1K70VKADSN68T34D1K70R32G9R6934AE246GRKCCJ66N142G9K8CVK8GHS6WS3GH9J74S4ADHM8MSKGD248D330CHK6MVM2DA48GSKCGHH88TKCC9354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSP8MSM2E1J70T36G9Q750K8D9P692KCHA36MSM4GHS6MWK6DJ564S32DHQ8MT3EHHG84W32G9M88RKGG9J74W3GC1R8H1M6DHQ891K8C9M84TM4CSQ6124CC1G60T30H9Q6CVK2DSH74T4CGT1751MAC1P88RK2GSR8GTKJEA46N0M2DT48CV4CDSP6MSKJE9K64SM6CSS6GVMCD9G6CT3EDT6652K2GJ46RT36GHJ85138H1Q6114AC1Q6H23ACHS8RR46H9M60VKECT688VKGC9S8CW44E1G612K6H1K70VKADSN68T34D1K70R32G9R6934AE246GRKCCJ66N142G9K8CVK8GHS6WS3GH9J74S4ADHM8MSKGD248D330CHK6MVM2DA48GSKCGHH88TKCC9354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSP8MSM2E1J70T36G9Q750K8D9P692KCHA36MSM4GHS6MWK6DJ564S32DHQ8MT3EHHG84W32G9M88RKGG9J74W3GC1R8H1M6DHQ891K8C9M84TM4CSQ6124CC1G60T30H9Q6CVK2DSH74T4CGT1751MAC1P88RK2GSR8GTKJEA46N0M2DT48CV4CDSP6MSKJE9K64SM6CSS6GVMCD9G6CT3EDT6652K2GJ46RT36GHJ85138H1Q6114AC1Q6H23ACHS8RR46H9M60VKECT688VKGC9S8CW44E1G612K6H1K70VKADSN68T34D1K70R32G9R6934AE246GRKCCJ66N142G9K8CVK8GHS6WS3GH9J74S4ADHM8MSKGD248D330CHK6MVM2DA48GSKCGHH88TKCC9354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSP8MSM2E1J70T36G9Q750K8D9P692KCHA36MSM4GHS6MWK6DJ564S32DHQ8MT3EHHG84W32G9M88RKGG9J74W3GC1R8H1M6DHQ891K8C9M84TM4CSQ6124CC1G60T30H9Q6CVK2DSH74T4CGT1751MAC1P88RK2GSR8GTKJEA46N0M2DT48CV4CDSP6MSKJE9K64SM6CSS6GVMCD9G6CT3EDT6652K2GJ46RT36GHJ85138H1Q6114AC1Q6H23ACHS8RR46H9M60VKECT688VKGC9S8CW44E1G612K6H1K70VKADSN68T34D1K70R32G9R6934AE246GRKCCJ66N142G9K8CVK8GHS6WS3GH9J74S4ADHM8MSKGD248D330CHK6MVM2DA48GSKCGHH88TKCC9354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSP8MSM2E1J70T36G9Q750K8D9P692KCHA36MSM4GHS6MWK6DJ564S32DHQ8MT3EHHG84W32G9M88RKGG9J74W3GC1R8H1M6DHQ891K8C9M84TM4CSQ6124CC1G60T30H9Q6CVK2DSH74T4CGT1751MAC1P88RK2GSR8GTKJEA46N0M2DT48CV4CDSP6MSKJE9K64SM6CSS6GVMCD9G6CT3EDT6652K2GJ46RT36GHJ85138H1Q6114AC1Q6H23ACHS8RR46H9M60VKECT688VKGC9S8CW44E1G612K6H1K70VKADSN68T34D1K70R32G9R6934AE246GRKCCJ66N142G9K8CVK8GHS6WS3GH9J74S4ADHM8MSKGD248D330CHK6MVM2DA48GSKCGHH88TKCC9354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSP8MSM2E1J70T36G9Q750K8D9P692KCHA36MSM4GHS6MWK6DJ564S32DHQ8MT3EHHG84W32G9M88RKGG9J74W3GC1R8H1M6DHQ891K8C9M84TM4CSQ6124CC1G60T30H9Q6CVK2DSH74T4CGT1751MAC1P88RK2GSR8GTKJEA46N0M2DT48CV4CDSP6MSKJE9K64SM6CSS6GVMCD9G6CT3EDT6652K2GJ46RT36GHJ85138H1Q6114AC1Q6H23ACHS8RR46H9M60VKECT688VKGC9S8CW44E1G612K6H1K70VKADSN68T34D1K70R32G9R6934AE246GRKCCJ66N142G9K8CVK8GHS6WS3GH9J74S4ADHM8MSKGD248D330CHK6MVM2DA48GSKCGHH88TKCC9354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30H1J65244GA27533AC248MW34C1P6N136GSR88V3AC1H8N1K8CJ2891M8DA2712K8E1N6GW48DSJ8GW4CDSJ6RT48DJ28GW46DT46H136E216D14AE9Q60VKECT26S2M8EA38MSK2D9S6MTM4CHQ88T3GD9M8D34ACHJ6MR38CT46X1M6CSH74SKCG9Q712K4GHG712M2D9Q8D1K2E9K6X2K0D1H8RVK0E9Q610M2E1K612K4E9M6MTMCH9P60WMCCHR6CWKGE2168SK4GJ26WV3GGT188S38H1P8MW44D1K6S344HHP74W4CG9M8H1K0C1M8933ECA16D13AH9P6RTK4GSM6CRK0GA36RV4ADSM7524CH9G8N0KAEA288WK0GJ16RWK0CSK6N0KJGT674T3GCS354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30H1J65244GA27533AC248MW34C1P6N136GSR88V3AC1H8N1K8CJ2891M8DA2712K8E1N6GW48DSJ8GW4CDSJ6RT48DJ28GW46DT46H136E216D14AE9Q60VKECT26S2M8EA38MSK2D9S6MTM4CHQ88T3GD9M8D34ACHJ6MR38CT46X1M6CSH74SKCG9Q712K4GHG712M2D9Q8D1K2E9K6X2K0D1H8RVK0E9Q610M2E1K612K4E9M6MTMCH9P60WMCCHR6CWKGE2168SK4GJ26WV3GGT188S38H1P8MW44D1K6S344HHP74W4CG9M8H1K0C1M8933ECA16D13AH9P6RTK4GSM6CRK0GA36RV4ADSM7524CH9G8N0KAEA288WK0GJ16RWK0CSK6N0KJGT674T3GCS354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30H1J65244GA27533AC248MW34C1P6N136GSR88V3AC1H8N1K8CJ2891M8DA2712K8E1N6GW48DSJ8GW4CDSJ6RT48DJ28GW46DT46H136E216D14AE9Q60VKECT26S2M8EA38MSK2D9S6MTM4CHQ88T3GD9M8D34ACHJ6MR38CT46X1M6CSH74SKCG9Q712K4GHG712M2D9Q8D1K2E9K6X2K0D1H8RVK0E9Q610M2E1K612K4E9M6MTMCH9P60WMCCHR6CWKGE2168SK4GJ26WV3GGT188S38H1P8MW44D1K6S344HHP74W4CG9M8H1K0C1M8933ECA16D13AH9P6RTK4GSM6CRK0GA36RV4ADSM7524CH9G8N0KAEA288WK0GJ16RWK0CSK6N0KJGT674T3GCS354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30H1J65244GA27533AC248MW34C1P6N136GSR88V3AC1H8N1K8CJ2891M8DA2712K8E1N6GW48DSJ8GW4CDSJ6RT48DJ28GW46DT46H136E216D14AE9Q60VKECT26S2M8EA38MSK2D9S6MTM4CHQ88T3GD9M8D34ACHJ6MR38CT46X1M6CSH74SKCG9Q712K4GHG712M2D9Q8D1K2E9K6X2K0D1H8RVK0E9Q610M2E1K612K4E9M6MTMCH9P60WMCCHR6CWKGE2168SK4GJ26WV3GGT188S38H1P8MW44D1K6S344HHP74W4CG9M8H1K0C1M8933ECA16D13AH9P6RTK4GSM6CRK0GA36RV4ADSM7524CH9G8N0KAEA288WK0GJ16RWK0CSK6N0KJGT674T3GCS354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30H1J65244GA27533AC248MW34C1P6N136GSR88V3AC1H8N1K8CJ2891M8DA2712K8E1N6GW48DSJ8GW4CDSJ6RT48DJ28GW46DT46H136E216D14AE9Q60VKECT26S2M8EA38MSK2D9S6MTM4CHQ88T3GD9M8D34ACHJ6MR38CT46X1M6CSH74SKCG9Q712K4GHG712M2D9Q8D1K2E9K6X2K0D1H8RVK0E9Q610M2E1K612K4E9M6MTMCH9P60WMCCHR6CWKGE2168SK4GJ26WV3GGT188S38H1P8MW44D1K6S344HHP74W4CG9M8H1K0C1M8933ECA16D13AH9P6RTK4GSM6CRK0GA36RV4ADSM7524CH9G8N0KAEA288WK0GJ16RWK0CSK6N0KJGT674T3GCS354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30H1J65244GA27533AC248MW34C1P6N136GSR88V3AC1H8N1K8CJ2891M8DA2712K8E1N6GW48DSJ8GW4CDSJ6RT48DJ28GW46DT46H136E216D14AE9Q60VKECT26S2M8EA38MSK2D9S6MTM4CHQ88T3GD9M8D34ACHJ6MR38CT46X1M6CSH74SKCG9Q712K4GHG712M2D9Q8D1K2E9K6X2K0D1H8RVK0E9Q610M2E1K612K4E9M6MTMCH9P60WMCCHR6CWKGE2168SK4GJ26WV3GGT188S38H1P8MW44D1K6S344HHP74W4CG9M8H1K0C1M8933ECA16D13AH9P6RTK4GSM6CRK0GA36RV4ADSM7524CH9G8N0KAEA288WK0GJ16RWK0CSK6N0KJGT674T3GCS354520818CMG26C1H60R30C935452081918G2J2G0"],"transfer_pubs":[["J65E5480S6ZVPBA5HV9D4APYXFS527DAJGN5HKZ5EWP89EFSKGK0"],["TYZZG5HSXTYJMA9XD3EMTX2S36V7F4VPR1RHQPKJSTWXJD2KPDAG"],["2MN2X2X7P6GJCN09Z6ZDF2R9W1W3BSYJER7FTBSDDSWMRJ7DG0DG"]],"melt_coins":[{"value_with_fee":{"currency":"EUR","value":4,"fraction":0},"coin_pub":"WQHES0X5XK43VBG1Y8FXR2YEJM04HQVMDTCS07MH691XWADG8QCG","denom_pub":"51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GT58S2K2HJ16H336C9N8CVK4E9N6H1MADHH61330HHM6N1K8E1H8RVKJH256D1M6E1K8RWKJGSH8S2M6DJ170TK2H266GTK8DSS64RKJDJ26D144DJ474SK0GHQ711MAD9G752M2CJ58S1KJDA570SK2E9G8N23GCJ28S146DHH610K2H1Q8CW3GGA16S146H9G68TKACSQ6914CE1H691K2E9N6RWM8H9P8CWM2H9S8GSK0H9P6D1K6H9G6X0M4C2171144HJ46N334H9J692M4H9M8MR4CCJ46GRKEGA46533CDJ38MV4CH9K892MAH1P8S2K6D9K6N246E256H244G9Q6D346GJ56S23JGHJ690KADHJ8H242H2575132CSM6X1M4G9N6RR48E9H8MVM8E9354520818CMG26C1H60R30C935452081918G2J2G0","denom_sig":"51SPJSSDESGPR80A40M74WV140520818ECG26DJ384R4CCHP8RTK6DJ46X344C9M6S1KEDSQ8923EE9P84SKAH9R84SMAGT670SM4GT66123EDJ46RVK0E1N65336H226H2K8CA38CVKCH9N711K2H1S6X134GHJ6924CGJ68RRMAH9P6MT30H23651KCD1N6X0M8GA58CS34E9K70V32E1S8MTK8GJ270WK0HA368V3GDHM68RK2CHS610MAH1H8RR42CT58H342DSG8D1MCC1J6N0KEHHS6WVM2GHS60WM8G9G6GTMAC258D338HHP8MWM2GSS8MW3GCA270TK6G9G6WRM2GA56RSMCC9R88T3JEA270SK0HJ284R3CC9Q6WVK0DT570TKJE9P68SK0CSG88T3CCHJ60R48C9N611KAD9N6WVMAC9N70T46GH35452081918G2J2G0","confirm_sig":"ZKYCWKVTVQCWMWTAQRNRSKGQP6AC14VF7W525ZQYFZ6J2T1GM7PE7945W3HNW32S0MZBX8K65VQPXH83NMQR2SGTY7DX2T60RKHFP00"}],"secret_encs":[["DCZPFTHF1GZRXFVSYJ4AK1HH4QE46T8F60X1AQ6T6BF1WHBNTJ3FEGH7Y1TH5A6YT10GFYGV12S8V9MB4HEKHRYBBMR7JJ9KB264TNR"],["ANBQS74BACGAWEC0F0654HYGZB3NBSNXVD4KZY4TRP4JMRADJE2Y5BYV513Y37PPMR6V5P200FEAHTZ5TS3R9G9MBYM6Z144N853PQG"],["1X6P2JR301MPR5JYMKPV1HXAFXKH9M09HWVQ4PS6JNK1K6E8ZSMW1C6NS79CPFP443QBVEW9SRWXXJQT91EHFKJ8007AB4S64SPWN3G"]],"coin_evs":[["1XB4A97X8ATPH1KG5NAMJGJY258AGK0MKVJRHN4NGKPRD17DQM0CMTT351FB7T56NTVA9E5VTH9Q12A10C3YVDEYJB4B66EC1NPXNPMNSVTBJ7HGM4DXFB03K1BXK247QK581CGE54286BTK6B5VW0FTS4AZ3NQP43V9E6VV1HTKG1CM5WQJEY14TAC8P2YSV2XH61RG4E84R","JVNGX2AR684RF4VRYFSS69WNMJBDDS9Y4AXFRX5JRFES5QBTMM51SB84BN61S5W0R7PZGK09NQ8QMX7Z90AE0JJH76J7DCM94DKWMV88K966NEA2CNMS50PG6D0K73619K67S9HJ0PQTQH8YD91P4CQQZTRPV7Y2RHEGMBB2G67ZBVVH1S9Q0HYX69Y2B3J2WYFMH3BJESVGE","6MZ6ZDF26MSAZN3VK3QDY62MD3AXZTK8K46A7ZFNBVCR33AE6H3A309F822EKPHT2YTJB6PMSPSJ3ZAE99Z5Q2J8F87X5N723J713A0QR87KYJ2NZ3PFCS5CC8FXJYK6YXZ4ND6FHNJRJZZZZRXE05VP14XW1BZFYZGQQZ8J05FYKY8AYFNCZ50WT7W8M83XS50GTYJGQTNPT","Q0JWR4WRPYT7TCNCV47RJ1FW3HEFQABN9EE27DBMDB4FYAVK8X9KM38HH9KN8QW28RXFXAKT3MGHXPGRWCY9P313CPR5Z2XSR3ZGKFHQHTP1A02SSD0DHH12RCV5TJ7M86WXWT7F00CQ8HV5H90MFEDX6J2X9JNX26NC0PRH0YXHGW7XVWXF00ZEJCGJDEZ1EVXA5BNW3SAEY","B20PSGCM77EVSXEC6T41N1T55KX76B5K3X38VBQ8NSAN2BKNH0W4DQGVZQTBA2588YCRW1TN9G721GP7ZCP9H32BBC9HM3XFTNJ4QBV2FMM7KSG6T0H418EBNBGKZXMTD9ZDVV6A2TEBMXSEKYVK9HCZT8EEQ7E44DWBDHN274N9D4ZDEFCZXNF0MKBM3TYZGHJ5T71T88CE4","F95WT8Y2WRXGK07MY3FMV1Y6PS44CMP59KK8G82EKW3Z0Y6C6QZ2CVC4A6DF9N3E6HW8SQ3CF4FSD1QC2DN5E9DM5G762DW491BAE5RC2A3YPGJTZ3XBFJ3R3NSRDRZMHR6Y755QDRZZEVFTEPN7MZ90SE6KTQS80CHNMTX15NMZPNFJSCQSEJ9RQJKZBH9ZZFMNXT8BXM7K4","1B8GKDJYJYCZ8Y2HCETV6EWA8FRVR0AAD7TRXFXBHNF7G10CZYDFTKBJ8X0J0M7VR1KQV67SF5VMM7VVG5ZQ4F9QEKRV3GSBB4M75CAXSEXSHDT36RJ6A98EKP3EZXT65HX2K788ZE4SVE5N2NJ8Q26P318PK9X69YWB4W7R1F1F9WHZ8WKJ69G5YZ1T9WR1N8CXTJASXW3A8","FV0Y6B21F01D5EH31R80BHG7GKA08C7HN3FYDMGQE6HJ2Y9ZSVJ17HR8Y22WGPN98T08E6XP4PZNMJ3RHAFK8DAVW3AYM5VNPHV3JSB5B64F0H60EMQNA59A2QAFRV3FCHJQATY87SGT9E67HRVE5QDP9HJC7A351WM55VV5MW3T251VVZ8N5V2GVX6MD14MR9ZX9TBS4N30J","K8QSPQTEV9YZC7MC6AT9R8Y0QGG4PT1V45KSSZR2YY9SBA3QEQG7R6YR4RKEKR5DGDM2K4DN9K128TQMMPFWZR8N95F29Q8E2WBD22F5SRFPJ2KJA6SEZ6RMPCHG6C4BJEV7PZAEVTNJDK8T79PJCB5B97D5WQ52TT2DB6Q4S599D61AA54FCVM9R3AB03PDTYZ0PCKBMJ23G","1655VA0GT3ASG2JVEZ93BWE01YDEA70ZW2FD1W3AZTD4B4WK9GYPMM9Y6KXDGDDB2TMDF5CWXNAX0B7E396E1NH3GCK8KFHMGXNPG0CTD2KNW9X3QB4HKK68JM2WGMF05VJ6K4G76R4A10JWPEZTD554TSFYVEM0355C9E5P9W5GFFFYQMTAXG43V8GP7P4BDBD81Q9G87MXG","J3T1MSDB9ZHCCNAYDXEQAMETNSCX5DJYQBCXGREMYAWAQ8AJCY5JV00HHX31DVT7X5S25CEX88707E81X4KNCN7RBRFMFNP8QZHFZHXNS6MN7BD4MGRVQEC50EWH6N6EFY1GA039KYJKX24XMGZXKB27H5R0RKN7YC00EKN99SGKF3YN7DPR8880MG457438G1YJR7BGRCG7J","DX73NQ14FVC978B1HP03MWBD83YT6NVP607WB126RAJPKPB11GSWB7H9W0QJSH9WD4SN9JJE6FDMQZTMJXCN1AKDBKKVPWW9VBEAE0YVCG64S16TXMSF5FTWNXYG7RDTHK03BXDGVXSPZFE7A2F6FPK9WCBBZSMKB8EJFGJK1PKKBHVA1Q2MX91NK32XPF00WFWZCC3N72JY2","PKSMQXMCT9H53FHGKVVPPTTHJ0M5X4SSR0XARN34VV7DX9ZA9N8379NVQKN4E5V6JXAPEVGPREWD720RZCVB90DDHRFW8GG6G434GR1A77SVPG9GKP2DEKBVP3CZ9WDMQD9RFZZ2Z3ZQ34YVD2J1Y4AZ52ADY5FXPB06W9NQ9NH7AQVHTPMSTP181MZM507M777SAPSCR9J8M","1XV578XYBQYYSV9V8ZRACRG3JTN103BZFPF5JXHF8YDCCCJM9XS343JYM8N6Q3J8D0NF2T8TPZKCB32MC1GXWZZRRWQW77JSSYXGA8136DNFYTCKB8EP277KBXAYXBW0ZX7S13J5NDGR7ZJ7QTAAPTEAG9MX6QBT9Z7ZZ1MECV7Z0D7GE7Z6KWASE5T4SW0MFHKVD1EAFNG0P","86PDN3KMT8AW8TVXEBGG3199YSEDMQX91QW82QVZ9TPYCNS3YKK3NCK5J64VKPKGJGQJP8E3RX97NZ4RDQ0PR21QS93MTWXSHKCTK2KTH7TQPKVQSWPGVC8CZDEGD9X484HJKMQG56RP1MZE7AWKK9C5ZK7MSN21GTG1KNF591Q8DVX0TM06BD8VGF6QNC745CFRMRSYHXK7P","KGXXZAECQ6DETR97HRYKFC4A1WAKC3S0FYNHS16J342WMPPP78NHWZXSEPN04XEQY6QNR9ECWZNXY6BZHXWAW2EV42295GBVA4R2J2KAK9EF7MR2789EHY0MVQJME1ADGEPGCQ02WHWT3C173SPDKW1DA5C8HBF847KV00A3F3ZM2HE18N5MFFQXBB014HK0MC4NW6G19AESR","92G786WTFN4BP3Z2RX1Q79ZPHGMPYC21K646XA8AB97V103DSJY0S4Y1FHHSHYQ8WZPWF0W07VY6CDG66438K8E8DWN7CCSB6R8KSC2VNAQBVC1ZD2H9VG33309B6QEKK5PYAGJZN5NG0CJXW9RFGPCNJJZW6KHM4TCMDJVKV9P953Q0TWVM2YZWK37504QJ41MZ4KXN52M30"],["NG0RYVSNPXP4B2NEYCTVC5A0EW5PFFW8V3PH9X5FFQ2VHCETMWDPZ75HBSC8QDBCWZ6EV9SEN1SMEMXABK3YTXBDDXYTN0JNKX1ME9T71JJTZZQ7XA1890DZR3ZAMD0XW8GZENDNAP4R5B80DQYFDEENSB141DZ0HG3A3FZA7JMCSBY0Y0PB2AXNS25Q6CQ5WA7GWCWA09PD2","R70NVRHFT6HCP69K8QQM9HQX0CZQYG09VV2VA7RKCVFDRTNH44XZHRBEJTBW1ASDS7WYA47PBVPB9T77B32ER6V2745Y8B1P22CZ5T70WTE6Y4HSS60CM5SFB6QT1VXTVW9G0DAC3XY2QDX6TKKQA1HX3RB0GPJH47D08KB1Y4EP1SXW851CFNEE373YJHBNYXMSE3MJBCQFP","JGZK4NS2E8D1CTR8ZAR5CHGN7T09Q41P545ZNNB7RMYV0CZ9CQWJWZZWRQZDRDCHV5PQS9G6WZB37CHQMZ7Y207D23FQAPMN6CP30CXYEQ449R3V7B8BYGJ84VYW60201B4343GG75TTTN947VVEYA3BWKRY4M5XCXSPAGW1TTAH9VAE8CBXBKAKJESX8XWS4W5PYBG8793TR","CEVHPNNSWR438ZWEK27YRYBKZXVBT8AM9QP2KXWC59ZSM8Q7J0GZNXZYBES04RB7BJY4D5YCPT3VRT7EPK0576809CG98X5SPTQPSR4TZ0R6DWMXH9893FCTVJX2A4H2EGSGP3Q6H53FD4CSDBMZFZVACXPMW1CR07A1GG7XFXSK1BP1BTGMBQD0818G6BC6SBFTXS1Z68PD8","72BFMSPJ57Q1B88JS6NX8YETQP0GSSRHXM25ZBGNW06YGYJ7FT1D7KVGF4PRKKQK8KN4MGHK5DXRQDTNEJSRZYGAN6DV5QAW9PACT7FQQXG6G4JS7J129VXFQP3DYTV0455Q37H5YPVGMWXZ4GNBQJ86HWWD670XY8JAR4WTYHX8FWJXZW57Q94KK79SD51HBJH1SJ4Q5C04W","2MDED6PYFV264D0SETN9MD4F0SADGVD75J8Y16DTSABNPW09AKE7A5F4QNB2124WDQ0HW4SGHCX26NAG46Q960Y01VYSX0GQHVKESSMEAZ2426GJ8KR0E6KTAXDZY6HZ6DRSPXXGZ07YZT2XF696CP8215HE92RGT7A50H5XXV2FDGFWJXYNCB7QND27ZWYWTK0KTCRYT104Y","267QZPCB0QGC63V9E75Y41J5285Q3FX6KQHM08MAKZ248X9WJK5H6H903C0T02W4G3EA22E0C1MHCWP5VRAEC8D0J0VYW9JNCH9YGPQJ3P941AADABVXG4M2K724C8VCFZQNXYJAMY4Q65WYHW6VYPN9H1K7ZABPAZX82SSSBPYKJZBREJ15MSS50R9HJ6FWW7T9P8ES015PY","7W8HPWAGBVFK0KTWG02RRTWKAE4W0PJH76GH9JWD7H48BS7CVD1T38QBX7HJVC6PJEAJ5NGP7MSJ9D5H1FD2T9Q5X4YQMQ1F80H4G8GRGXFKS863G48HN15D72EZSM9V8M75FXCH3639PHENNV35GA6TWPRS70HEJSBAX9A52DWTB63ZCSZXFGACZEQFGR0EKEE0AZB3HKE2J","JMGGS2DPDKNQKTM593M69Z1R331654KR6N12Q7EZPX20BMHV1P9JZZ8HNN6VHQ76YKWNMW9HR3173C2J66R8EVVWZAR001Z18F9HJ9YBVSM1Y94PJSM57GNVRNN0GH11A3ZRCA4WBKFC64A4RE6KTYMMJKJNHTR9A75QC7JWG5YY9D0J8TM9KXQW8495V702VS0E40121HNHY","REH6B17HSBJGMMPYKG7EA1J4G1D7H95N20P647AWHJKY3VA5BDZKN84FEG0G0FK6TKNN8D0TDFQ41610SK9CBR27SFD16PKEZ7AP97NB9ENPQRQ9PEKR76KB6VFWP666MJBTQJ5R7WGKTTE3ZN5Z2VWY2CPB437746CYBN52G2P2Q7V6SDGXV5HPYJBKXESS1JMVH3EHDC988","MRE9Q877Z18VMFNH1PWNH9N3Z5YE2SDZCVVFBZAA41SSRC2F81D6F9TCZP2AGYAEXWM3977KWJ6SV215ZJ24RAWA94NCKEFQ1CMW8MYJ063EP68NVVPH51SFVYQVHYC45MGNEXK61R9KFK6RWYV5Y6CHMAYZTW5M17WBSHBZ79F4DSWHVVY7774FX3WEEVM8EZ0WMJWY99A1P","2CM59NJVJ68NYX5ZJM2ZX7JK8EW7JXAVE0H2D1ZTX9X6X72P9768WK5GG3ED7MGEYGFQBK5YSSJ0DM68DRPDPTAHQGWTGJ7E79GAW96BXHYV64BQXXEV9YQ0XR84G8VZWQ8Z7T9VX7VQ0HHX1SAS3NGNZAH1H03KPFNAZMNMGV3H4Q3PC96JHGAP5JNDWA1FAFJ410REB33D0","2KEXQVJHACQX7V1XGF8PA6DV0EVAKFNSA037YJ3BP91NNQB60ZBAEFT9DKVW2M19R51B8HGQ2RCBSWBRHD0RFC95TYGJVKNJSCMQPXV7SM6A1JH18DFPWB1AJBS4SSVJXZ9V72TY2P8Q8ZTBMGCYERXCT4QJMASTQM1T22C9AXMMPZ1BHKNB2FBJ3TY9AQP4ZGHRSRBKR75E4","5Q9ZFEFF6C6XPGM78EXAWJF2CKJVJ0SPA113XT7R5JTRW7MKE70TETKK1MBRSTF566AEZB2CBX6CR10J6DPAQ92GC6XYK27A52KQGQHR5W346RGF28WE2PPC8S3XWTGCGVP318AKKTC8GZQ62FRYZVPTVF07HWYHDESQFKYNSDRNV8Q5KPGRQM27SK3CX14RA2K9KZ0NJJ0FE","7ZK7H90WBS080M9TH8XH9DNENKQ78YHYSXWF16VRM76YDKP4RG97DEM1DBX84VXH98M64CJS1GYTDKG4XA8XCNSZ4KQ12GAXCJC168DBR8Q4M29X2Q70WNR7BZDNH0DQJMR154M0C1NH2EHJBKA76PNZT2369PT9Q4961EBCY5FE85EKCX7584GXBMHQF1GRDVCCDVY7TH210","CFSSPFE27VKZHPS7D48AAHE0C2XKXS774CDPFE530P5C4EFZVZEP1PTS3NYB7KBNHNAGMZX8YZJVTNJAB1DE7GCRG43Q2JVS15RGJC4K9PDA0Q2MG4DGRX1CM28HE8CB1XWVW7MCDJHPNE28NWN0CYHDES8GHAZ6Y50NNZRN31FV8C28V0PRNZK4WFAPJ4EGV5NQPHGSZAWNG","9XJB29QY06J1DYBXQRV9F7M3T11JTGSZHX8D0Y80Q07M646VTHNFKNA19CG84BA2DKRE1D5DS3M72QH29S9EZEADW6PCS1FXGCMGZ0RQXT5ASSX7VBQ40NGPYY6PFE6CYV45YYEGPGJ2DCQCNYP2Q9E78SBD53QTQVYZG7W3QE8EASEPSZXP1VVB1TYYHN0B1BZGY2JKGWWPY"],["CSATAT8AQRB91XKHJD1B7MK1DPVPV991WZM4Y2NF0G8SNQSR391KBDGHR0AWGZ8V6EYXKNGTACT9GRBWWAPYTDPWFZ5QPY9GY8Z4YDXRK1QW30YT6JMAYQM2TH36XP81G2X55785ZJWCRXZ9QQDGGMMM8QZTWN9TPW8JXH4XM8A0DPG5354PF3782GVC8J8DXD7KY88KT0FZY","4MG8W9X1M5361HRRG6W9QF13EQDRFWVAQGMWJKEESZ3FBCWP7QQGQBBDT3ZYE3XARBVGJAVFQRQPAYA1PC4CQYRX0CZTS0E4QZB3ACMXG9XJ91VPP7X0ZG08WXWMT02M9V4HCHJ7MJQZ7EWCRJAG7E14BQ465R123P6QFGY770GN0BBNT040S3XPC88CD8X3D8497DE2YA1MT","DCSBAHV955VGACX5WE2N02QZSPQ9N375DCS0W3298N8KF59YHVTS7FYJ1NE011VWF8ET7F6SQZHW1NWJZW3XYWN2ZMWXCRGH80CD6N8YA6EE8Z2N2PW4T65M0DDX4YFXZPG61E7JJGK03VTDW5J8WS2F2R88GNNJMWBQCYBWWTZ34H7J5XAPBBY3AF4M6A8C0RAHPX0FS044W","5CKW4V17Q2XAV54F07X0C2NZCRZ21B9M79JM37F10DZJ3YMY9WPEWSYEDC6DP1QZ2DRRAXDCFG1WZH3C0Z58Y26XGSC28R9Q3ZFSKZHS0F0DC08JWY01W9GPECCN0CP0Q19N7GWX7HAFVR0PR89P0DTBPJ02NKCG14HZK933C6C62AFS8GFWNWN7FJ042YVSH69BAM9FQX4JT","G3J5ZS034N9TKESCEBQJQVXJMYB7HX48QT7QTEZA688019KSSCFVDG2TD963JA9NQF2G80Q3J1853PRB8QD2WFWDSMPP9X4FXKAEH79Q2X8K2KZJ120848KY421H81WAA1AMPZ6APD67RDRBKM0H5GN974TBQ8S5ENFRJSNTJRJEDY57GRHAN8V2R2PCNCY1VWH92EN1MJA4P","BFTC88T9TMD6EQPMBN6M7ZES14AGZMK8CYF0BQ7JAPPAPKXRTW83Q2ZF5270PG9WN0SET02R2HHFEM4J66GDWK2GYCF6GBZWF0GAH9ZC0AHVT3JW2XWDZ2X1DH2WB9Y10A7QB83S3E1S5DQEJXD6AS4J6HCBJBZJPC1EJZP3E5R5ANY1EYZ7P1692WSNABA0KWZS5T05DK51M","HFBFB0NVHM82461FBYT7SKWR1R38QD6MPMTSZXQAAH7CKVSKE21M4KZMWE3EF2C0FFJ6QGZPFWYXM58NANQMHP293WNJD5NYM31ZGV3ZS8SDP9DK1ZVSF5WXW3PEKYCAR3C7J6QZKAT1CVQF86P6M3S7XPVN8EV212SG4ENANH650EF7H5RDBN29PRREWB5JA4PSX9TQR1Y92","1JT0M6407RNBHQFZ0SKEPHQW2ZTZTQ5X7BZ1Q1FBQVR9QBH3EP20CH1KJSP71PC8Z4WFKJ97442BWG991RJC0XE7Y71PW9SXMPX4MG1SVN371ZG3GNCBX3Z9AZAAR9PGHS46AVG1RH71X1WQZ7W9HGQPRYDPCMQBQ7N2PD0AH8EJMT2ASXKPNK09S9H9B0ZTYMB5KK2VB7C72","E5PWK8V063470P271354CCZ3JS5H6GB8GWPDCDB125CVT4YHR3MGTYAFTDJP38F2WSNERY5KNE9VK85N70WC6VZ22WF7MABK2FJZM3179NHBV92FQC19BR21002AZHHE4A171QYFX69NXV3XA90HY27ECQ28YCPZNKEPQXXVCNHVSFJJE8553M68KM81EW45GXEV3797W2KX4","JQGN8RXXYMHG8MD2GSNQ6NQ8QCH8A6WDT0S0WY4ZP5424RGY975300CQC1C575K619FY2JN71RQ62RM6W2TBVZX0Z5B8A2PQ2MTN6W486AHBJKX6ST9HA05EQ9VMJN3PGDSF1GACENFK8CA1EW4KNG185PG6CP7YY1TNX3FZ43H020K8SYTQWMPM6FAZ5N4MA6CQND1ZJ6AC0","NJ5PM2Z4VNYKG6X93KWH0GQZNMBKHYM2FWX0BSNPGJ18Q7R32YC9FPC2Z45MPJ5EZY4E3FD0CD67TB9K4879SQ33SCG4Z9BARNQVWXMK975GRGAB4306D1W7PF1QD1MTVW79DHY3M2VVC01R4T41M3TXYTD1X3JF1PBGC4T63MF9MJS4KZTS03Z84AY6C9WK9RTNHXN7R0VJC","1ERPPXVG2NJ4RHCD8QED1E25R6QCAKKZWRV3EAVS4Y17ZDTXK46TSBJ1MF4HN26K3FHRE3PVBV4DA1RJ36R70BSAHSW7KTEGJNR8M5FBJ7ZDT907X2H0RMPCDANGGT9Q8VWYKBAHQ1T72S0ZAEC079SKV16XWTRX698P3P2TCJQDT7EA0G67N82YFPRE7YVGFKTMYXYC7EH1E","91CHR983Z222JN1695J11YY836RHP6J02SSZ8T5ACZ15X0D1YAW8VTCMECW3CPMCHG2RWTS5C30HSRWTYHAE3J3F61Q5N34ZSBVFPWJMN8WSFTAFEG7WZDWCM1VJZX5KZGXF7RTCS09MHJ0WRMCGC18WSR2SDV64P9HE1WTNCD8KT65JGQ3ZPWAWVJNJS63YNTGDPZ4RHTZ78","8Y2GMEETX3N9XTAB124JYKMHQH29009WJ5KF6V31WWZ99GXWZJHC5C03P3CKMB4R8M7ARQ3XD7AHPJTY2EKKPPF9QXWN7685HDTPT2SBCQ67XHHRA2RJ5E1S4VFP2YB1H0W1D9RE4C6WDT7FNVWXFHXAEQAJRJ577QNJD8G4ZSARSJ2SP6625AW9VQVVRFS0FWMBWJZNHXGWE","33MH8RQ5PXQFB0Q2T0H30VM9G4J0ZQHB3JHH8MWF5K3F3KDBAR20ZG4APHCTF1XRG530W7K2476P7BNFKENP2WXJ4HAW6RS91EQKGYD6PEHRYCVF8JJTMWR32WFVPV5Z2M1FWKNNC5N4CC3MK7S2K1V05PQMZ347KYD45FW7Y4KKJJ89TDPT7SFSQP1A0J00SN3MMTSEGXMVC","HR05CWNNQ5VBAF0WNHP8DZGN3S0DGYJ37EYP8S57MK8KDXQHXTPV2SXNJ2M5SZMS43BR168FJTB1SC9EW9P9YT7DYRBRM7G0VSD86P4QBG42NH06XYCCF7TVN9H9EB5K1G07M2Y3TT4WBAZ2Z350PV67ZANG43054GZA4N08BN2WEDFN94BNFKF45C10C3FFG77D9P3PDMGVG","JS43E6R3YEVTRZGRTS3RF0F9PCW7NBTWB6XEV146AXNTSKEKCDQ8CX76GX6HA06N0RTA699M99FXMD4JX04VY961YXJ18G96Z1AEEJ5BR5A8GYEWSNJSR5Y55ETBCKHBHR6CJ8A3659Y45FNG7H4C1K44WZ520PE4NAQVJ9QS1H2RWQ5ZZ8H8J8WGBDQC6DQYRTQHR0WF2156"]],"link_encs":[["7A1HVQDZ7C2M4K913020KMYGK4K86PM30APZ8AH5ERT5Z83QZPJRTGEFXF0TGYH36R1N36N3VKB5V845FK33ZW3R4G01X6CZNE37XHXE707T0TA4DYGYNX295QSVF5VEPD8QMK1J7ZAGDS27QP9H1QVK3NRBTZVRZ23XAV642CGEFS1ZMPWEDSHWECQ4G6CFK1V0K2118BQYS41R9P05NZ18PY3Z1FWNZH472HTKWT74KC752S0W1C2ASM428009","VN0WT37PVBAEYW3GH2ZGZ6X37R0XATMMGHJ86G953NVD6TE608Z3A6P9PG9XAFAGPQ13GSQ435C8763NR5T28RQHH4JB9N578TW7FHA5Z4Z6MDS6CMFK9A6ZGGT35G28NH6YG55EZR6GCM5E2ZKA8A9TR8Z8C6N4BTEVJCCJRWQ5MG8W4TR5N3YNPA7NAM49TYECT56BHJPCD6DHJ3XQF2XTZQTCECDJWZG4TXK3DEZM7Y55XFJ49C7F1P17MWQJ","88RWAYX9765MBR4KBM2BN9NSQ5KHWFC0AQZ6B0RF617VF5CSJJZBB69XEVDFC12K5D0T06KBE71BHK9E5ZTEWD5FK4Z2JVKD6WV6QBFHQ8Y1ZPP7Y2EYB9XFW7S9NVM27FNX8RKWTAJ45FFYKJPNR7MX5HZZ77B25ZB57Q0NJB1E7ECH4QP5BKZG63CF5M75VA633AJC7A4XWR8JJTTQDZ40KJMXE15X9KPABVZM6HSGF19P3N2SE2VSSWFXF3XH","MBPTJC2E7C7YQMAKCJN8EYBX1Z0CG8Z35G5VW3AZH3PAM6DF32AXKFX06Y9686R1CC1TP5BWX94DMN78RXSZGXFTEKPK3RSEVWQZCBR6BWB7YEEJZTV0C5MGREJE73RP40730074SPPW6CW665XV5TJA5329ZM0XC0KJ3MR9V70TDCE3M9DMB1JXPN3M97A74XG6KNJ57FKDQH4C4NV3Z3MJ276Y9MDAPDF7GM7MW7AQ7T1Y98RJ4H6163WCSN6V","Q2YNH71E69EDZ1NF8PFRS66PJYZ360SPQVQK73422944P34FGDZVJPMF59Y5KVJSB8VTJRPVMBJQX5Q8M8BHEJX1YAHDXAAWBTTBFM12CACZT72CYH6S80TJ8GYGS9ED1HBBWWT8RTZGMD8R4NMBJJRED107EASZJTH4SW32FJ04X62EA7YAMAJFTPCC5E6QJ43F0H592Y3J81NXQS66P6GQXKQMCN5W3FX8ZJ3160V542YVE2FNV3X7WD88FVX3","5H1650VJX95VFVA0G8ZNGWBCACCZ53D6SNJ2S4QVD27X2128KWC4MXRH8B3FK89QXV07NTEAJEXGDZC8BSYW11JZGF7H3D2YVXX8ZEEZBHWESCN7TNSE9AQK5NBV709PNHP9KHE0K7KEJTY2GY08CWAN1G9CQ3ZPF71NEHC7C0C47GYNVHX9PTXAYH2ZKQJSRNNSERC62CFX7NQD5TPF6WFEYC9R1AB3TWEQQF3FKY5EAE04EAEQJWSW0ZFGRCZZ","F5DZP7M59MGX6YJ3M3CEXVSJ0FGD3Q3EVHSQDSDDKSFPJ7ZBN60SYEBB293PDCFZVKC7K4HZ54E3XM7G3GECK2ENZYRCQMS6PS7VM7DYKF7S56KP0N4SE6X69Z6EDA309XPHHPDGRG1593SZ63YN63M815C2JP9M5E7H4FWYWQQNA9C81FQFNH48NVF43G3A54EEM3JV6VKB7X9H3SESC7YRA862NZF42C2FP5MCSACPQBSX0HV2Y6QRZW53BTW2","HJ9AV53HDRMNPEJJPSEPJV98XXVK1D93WT9CQNNY7B0GKY2TTAW55C83EHJQ6SHMRYEWQPBS096ZKRDFHNM5YJF09V0NFEYTTJGSMFCDYKW5GYCZRH3FGZHKEMNN3S98WV5KWDG5PT0S9TCYTDW074WPNMTH9V9KE08E6N8ATBX62PZ2X326TT3N15RG3ZKHQ09360981ANE6MVMTM2J4RXPFMA936KTXAS0EF4FTTGZA1GHS4FNG0JRFBXTNF0D","BXGHTGZ5EWJY0XKATMGECZB8QM74WH1RY4FB4KZP0VQ4STWVBY0NMQ8832YWB04GQ2B50Q5RTD61091HH1R3S2DK6QD7VJYE9BZ4EEQJTDTSSQT86DQEK2QPTXQ8W39WBTAT8PKBT037JM9DCYAC4CRPGTQWBZK3M1Q8BZHRVXYVGGVC5GWG3YQX075EQCSD9Y18N08JKNFHJKVZH6X27ABDHC323HQS30VS15BD65GZ35MH4HBFT7CAHPDZ5C4D","D3EF8ZKVNK6PCTC7KWGDGTSA3804J3N76R186NKQC3YJPW9P9B6CXDN9910S5RNEGST7MSY566PFZY2NP95WZNJTMG8988H3HFVXTXK2TST59AFBNDNTGAK3Y149WVWNDAVZ3G4NM9R75HSWC75BGNDRJ1F6DQ8QACZRAN9E4K6J3NZ3BNG5A2JY48TVXEC3E6TEDMF3E4Q9QSWQXPEHE48TJB5J7Y98WTDDWWV3S186BGM5V06PTGS3V2QRMRW1","YXS4AQF8X7VR35BBA8RBRMCJCSRJ1K2820ERJV75TZ5X3XBB4S5A1EKAGQHRF6KENMX4GEM3NT30K20FD9W03B79AKARV0KK6SMW0W1DY68KWGF69JH95RCHXCSVVV8XGVC48CBW1ZBVPFXS1BEJRDBHJV5229R5CZ9AXC3DNSS7S2QYENQZT7ZSKM9VKYGN604GT55SXD0NXJ0QCVT6TG8MTGTTKSM9D1DFKGDRMP251FCEZVG4XGZSVHKGVHGY","2QYSG97X93Z5TR1JT9FJJG05T1ZKZCPKZ4FNTBNF3DT2WPJR12QF1EPB5NSYQK068XFRKK46YGTT5GX6QEJJZ2B61A35RM30HNTP9S794V2TSME26S4RG32M6AYCNZFR6YEXBKF828ER3HMS1XAF8H475HQSM4M0004GN2S2EHMBXR62TYBEYE20GB123X5V9B31W9650S1E833Y6AH8SKQRE5JY1AK0NATPP6DBKWRC3FTEV6QBDVDGB8B3FKVB","XHTD1E2BZMY311GRBP3PJ6TNJ5S9MMWYZEPFKBF98X6Z3B9GK8EKYZ9DMN0CR9JYQ95KVWETVEKGWMF3V9VT3KK29WGDBPCX1V44J09E2AA1V72A6Y6B6KXPPESB3SM2TH0DZZC1AYQG7TNAMWTQ028ECEZAW30DE3S4ZWKE78ZZJVKP4ZXFXWN3JTRY2TX112B3TRYH94GNYJ1JKGHVG55ESYQTFN7VPPW8GMRSJRVNJZ09S4P37G21HBH46NCK","2N7GXE2T60XCGFPMTS8YJZ64G6STTNPQ4R31CCRJ9Q3F7KJK9RNW4JAQ0VP0A0SCQGR12HJ09NW0TMWKFP46XHQWRJP9WMQA8TXPP6F1SR9XQX7A8KB73EGH3BPN8X5N7YY95M7ZBD1F4SJK24HR0P0GV6BMEEQFTE86QGTP67Z7KC1JPANYM1B3MX9Z9CVESD83WX4VG459X6YR2HR8T576M0EV5K73P9397H25ZSP0NXSAMGY87R0DCQM1GFR3","7WKT07H36FAWM8W3X54AN2VAGJC5DWKP2MJMHES1NA2VFVJD3WAWJJCNWARWP17SQSEWS38HF36A63BNR5Q0Z2R7VETVASYVEYRFQYFGW3KXCVT5AS2ZHJCDMWRJ0N5EQXP0J7MNMCCCQZ8F304SKWWMAQJ8EB5Z7KKM1T0Z8WQHZACR8YTATF4XSXSSJVPGGCXNC47B11QM4RN3HDGXF9MBZZMKASBZRF8AA2EH4M1VFG8NR243ZE3ZKK3818C7","FKZQB344EWN58WPNVZ0BR5A7D30ZR58HZN0PQPVJBKWK3F4A4DPNEV3Q8QXR70E8V5WT4JX8AG9H3X5RZ74DQPKH7RT4M4747NY98P4J09R58JP7BR58KA8Q0AHS6069GHNWC0SX9GF33RV9SA5ZYA75CV37V1N5GRAPX6A0SWR76B6HYSPA94G5PY02R9B05W8Y5MDWDEY70NW4ZXWG2ZH3S68SSNP6SASAJV85KMH6CH7FZXTDYQTAR0HDB4K7","3073VTHEC9679DTAPZZ2J52N4PXFJ2B5JDQQ4FEPWCDFWMTXJK4KTSVMSF6DNMY8BH3EJDS192CK16JZG95JGDGYXMYBAFCY4X77AGA33DZSNXT32VWNVKKP6PN5E7JWRDJT8SSCRJ6C90P9YZ31SFF4PPKFPSVZFRC3PD87CKT8M9D6XDWN61D0PMXKMWGJVSNQDR8NP86MJYE8KGHSX0SWDV2J9N3X9212CXV1JDQNS3DFJ7XEX38EEKWQDSJP"],["8HRTEYDE0W9T678MM8X20QADH4XHBW7JTHJVFDJDANF90K84P7FK7TN0KJGE5BW52VQQFB10YNEQ8S9J3WVRV229RSMC9FKNRJ60968J536EYP24C5WNACGATG6AG8N4YNDQNQQM8V2KQ6DX2XVEH3Z86W9ZYKW7SS4V4B2WM1ZT9WA80SM1H73H9JN62GMMQE37XHVQZ212836WQJMFFKR29ZSYZAE45FJ14AQMP6YKF56M4F1PSATNFZ62VC87","R2CW58HWX89YHVRJ8WYRJ1A2GSC24H1BQW38VGBXP0MXYZS939BWPG8KQM4ED9M19QVXKZ4RC21RN62EC7E1SSAF5VW0BFBGER1FWW3ZHJ78FFF1TNJGRVBE2PF18W1ZXMATHZ12RRKDPSYT81YTGRGAYZ71GHYD1SKYD711P2J80ZTKC6YCWJ6172Z14VWVFA6H8JWWA8VYZ4XTARPXZRWR1Y1YE4232XYZ8X6A8HHWQXS5ZNSM0WPK8BW0KSTA","DE3DY2F0TCKEXVV65EHTWFKKAB2ANTDS435K869BXNVA8NQH4JA6X3WM0H5VAVFT3RCAVQ1WYCRWZ2PKDABAFEXF8RP1R4ABX04ZZS4XPB9K4R1T5KK2HX7GMWQX8RZ16Q40C44DJ70YTHVBD0BM6AF2R5G86SRZ8KERPS21RMPYWYBBWM8DR5YS1D3HM6CEJE6ZX6K398XJGK59E3FCHYJX0B2SCRKJB5E5G1S6PAB3QXKZ2SDGGQR7F8XSESZR","ZNXFM7CDW0M2XRNBTD1GM4FDG74G2XDMTF53N7R71J93YPMPXTT4G6CVZM9YZHCK6GQ35P9CHEKNWK9Q3VESZCYR7TDMMQYYFBNPPM8E4CVHNHFZSBSAHP6MMSWQJ07GZA9FRW7ZGDH6ZNEWNVMQK0V8GV0035C8PP2SYMH66FWMA24E3D15RMJSF4C817TD7SD4F7WG6RCBQ6Y0DDHAY4VW1TB5V4W55J3N0GEMX7AJ98T0TSZQRS6R9PSP1JA2","2J8G5KH2TYWDYHA2KX2CEPW58CRDHK05YX5TER84CGAEBAQASD9Q0ED8R67VBVMDQE2C9NG7V4T3QZ2VRPJPYGQXFPCXBQF99FXK1GBMEGCVP8Y4S4TTN8NJCRBTY6CXA03KQ00JKVJMVSWA85969C2F8YAG8GS5TBPF3D8ZT2C56D2ZD65ZB7F0E11S7B2RWWES7TXQ7HKXDWEBFMYTC2WNGZT1NET0JG21MP03KPS6BEMAX5Q5B139QJNCW8F2","K1605K54DR7EXXPBSWWFNJMXQE6QZS3BN3WWVBF2Y83WD42NMB9FZJG0HXPXVGVJTV1EFEXBAV7PY28FAXTVJHAZ06CDD13ZF7JE12D1BN6T6AT4RHN6N00A2ZJQTE1VCWGBF49Y5B75ZXNS7F9K352AR5A5ZXG21KV7VNT2ZPJ2B1YQG11QY05W920TEGSC8TW0J41HN035ZHCG8KBF5QN5RMGKSVZQV708K7DPVYM7NC2V7X2ZHZ44CBCPEXHD","MGQFD9DMYZD61RHS6KFGC5YGRG2Q56CYNX4GYBGASQ1P69N76M6P4CA3WZV163VFR71JPAXPK11BFCVVCS3A9E59WAR6R0ZWXXN5P9XJXD7DSA1PWCS0JVJ851BCMPWE54M23JGHNRJ03VE76HW8ZH45B6WGG809T8T2HQRBHBPFN3WC9NSE7NY07YWH6V3MFMVPQ2GZJ6QJP4C9D2AGM88H71PD0H65YZY07HD72F3VV1QBJX6T1GPY3NYPEHH3","KWJQX6FGX9XTEXEXYVWJ657TGZHYWHX24KEE1XA5TW5NVVQMQGQVC6HE6GA4RBC1MM0S1GWN7Q838QQEP5DQNDZS7VDBSEP7SPHC832WRN492FN8CEVSJ01AAE36HRXNM8DN6JPK160DJ4M842W9STXQRWPPF94S6HER2WZX330EZ9CECD2NKWTPTYSDGA5FVZS2GC9ERQGYP4YKZWZ8DXRJWCNVE1G7BYYPWNTHPVX0EW5F9GHT4MR29YG9YMRS","50FNFP9CQNHME6EDC0JFNXDVHW58CMC0TJRRZ0FGAYK0MAYQ9EV5WZEYBDV5Y967H72AQBJ28GXD394BDW949KGEMNSAWYE3YX5GSZZNZ9CCXEDKJJSC7W1P1Z04B9VAAM07SPWN4EMREMR9R4XXCD014YHPMMKP64C58X93FCSFJNW8XDF5FW0S2KZJHSP8FGRNQ6D62CQJCVPS615D4N52XNPWTFS1R379GVKSZ4FRR6SRTT4R3PA271HTX3ZQ","NA6QQDXM6SB52138M8CN3E4ZY7P0P9QS9AJ6EGNKYYK58XTRR4D4BANF0ARSTVZGMSJ7FP803B87J21SR2KT35V5K4FEHWVQ9WB9F9ZFMREFAA3SP3NWXNYJQQS0YA2K3RRAWJYJ2XSPQERXW71702QAEQRZQPMGXCMDKFNY9V7Q77147RQ7M87G8ZHZHX83D9C6QHKWGKTSQ0R66MZRCX29C6P6A5EGHACWVXD32Z8SA1ZPNJBDZY0PNJJM68D0","4M00E1KQVQ5KTJPXTCT3EYFN1SH975ZASNAWN7Y0NB12ANR6VZ1163GKCK9WBQJ34Q9P0G763PQ0B68WCJT2KXYXX5X3AV91BPDGGWBYB220HQ52V5VES6H7D75KYS176MA27G09X9PAGTQJ78VQHDAZCYZ5TS82D7HM5GT618605NVEYTYQYT3BA38R6V0RHYGE0CHNEXZTM0TFGDXQZ3P6CTNMYPPPM4E4PMCCQC4XR2S6CGVWYE1TXN0DC59S","N98TSJ6HGBS2N3K3MRPW6WEHBP409B4DC7PW8GMHM2QM2VGC0XQE1AQQ9AT1J4MZAK0B3RAMWF74XZT0D1CBDFABDY8M2M3X6QM7M4C8Z2DV7B2KHF3GBV4FMEG2PWAPW97QJ47383BT5XEYC4KQNXNK1K22NXYQJK0W5GS64KRFZWWKENZABG8VYZ7AYDAXMPVRSCQCCDE1RS6V8MDAVP65SAB59NSM0QQMBAPVBDTWD0P3HCZMG4F17QGTYZYP","ABT20WXHNAHYANMGCVGR9VC1QEQMS5YYRMMPS8GE04RK7XDF4CFXV9HEC1TAN77PBZJXAGX8GVZ8Y0068T84M1DQ90F2VEZGHR1JBBJ91S6RER8FKC301STT9MSF0CYBQTG5GAZK948305FBT6QRFX5BYS4NB5ZX86YMTNZ01V5C26W6Q6T78E5MHC7PH21SQ2VERSMDBWC640068HDX4PCJWBV7809DBSHR2ASJBMM32C49SG4EYVW74H4385QA","M0HBDKZV4TAYEBJJFBMMV55735T3F114RQMHBW2FRTDNNEM6NVBYR65Q25GJ1XNZJV0J7CY55CWMNEARHS2F9TYZPM0BVFZ429DVB2BTX8ZJ6Y9M1RSMVZZZM3GVE865PFEYCVG40AVC5T01Q8XGW1FDZKZTXBVCR5J7H24870S3BJB820X7MQMXDHNNT7VA4XZQJRN96JNHFC3RWB8R2X6Z4BZA9AYETNHP180MY0CJB8CJ8P9DTFHFRAY4M8WE","HV2FVXRE3NGW0WR4XMX0QZX2Z0B7MQB2ANCC95GY0FY9WD74D6CSM11YC0HNMBYCX8CTX7QJS28FGK9FC2RP79M8MKBGHHP0E3SK33AHRQ6457TSMARYKRY2SNSTV7JC8Z1KPV55EH5EH9C736G42J9W84E61JRWG8PMD24V1TS0MJZWKNP1QKSET7N40VAJXZE6B2QDPH2D7F26S4SND52B8KS1HB0NH762F41QE22ANVSXV6S2TN73Y5MXTB9S","70M04PP2WVXGK3NJCMPXGQQCCCDTRW0AF5F8N8A605DHY4CKBFFXX9F5Y512JS3CY6MD9T7K1X0ANT3N5VJRWPF9Y1S97EA2NAZ2W783DEP32Q1M3Y93VBHKCBQNM9M456VSMZA16E8SP3EGPJ9K7QVE0KYV0VWFJRXCA2CWDNNFDEKEJ6GK164SKGZ5MV1QQEDM258SKTQQ0EER895QRAA50PHNNQF1Z8MGHMZCV9WP0ZSJASVK63E4Z073TTVD","799TV24HVN9JDQBG0WFGA5FH0RNJYFJG2XF1B93W5W571CRPMEAHCAKA294YT63VV0K7C46AQYF1AVS58ZJ58CESDYWMY770P95EXPM5H38NV3F3RDTDB7EX5Q9XN0X8QRW37BTN8VBQ4PZ6Z1Y4539C7QGE2F012RBKGK47BST1TQP3XA427H1CDGT8HJBHBY8BD4TH1N7EE0XZF2QV6AJ0JCPWD0H6TKPVMGH1QYKVEKEPQBX20S8K502AYGKQ"],["23NQ8CW42D7FAY7FD7636SFA7THRQA2EGJYMFYF75FA1ZATVSMPPYSHTEC58J84PKDHRD0KPFMK811CTSGW0W274D8A3E5FKEX6P5AZ2ST0K9PK65ZP6R6CR8RMWSGQQX5VAJ2YVJ3RNV20C7YJA0CDCJNQX3JKGRX5NRY8GY3RNA38HG3SM0NW85AKZ0SXP1XRZRY0A0T3SM6E4Q6PTJMJZJ677BRZHXAGB7BEMSK6KDERXHVN9AGN4J9SZ5AY0","ZNZFHJ4X5T79G4DXBX8TPG0RV8SV4B7ZKV56CAN893MZ6Z00CNFPH6KPPXTKM8MB6MTPSCHEA0PRGRHSAKPBSAVR019GB9E75TFSXV452ZQN8Q54ZH9BJPKMAMB26X8BDQKGYK01FAWRWZXJ1TW9GVPJ9TS03WKQS64TVRNH17TTFVED3K8MFMDC9SFSJ4FZC2J19XTSWASXSY4BDE4T8Z2VGWEGDTYF0PNPMHDN2FQN2T25T6TA2DW702HM9MBC","SCC6Y84J99YQ598PR67G5VDT1N9E6S2VEZ22QWD7PQDX108ZZJ2AK16YF9PS8WSAP1PCPM0AYD1R9522XZNGWEHX92RE3R0WVM43A38450XH0EHY6XYKCK0NWJJ9XKN0FG4GACRF6X1WG4Y7AYW6FBFMBKHWDHP8BCTT0J9Q9XXZEBC88C57DMG18ABP6S248JAX9JCN0GZ4Q25AX6XYA2W2KD7CW97H23H11NS47XMC0GFKF7FDZK11JQXASJMT","PJBV9WQ064WKS9WVV0JWN3JHGNCY82T42FC0ANKNV5SEE8BBPJTQ6HXM76D8FAH00A6GNB2HMRYAAS0ZTGESRE2JFDD5HFAWW2EX9BN1QA7T6EB1G4PBFFZXVD3AN3GZKK72A4VT5AFM9QF7GHEXVWC29AHH5HH3ZHYQ1AR5TB5G7CNRCM8GA1Q61P574H0G2DWJJ35XE79D3G6SEWPCPB3VZS89B3XY9A1HV86R6BVY6C48D7FHXEA903AVVSMP","7HS65FGXB1Y1E39DHB5J0SRX8KDS7K6YBNP1XAPBQQNBM8N2T1QAVVS37XG15A2ABWK9J2CP16WCWNMG1ZCS6KEY98DXRNKNEQ099M9P392D00R8HDE1Y8C918PV0DVXKM3141CYJFXTQCYM9A5ZYKDEXNF09NTWHNDT3F990R6EWYS0TT4F4YDW9605SWTPJ2G29CKQNP13N1KKFRJS4BME8VE800YH6HTCSWTPXQ19TFXCHET6PF9EZE00RF9D","39HPW4NZCE7T1TF3ZFW6FGSSSETD2NMY5ED7T49572WAZ5FBJJSGGK8G45CQSSS488KZ2WRVM2N79ZKD78AX08X7TAW7DGWKB5KX9KRFCAF7ZZJQP5W6A1652XYNV2NWR51MS30ME0Y6RPXCMVC7K2B3HK5TFQ893HE8JJJSD2TF3AS9RGMPRBXXPCTC1R3Y5MZJQ79RKV70PVW65MA00RPMC27VGBWW5MA7FWK4X8JWQ8PHB9666MVM64AZJZSC","WFPTT5ZYQZNF2TYKSMFT6D7RP85980PTXNP6PS1HY3J5Y4ZY8DEVNPGFDP85SQ2DST4QQ1EHQQ9CHP9T1Q25XSGNKAHF9QAXX6N029SNH3C0MTG5VAQWQMCSPAYBW08EKFJS560JXX1AT9JCD0CJXZTX1P7TC4XD1JJCBDYKQ592CJZDKBCVMDN2CAG7RNXVSKQ8EWCRM7NCEF3YVWBJ7SQRE9B3BB067SVSRJQ9SB0PTGRP93WRNQ2JEGC584K8","P0VHPX17WERWA5G19P5NV2ZBWXGX3YWFTYHMSWQ2C5H82TPQ08BNDPE813NYWWT231EMTY7VRW2ZS8S447T4K5BCD7S0YQX2D92G5VRP10YTC57P16QZ37T0QZ9JENVPT6665TZ97JERST3M8SG7BD3ZDM5H0S9KCQTXXDJCR9NB1WHNJFTYZ0DJYJFDNZMTKREQG3R5VF5DQXXWKJ2GVKZG6ED2VTJR11MKYTQBMXSKP50RJYEZMVCZQ8H5KZBV","VDS1SGX5A31KBPTBPBR82C4ZD5FG2XRDM5KGVAR7X8XGXP7ZMGREXS9CYFKA9SZ3GMF0H7X4H733C81HPNDDC2NPF4443CB0KY0XSMJ34SA6TY99SE0NTZ96RZ36CZP77HRQQ93WC2EN3FBGS9WQMZEQ7GKNZSH6RBGXMF237ZGRN4FPRGX771VXY5DQXFMPTFYRDHCPWJR9FZKF7TQT0MB1K412PRFHSHM9S8FZJCHYR2PP443WF9SZWGNM2CV8","J8KEHBYBAQVMTR1XTNDY23VSTQVW5CYE2EDNRE9DBP56DV1PSC2XXFH8KKHXT8D3TM5G6MCHE0JWNHN6417G696MHY5NZ6CRAYYBNR4YBPHDF94CHBMZSXH6KD3N0AVW3F0KK88DPA9G2CZ8C82QTV8Z8C8WRRWAHRH0X6SEGK4BJPEB3WEGRMD9AFYTXCPQ0N7BXK83052V4HD6W7AB0AE7EQ789XF6C8AD4TN7ARF7MJDMTCY624B6QR5K15Y5","R5D4QJ0HM2A9E8WEVZYKG9V612D1ZRRB6RCGK4S8MDBM7NRBP7DDJGV1H7SN0JEZJSTW7DGVA7WRK0Z8HSH6VGB2NZ2CRZKZTTX15W58EB2XKJKZJYA0AR5MKTNHPP7D39PFF8BP39PJX9VRFHGXMWB84GHVMND6S2M2WWVHMBVNK2D19TJ1240NZFV6GP64416JT0QW06ZQMXVHVD4GFRESTSVBH1G78AMJ972RAHBF4DM0PJXY0PTXYWQSP2GZ","KZ0R0MPKXYKS13894TAC8SMNGYQ9F1EB9VN9S4P7VJ8KDXJEREYR64HDRAP4HBRY9D50YDKDPE9AHMW9ETR4CXK3E0YQJQ2YGPAWJ823GH7KHJXK1GTFSZ3TERT1VB31FGXC0950TY0MAHESMPHNRXSG9738C0CEJ5Z1TNBVV48WJCH2JPCKAPDMTSFYFAECZ28WJ7J9EW459NNT5H4D0DB5ZZ2CY16CCC8PBC0K4ZYP1Y2ZW5MK0N0MJTXGQ49T","YVH178SVB7QK1HM2HVSDGBZ1QJN9NRTG3RXMC9YVS74VXTW719NRFMAXKJHVZJE77HNRW53S8DSD3QTYDZ9ZNZ1185GZXP0NNP40NQFYJQSBHWVE7WDPR5KZT42BWQE1P20B3F5HCHV3VD1ZCGCZ1NF5K5P4WKT01K7XKXNGFTX9HY5YAG30THBY4S3Q3CPMQBVCBXDHW929DHRBZWHZGGGBCTVNRRNDA7J0EVBAVYYHES2PDK9YW5STCG83S5AB","MA1RKAT9SCJSXJG7T5P1QF1VW9GRBCYK03P8Z55KSCK4SN1K0MFSXR3YERD4VA5QQ1N7KKHPDAPNYHQ6QW2TPBVM932A5G42VEKZ9CH5ZH7346RKDT8BMNSFYQ799W6042CCJ3JSESFYMER8JHZNBVFWFGMKMJJ642BCP9PPA9H7B0XSSGK2CZYWK3RQX5WKZK4V40AMTRJ9JRZNX7QKXRN9W464J3X3BRYHR8Y70C42HH0HVW86CPNVKWE0Q36C","K8CP0BM60F6P218ZMC4HW307DNN3FHHEG3XMWN55KEZR4RQ86ADXGJF2FKD7QW2TVQ3AWN2KVJ6QMP6SFJHF11C6T68FWKWMQVGQWYY28591XTQ3D78FB9N06T2W3R6EERTHRQNK7X9WP2BV3VM43A39YXVJ0W0BHJ0XSZQENN218ZR4GTVDX48788WXFD6064ZXGMWGD2EKWMS4BBVWK59FVZPGDRQD6EN3TRA784HBBWE831JWBEVX3YGPX9T0","QC2WWZPKJDCFZA65QKSGKW7YTAS9RP0W4H0FP96Z5TGCCD0K9FXFX4F75MQEDNKCM0B34D8ZR46CHR97HMMEB3W2T8W6P8X5289Z729365HVDQWCGHMJSGM7AHA1HS6ZND695X67D42H7XB7NQ5X57XG2S32R767YPQJERZ0ESG3S01NGNQ0P62KM3AT28JX41GMHPEA92V1Z80RYS8GWE9CGZ6K66NVGW72SN8DJGDFVC7771HP93X6DYN0TF63","H3P6CNRKH4BSCAWG3T9RRA89EBF1F91JEYEY4RZJQ2NNCZZASY362XHME0KNM36R4SNQD956CBW15XAXVK70TZM6TBVMHJ8KKQQQKBG4Q91ZHKAS10NEVD5B36QGYM7CMJM3DVJ00KTK0ZAM8C4YRNZDZKV5CNFGDR0DDP1P38704Y3G9KFRJ455SD39J7ZYDB5GQV17ZF2PE7GY927S1KSXV4KKXQ7FRX1VZRJGAD1F3DY4KJFRZJGAT5951JNS"]]} diff --git a/src/exchange-lib/baseline/refresh_reveal.req b/src/exchange-lib/baseline/refresh_reveal.req new file mode 100644 index 00000000..3fb14396 --- /dev/null +++ b/src/exchange-lib/baseline/refresh_reveal.req @@ -0,0 +1,7 @@ +POST /refresh/reveal HTTP/1.1 +Host: localhost:8081 +Accept: */* +Content-Type: application/json +Content-Length: 255 + +{"session_hash":"V97SB8T670M9V71D1Q0KVQ4GSJCVQ5AAKTTH9QKT0ZJZZBFNZAV4NA8NMWRRGVPFEBEGB6ANCN9BPQASJ40TM4Y1C49648TJJ07PGSG","transfer_privs":[["EQKJA401A9NJ2YJDFZJ1EV8AYXBHWZB6NT5T0TWSJHVKVDM6W8A0"],["TKDJ4DF3GZVG0DGAB9E3RGBGSTANYB6JVVWXJGPMB2AY4VQNTBA0"]]}
\ No newline at end of file diff --git a/src/exchange-lib/baseline/reserve_status.req b/src/exchange-lib/baseline/reserve_status.req new file mode 100644 index 00000000..4f988f66 --- /dev/null +++ b/src/exchange-lib/baseline/reserve_status.req @@ -0,0 +1,4 @@ +GET /reserve/status?reserve_pub=TMZCK5CFM1KZQGY1WTF0CEZZPGA0670G94969RF79PA5106ARTK0 HTTP/1.1 +Host: localhost:8081 +Accept: */* + diff --git a/src/exchange-lib/baseline/reserve_withdraw.req b/src/exchange-lib/baseline/reserve_withdraw.req new file mode 100644 index 00000000..48495025 --- /dev/null +++ b/src/exchange-lib/baseline/reserve_withdraw.req @@ -0,0 +1,7 @@ +POST /reserve/withdraw HTTP/1.1 +Host: localhost:8081 +Accept: */* +Content-Type: application/json +Content-Length: 919 + +{"coin_ev":"Q5X8A8TCBFH7E5BMY7HSB17SHFTM1JPJGV61P2CA7Z9EXG8P2HYS69B31NZESKXHSZHNJ2DQN3CC2AWFNC6V90J577JD3TXBMAY8Y5M9V60KKT73Z1DW24JFSNAK91G1F2WT55ADP1EG7N5F9AY7A7ZJD03MPYSH0RDP7SVZS2KRPA5JRHFR4GDJ59CFNE7A43M95ZKQHQAS8","denom_pub":"51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GT58S2K2HJ16H336C9N8CVK4E9N6H1MADHH61330HHM6N1K8E1H8RVKJH256D1M6E1K8RWKJGSH8S2M6DJ170TK2H266GTK8DSS64RKJDJ26D144DJ474SK0GHQ711MAD9G752M2CJ58S1KJDA570SK2E9G8N23GCJ28S146DHH610K2H1Q8CW3GGA16S146H9G68TKACSQ6914CE1H691K2E9N6RWM8H9P8CWM2H9S8GSK0H9P6D1K6H9G6X0M4C2171144HJ46N334H9J692M4H9M8MR4CCJ46GRKEGA46533CDJ38MV4CH9K892MAH1P8S2K6D9K6N246E256H244G9Q6D346GJ56S23JGHJ690KADHJ8H242H2575132CSM6X1M4G9N6RR48E9H8MVM8E9354520818CMG26C1H60R30C935452081918G2J2G0","reserve_pub":"TMZCK5CFM1KZQGY1WTF0CEZZPGA0670G94969RF79PA5106ARTK0","reserve_sig":"8427B3RTB217124EB1C37ZVJFC08KN17RHGHE9ENZQMQVJ0S11SAX6H8Z06SWCKT06DRQ9DQ8XD786XKQ94T27PYR9GC9EMT1Y02W10"}
\ No newline at end of file diff --git a/src/exchange-lib/baseline/wire.req b/src/exchange-lib/baseline/wire.req new file mode 100644 index 00000000..a4f1d074 --- /dev/null +++ b/src/exchange-lib/baseline/wire.req @@ -0,0 +1,5 @@ +GET /wire HTTP/1.1 +Host: localhost:8081 +Accept: */* +Content-Type: application/json + diff --git a/src/exchange-lib/baseline/wire_sepa.req b/src/exchange-lib/baseline/wire_sepa.req new file mode 100644 index 00000000..80d3d461 --- /dev/null +++ b/src/exchange-lib/baseline/wire_sepa.req @@ -0,0 +1,5 @@ +GET /wire/sepa HTTP/1.1 +Host: localhost:8081 +Accept: */* +Content-Type: application/json + diff --git a/src/exchange-lib/baseline/wire_test.req b/src/exchange-lib/baseline/wire_test.req new file mode 100644 index 00000000..684352c9 --- /dev/null +++ b/src/exchange-lib/baseline/wire_test.req @@ -0,0 +1,5 @@ +GET /wire/test HTTP/1.1 +Host: localhost:8081 +Accept: */* +Content-Type: application/json + diff --git a/src/exchange-lib/exchange_api_admin.c b/src/exchange-lib/exchange_api_admin.c new file mode 100644 index 00000000..3dcbb80e --- /dev/null +++ b/src/exchange-lib/exchange_api_admin.c @@ -0,0 +1,254 @@ +/* +  This file is part of TALER +  Copyright (C) 2014, 2015 GNUnet e.V. + +  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, If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/exchange_api_admin.c + * @brief Implementation of the /admin/ requests of the exchange's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include "taler_exchange_service.h" +#include "exchange_api_json.h" +#include "exchange_api_context.h" +#include "exchange_api_handle.h" +#include "taler_signatures.h" + + +/** + * @brief An admin/add/incoming Handle + */ +struct TALER_EXCHANGE_AdminAddIncomingHandle +{ + +  /** +   * The connection to exchange this request handle will use +   */ +  struct TALER_EXCHANGE_Handle *exchange; + +  /** +   * The url for this request. +   */ +  char *url; + +  /** +   * JSON encoding of the request to POST. +   */ +  char *json_enc; + +  /** +   * Handle for the request. +   */ +  struct MAC_Job *job; + +  /** +   * HTTP headers for the request. +   */ +  struct curl_slist *headers; + +  /** +   * Function to call with the result. +   */ +  TALER_EXCHANGE_AdminAddIncomingResultCallback cb; + +  /** +   * Closure for @a cb. +   */ +  void *cb_cls; + +  /** +   * Download buffer +   */ +  struct MAC_DownloadBuffer db; + +}; + + +/** + * Function called when we're done processing the + * HTTP /admin/add/incoming request. + * + * @param cls the `struct TALER_EXCHANGE_AdminAddIncomingHandle` + * @param eh the curl request handle + */ +static void +handle_admin_add_incoming_finished (void *cls, +                                    CURL *eh) +{ +  struct TALER_EXCHANGE_AdminAddIncomingHandle *aai = cls; +  long response_code; +  json_t *json; + +  aai->job = NULL; +  json = MAC_download_get_result (&aai->db, +                                  eh, +                                  &response_code); +  switch (response_code) +  { +  case 0: +    break; +  case MHD_HTTP_OK: +    break; +  case MHD_HTTP_BAD_REQUEST: +    /* This should never happen, either us or the exchange is buggy +       (or API version conflict); just pass JSON reply to the application */ +    break; +  case MHD_HTTP_FORBIDDEN: +    /* Access denied */ +    break; +  case MHD_HTTP_UNAUTHORIZED: +    /* Nothing really to verify, exchange says one of the signatures is +       invalid; as we checked them, this should never happen, we +       should pass the JSON reply to the application */ +    break; +  case MHD_HTTP_NOT_FOUND: +    /* Nothing really to verify, this should never +       happen, we should pass the JSON reply to the application */ +    break; +  case MHD_HTTP_INTERNAL_SERVER_ERROR: +    /* Server had an internal issue; we should retry, but this API +       leaves this to the application */ +    break; +  default: +    /* unexpected response code */ +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected response code %u\n", +                response_code); +    GNUNET_break (0); +    response_code = 0; +    break; +  } +  aai->cb (aai->cb_cls, +           response_code, +           json); +  json_decref (json); +  TALER_EXCHANGE_admin_add_incoming_cancel (aai); +} + + +/** + * Notify the exchange that we have received an incoming transaction + * which fills a reserve.  Note that this API is an administrative + * API and thus not accessible to typical exchange clients, but only + * to the operators of the exchange. + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param reserve_pub public key of the reserve + * @param amount amount that was deposited + * @param execution_date when did we receive the amount + * @param wire wire details + * @param res_cb the callback to call when the final result for this request is available + * @param res_cb_cls closure for the above callback + * @return NULL + *         if the inputs are invalid (i.e. invalid amount). + *         In this case, the callback is not called. + */ +struct TALER_EXCHANGE_AdminAddIncomingHandle * +TALER_EXCHANGE_admin_add_incoming (struct TALER_EXCHANGE_Handle *exchange, +                               const struct TALER_ReservePublicKeyP *reserve_pub, +                               const struct TALER_Amount *amount, +                               struct GNUNET_TIME_Absolute execution_date, +                               const json_t *wire, +                               TALER_EXCHANGE_AdminAddIncomingResultCallback res_cb, +                               void *res_cb_cls) +{ +  struct TALER_EXCHANGE_AdminAddIncomingHandle *aai; +  struct TALER_EXCHANGE_Context *ctx; +  json_t *admin_obj; +  CURL *eh; + +  GNUNET_assert (GNUNET_OK == +                 TALER_round_abs_time (&execution_date)); +  if (GNUNET_YES != +      MAH_handle_is_ready (exchange)) +  { +    GNUNET_break (0); +    return NULL; +  } +  admin_obj = json_pack ("{s:o, s:o," /* reserve_pub/amount */ +                         " s:o, s:O}", /* execution_Date/wire */ +                         "reserve_pub", TALER_json_from_data (reserve_pub, +                                                               sizeof (*reserve_pub)), +                         "amount", TALER_json_from_amount (amount), +                         "execution_date", TALER_json_from_abs (execution_date), +                         "wire", wire); +  aai = GNUNET_new (struct TALER_EXCHANGE_AdminAddIncomingHandle); +  aai->exchange = exchange; +  aai->cb = res_cb; +  aai->cb_cls = res_cb_cls; +  aai->url = MAH_path_to_url (exchange, "/admin/add/incoming"); + +  eh = curl_easy_init (); +  GNUNET_assert (NULL != (aai->json_enc = +                          json_dumps (admin_obj, +                                      JSON_COMPACT))); +  json_decref (admin_obj); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_URL, +                                   aai->url)); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_POSTFIELDS, +                                   aai->json_enc)); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_POSTFIELDSIZE, +                                   strlen (aai->json_enc))); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_WRITEFUNCTION, +                                   &MAC_download_cb)); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_WRITEDATA, +                                   &aai->db)); +  ctx = MAH_handle_to_context (exchange); +  aai->job = MAC_job_add (ctx, +                          eh, +                          GNUNET_YES, +                          &handle_admin_add_incoming_finished, +                          aai); +  return aai; +} + + +/** + * Cancel an add incoming.  This function cannot be used on a request + * handle if a response is already served for it. + * + * @param aai the admin add incoming request handle + */ +void +TALER_EXCHANGE_admin_add_incoming_cancel (struct TALER_EXCHANGE_AdminAddIncomingHandle *aai) +{ +  if (NULL != aai->job) +  { +    MAC_job_cancel (aai->job); +    aai->job = NULL; +  } +  curl_slist_free_all (aai->headers); +  GNUNET_free_non_null (aai->db.buf); +  GNUNET_free (aai->url); +  GNUNET_free (aai->json_enc); +  GNUNET_free (aai); +} + + +/* end of exchange_api_admin.c */ diff --git a/src/exchange-lib/exchange_api_common.c b/src/exchange-lib/exchange_api_common.c new file mode 100644 index 00000000..805c3fc4 --- /dev/null +++ b/src/exchange-lib/exchange_api_common.c @@ -0,0 +1,194 @@ +/* +  This file is part of TALER +  Copyright (C) 2015 GNUnet e.V. + +  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, If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/exchange_api_common.c + * @brief common functions for the exchange API + * @author Christian Grothoff + */ +#include "platform.h" +#include "exchange_api_common.h" +#include "exchange_api_json.h" +#include "exchange_api_context.h" +#include "exchange_api_handle.h" +#include "taler_signatures.h" + + +/** + * Verify a coins transaction history as returned by the exchange. + * + * @param currency expected currency for the coin + * @param coin_pub public key of the coin + * @param history history of the coin in json encoding + * @param[out] total how much of the coin has been spent according to @a history + * @return #GNUNET_OK if @a history is valid, #GNUNET_SYSERR if not + */ +int +TALER_EXCHANGE_verify_coin_history_ (const char *currency, +                                 const struct TALER_CoinSpendPublicKeyP *coin_pub, +                                 json_t *history, +                                 struct TALER_Amount *total) +{ +  size_t len; +  size_t off; + +  if (NULL == history) +  { +    GNUNET_break_op (0); +    return GNUNET_SYSERR; +  } +  len = json_array_size (history); +  if (0 == len) +  { +    GNUNET_break_op (0); +    return GNUNET_SYSERR; +  } +  TALER_amount_get_zero (currency, +                         total); +  for (off=0;off<len;off++) +  { +    json_t *transaction; +    struct TALER_Amount amount; +    struct TALER_CoinSpendSignatureP sig; +    void *details; +    size_t details_size; +    const char *type; +    struct MAJ_Specification spec[] = { +      MAJ_spec_amount ("amount", +                       &amount), +      MAJ_spec_string ("type", +                       &type), +      MAJ_spec_fixed_auto ("signature", +                           &sig), +      MAJ_spec_varsize ("details", +                        &details, +                        &details_size), +      MAJ_spec_end +    }; + +    transaction = json_array_get (history, +                                  off); +    if (GNUNET_OK != +        MAJ_parse_json (transaction, +                        spec)) +    { +      GNUNET_break_op (0); +      return GNUNET_SYSERR; +    } +    if (0 == strcasecmp (type, +                         "DEPOSIT")) +    { +      const struct TALER_DepositRequestPS *dr; +      struct TALER_Amount dr_amount; + +      if (details_size != sizeof (struct TALER_DepositRequestPS)) +      { +        GNUNET_break_op (0); +        MAJ_parse_free (spec); +        return GNUNET_SYSERR; +      } +      dr = (const struct TALER_DepositRequestPS *) details; +      if (details_size != ntohl (dr->purpose.size)) +      { +        GNUNET_break_op (0); +        MAJ_parse_free (spec); +        return GNUNET_SYSERR; +      } +      if (GNUNET_OK != +          GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_DEPOSIT, +                                      &dr->purpose, +                                      &sig.eddsa_signature, +                                      &coin_pub->eddsa_pub)) +        { +        GNUNET_break_op (0); +        MAJ_parse_free (spec); +        return GNUNET_SYSERR; +      } + +       // FIXME: check sig! +      TALER_amount_ntoh (&dr_amount, +                         &dr->amount_with_fee); +      if (0 != TALER_amount_cmp (&dr_amount, +                                 &amount)) +        { +          GNUNET_break (0); +          MAJ_parse_free (spec); +          return GNUNET_SYSERR; +        } +    } +    else if (0 == strcasecmp (type, +                              "MELT")) +    { +      const struct TALER_RefreshMeltCoinAffirmationPS *rm; +      struct TALER_Amount rm_amount; + +      if (details_size != sizeof (struct TALER_RefreshMeltCoinAffirmationPS)) +      { +        GNUNET_break_op (0); +        MAJ_parse_free (spec); +        return GNUNET_SYSERR; +      } +      rm = (const struct TALER_RefreshMeltCoinAffirmationPS *) details; +      if (details_size != ntohl (rm->purpose.size)) +      { +        GNUNET_break_op (0); +        MAJ_parse_free (spec); +        return GNUNET_SYSERR; +      } +      if (GNUNET_OK != +          GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_MELT, +                                      &rm->purpose, +                                      &sig.eddsa_signature, +                                      &coin_pub->eddsa_pub)) +      { +        GNUNET_break_op (0); +        MAJ_parse_free (spec); +        return GNUNET_SYSERR; +      } +      TALER_amount_ntoh (&rm_amount, +                         &rm->amount_with_fee); +      if (0 != TALER_amount_cmp (&rm_amount, +                                 &amount)) +      { +        GNUNET_break_op (0); +        MAJ_parse_free (spec); +        return GNUNET_SYSERR; +      } +    } +    else +    { +      /* signature not supported, new version on server? */ +      GNUNET_break_op (0); +      MAJ_parse_free (spec); +      return GNUNET_SYSERR; +    } +    if (GNUNET_OK != +        TALER_amount_add (total, +                          total, +                          &amount)) +    { +      /* overflow in history already!? inconceivable! Bad exchange! */ +      GNUNET_break_op (0); +      MAJ_parse_free (spec); +      return GNUNET_SYSERR; +    } +    MAJ_parse_free (spec); +  } +  return GNUNET_OK; +} + + +/* end of exchange_api_common.c */ diff --git a/src/exchange-lib/exchange_api_common.h b/src/exchange-lib/exchange_api_common.h new file mode 100644 index 00000000..49f486b0 --- /dev/null +++ b/src/exchange-lib/exchange_api_common.h @@ -0,0 +1,41 @@ +/* +  This file is part of TALER +  Copyright (C) 2015 GNUnet e.V. + +  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, If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/exchange_api_common.h + * @brief common functions for the exchange API + * @author Christian Grothoff + */ +#include <jansson.h> +#include <gnunet/gnunet_util_lib.h> +#include "taler_exchange_service.h" + +/** + * Verify a coins transaction history as returned by the exchange. + * + * @param currency expected currency for the coin + * @param coin_pub public key of the coin + * @param history history of the coin in json encoding + * @param[out] total how much of the coin has been spent according to @a history + * @return #GNUNET_OK if @a history is valid, #GNUNET_SYSERR if not + */ +int +TALER_EXCHANGE_verify_coin_history_ (const char *currency, +                                 const struct TALER_CoinSpendPublicKeyP *coin_pub, +                                 json_t *history, +                                 struct TALER_Amount *total); + +/* end of exchange_api_common.h */ diff --git a/src/exchange-lib/exchange_api_context.c b/src/exchange-lib/exchange_api_context.c new file mode 100644 index 00000000..54471900 --- /dev/null +++ b/src/exchange-lib/exchange_api_context.c @@ -0,0 +1,537 @@ +/* +  This file is part of TALER +  Copyright (C) 2014, 2015 GNUnet e.V. + +  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, If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/exchange_api_context.c + * @brief Implementation of the context part of the exchange's HTTP API + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * @author Christian Grothoff + */ +#include "platform.h" +#include <curl/curl.h> +#include "taler_exchange_service.h" +#include "exchange_api_context.h" + + +/** + * Log error related to CURL operations. + * + * @param type log level + * @param function which function failed to run + * @param code what was the curl error code + */ +#define CURL_STRERROR(type, function, code)      \ + GNUNET_log (type,                               \ +             "Curl function `%s' has failed at `%s:%d' with error: %s\n", \ +             function, __FILE__, __LINE__, curl_easy_strerror (code)); + +/** + * Print JSON parsing related error information + */ +#define JSON_WARN(error)                                                \ +    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,                              \ +                "JSON parsing failed at %s:%u: %s (%s)\n",              \ +                __FILE__, __LINE__, error.text, error.source) + + +/** + * Failsafe flag. Raised if our constructor fails to initialize + * the Curl library. + */ +static int TALER_EXCHANGE_curl_fail; + + +/** + * Jobs are CURL requests running within a `struct TALER_EXCHANGE_Context`. + */ +struct MAC_Job +{ + +  /** +   * We keep jobs in a DLL. +   */ +  struct MAC_Job *next; + +  /** +   * We keep jobs in a DLL. +   */ +  struct MAC_Job *prev; + +  /** +   * Easy handle of the job. +   */ +  CURL *easy_handle; + +  /** +   * Context this job runs in. +   */ +  struct TALER_EXCHANGE_Context *ctx; + +  /** +   * Function to call upon completion. +   */ +  MAC_JobCompletionCallback jcc; + +  /** +   * Closure for @e jcc. +   */ +  void *jcc_cls; + +}; + + +/** + * Context + */ +struct TALER_EXCHANGE_Context +{ +  /** +   * Curl multi handle +   */ +  CURLM *multi; + +  /** +   * Curl share handle +   */ +  CURLSH *share; + +  /** +   * We keep jobs in a DLL. +   */ +  struct MAC_Job *jobs_head; + +  /** +   * We keep jobs in a DLL. +   */ +  struct MAC_Job *jobs_tail; + +  /** +   * HTTP header "application/json", created once and used +   * for all requests that need it. +   */ +  struct curl_slist *json_header; + +}; + + +/** + * Initialise this library.  This function should be called before using any of + * the following functions. + * + * @return library context + */ +struct TALER_EXCHANGE_Context * +TALER_EXCHANGE_init () +{ +  struct TALER_EXCHANGE_Context *ctx; +  CURLM *multi; +  CURLSH *share; + +  if (TALER_EXCHANGE_curl_fail) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Curl was not initialised properly\n"); +    return NULL; +  } +  if (NULL == (multi = curl_multi_init ())) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Failed to create a Curl multi handle\n"); +    return NULL; +  } +  if (NULL == (share = curl_share_init ())) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Failed to create a Curl share handle\n"); +    return NULL; +  } +  ctx = GNUNET_new (struct TALER_EXCHANGE_Context); +  ctx->multi = multi; +  ctx->share = share; +  GNUNET_assert (NULL != (ctx->json_header = +                          curl_slist_append (NULL, +                                             "Content-Type: application/json"))); +  return ctx; +} + + +/** + * Schedule a CURL request to be executed and call the given @a jcc + * upon its completion.  Note that the context will make use of the + * CURLOPT_PRIVATE facility of the CURL @a eh.  Applications can + * instead use #MAC_easy_to_closure to extract the @a jcc_cls argument + * from a valid @a eh afterwards. + * + * This function modifies the CURL handle to add the + * "Content-Type: application/json" header if @a add_json is set. + * + * @param ctx context to execute the job in + * @param eh curl easy handle for the request, will + *           be executed AND cleaned up + * @param add_json add "application/json" content type header + * @param jcc callback to invoke upon completion + * @param jcc_cls closure for @a jcc + */ +struct MAC_Job * +MAC_job_add (struct TALER_EXCHANGE_Context *ctx, +             CURL *eh, +             int add_json, +             MAC_JobCompletionCallback jcc, +             void *jcc_cls) +{ +  struct MAC_Job *job; + +  if (GNUNET_YES == add_json) +    GNUNET_assert (CURLE_OK == +                   curl_easy_setopt (eh, +                                     CURLOPT_HTTPHEADER, +                                     ctx->json_header)); + +  job = GNUNET_new (struct MAC_Job); +  job->easy_handle = eh; +  job->ctx = ctx; +  job->jcc = jcc; +  job->jcc_cls = jcc_cls; +  GNUNET_CONTAINER_DLL_insert (ctx->jobs_head, +                               ctx->jobs_tail, +                               job); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_PRIVATE, +                                   job)); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_SHARE, +                                   ctx->share)); +  GNUNET_assert (CURLM_OK == +                 curl_multi_add_handle (ctx->multi, +                                        eh)); +  return job; +} + + +/** + * Obtain the `jcc_cls` argument from an `eh` that was + * given to #MAC_job_add(). + * + * @param eh easy handle that was used + * @return the `jcc_cls` that was given to #MAC_job_add(). + */ +void * +MAC_easy_to_closure (CURL *eh) +{ +  struct MAC_Job *job; + +  GNUNET_assert (CURLE_OK == +                 curl_easy_getinfo (eh, +                                    CURLINFO_PRIVATE, +                                    (char **) &job)); +  return job->jcc_cls; +} + + +/** + * Cancel a job.  Must only be called before the job completion + * callback is called for the respective job. + * + * @param job job to cancel + */ +void +MAC_job_cancel (struct MAC_Job *job) +{ +  struct TALER_EXCHANGE_Context *ctx = job->ctx; + +  GNUNET_CONTAINER_DLL_remove (ctx->jobs_head, +                               ctx->jobs_tail, +                               job); +  GNUNET_assert (CURLM_OK == +                 curl_multi_remove_handle (ctx->multi, +                                           job->easy_handle)); +  curl_easy_cleanup (job->easy_handle); +  GNUNET_free (job); +} + + +/** + * Run the main event loop for the Taler interaction. + * + * @param ctx the library context + */ +void +TALER_EXCHANGE_perform (struct TALER_EXCHANGE_Context *ctx) +{ +  CURLMsg *cmsg; +  struct MAC_Job *job; +  int n_running; +  int n_completed; + +  (void) curl_multi_perform (ctx->multi, +                             &n_running); +  while (NULL != (cmsg = curl_multi_info_read (ctx->multi, +                                               &n_completed))) +  { +    /* Only documented return value is CURLMSG_DONE */ +    GNUNET_break (CURLMSG_DONE == cmsg->msg); +    GNUNET_assert (CURLE_OK == +                   curl_easy_getinfo (cmsg->easy_handle, +                                      CURLINFO_PRIVATE, +                                      (char **) &job)); +    GNUNET_assert (job->ctx == ctx); +    job->jcc (job->jcc_cls, +              cmsg->easy_handle); +    MAC_job_cancel (job); +  } +} + + +/** + * Obtain the information for a select() call to wait until + * #TALER_EXCHANGE_perform() is ready again.  Note that calling + * any other TALER_EXCHANGE-API may also imply that the library + * is again ready for #TALER_EXCHANGE_perform(). + * + * Basically, a client should use this API to prepare for select(), + * then block on select(), then call #TALER_EXCHANGE_perform() and then + * start again until the work with the context is done. + * + * This function will NOT zero out the sets and assumes that @a max_fd + * and @a timeout are already set to minimal applicable values.  It is + * safe to give this API FD-sets and @a max_fd and @a timeout that are + * already initialized to some other descriptors that need to go into + * the select() call. + * + * @param ctx context to get the event loop information for + * @param read_fd_set will be set for any pending read operations + * @param write_fd_set will be set for any pending write operations + * @param except_fd_set is here because curl_multi_fdset() has this argument + * @param max_fd set to the highest FD included in any set; + *        if the existing sets have no FDs in it, the initial + *        value should be "-1". (Note that `max_fd + 1` will need + *        to be passed to select().) + * @param timeout set to the timeout in milliseconds (!); -1 means + *        no timeout (NULL, blocking forever is OK), 0 means to + *        proceed immediately with #TALER_EXCHANGE_perform(). + */ +void +TALER_EXCHANGE_get_select_info (struct TALER_EXCHANGE_Context *ctx, +                            fd_set *read_fd_set, +                            fd_set *write_fd_set, +                            fd_set *except_fd_set, +                            int *max_fd, +                            long *timeout) +{ +  long to; +  int m; + +  m = -1; +  GNUNET_assert (CURLM_OK == +                 curl_multi_fdset (ctx->multi, +                                   read_fd_set, +                                   write_fd_set, +                                   except_fd_set, +                                   &m)); +  to = *timeout; +  *max_fd = GNUNET_MAX (m, *max_fd); +  GNUNET_assert (CURLM_OK == +                 curl_multi_timeout (ctx->multi, +                                     &to)); + +  /* Only if what we got back from curl is smaller than what we +     already had (-1 == infinity!), then update timeout */ +  if ( (to < *timeout) && +       (-1 != to) ) +    *timeout = to; +  if ( (-1 == (*timeout)) && +       (NULL != ctx->jobs_head) ) +    *timeout = to; +} + + +/** + * Cleanup library initialisation resources.  This function should be called + * after using this library to cleanup the resources occupied during library's + * initialisation. + * + * @param ctx the library context + */ +void +TALER_EXCHANGE_fini (struct TALER_EXCHANGE_Context *ctx) +{ +  /* all jobs must have been cancelled at this time, assert this */ +  GNUNET_assert (NULL == ctx->jobs_head); +  curl_share_cleanup (ctx->share); +  curl_multi_cleanup (ctx->multi); +  curl_slist_free_all (ctx->json_header); +  GNUNET_free (ctx); +} + + +/** + * Callback used when downloading the reply to an HTTP request. + * Just appends all of the data to the `buf` in the + * `struct MAC_DownloadBuffer` for further processing. The size of + * the download is limited to #GNUNET_MAX_MALLOC_CHECKED, if + * the download exceeds this size, we abort with an error. + * + * @param bufptr data downloaded via HTTP + * @param size size of an item in @a bufptr + * @param nitems number of items in @a bufptr + * @param cls the `struct KeysRequest` + * @return number of bytes processed from @a bufptr + */ +size_t +MAC_download_cb (char *bufptr, +                 size_t size, +                 size_t nitems, +                 void *cls) +{ +  struct MAC_DownloadBuffer *db = cls; +  size_t msize; +  void *buf; + +  if (0 == size * nitems) +  { +    /* Nothing (left) to do */ +    return 0; +  } +  msize = size * nitems; +  if ( (msize + db->buf_size) >= GNUNET_MAX_MALLOC_CHECKED) +  { +    db->eno = ENOMEM; +    return 0; /* signals an error to curl */ +  } +  db->buf = GNUNET_realloc (db->buf, +                            db->buf_size + msize); +  buf = db->buf + db->buf_size; +  memcpy (buf, bufptr, msize); +  db->buf_size += msize; +  return msize; +} + + +/** + * Obtain information about the final result about the + * HTTP download. If the download was successful, parses + * the JSON in the @a db and returns it. Also returns + * the HTTP @a response_code.  If the download failed, + * the return value is NULL.  The response code is set + * in any case, on download errors to zero. + * + * Calling this function also cleans up @a db. + * + * @param db download buffer + * @param eh CURL handle (to get the response code) + * @param[out] response_code set to the HTTP response code + *             (or zero if we aborted the download, i.e. + *              because the response was too big, or if + *              the JSON we received was malformed). + * @return NULL if downloading a JSON reply failed + */ +json_t * +MAC_download_get_result (struct MAC_DownloadBuffer *db, +                         CURL *eh, +                         long *response_code) +{ +  json_t *json; +  json_error_t error; +  char *ct; + +  if ( (CURLE_OK != +        curl_easy_getinfo (eh, +                           CURLINFO_CONTENT_TYPE, +                           &ct)) || +       (NULL == ct) || +       (0 != strcasecmp (ct, +                         "application/json")) ) +  { +    /* No content type or explicitly not JSON, refuse to parse +       (but keep response code) */ +    if (CURLE_OK != +        curl_easy_getinfo (eh, +                           CURLINFO_RESPONSE_CODE, +                           response_code)) +    { +      /* unexpected error... */ +      GNUNET_break (0); +      *response_code = 0; +    } +    return NULL; +  } + +  json = NULL; +  if (0 == db->eno) +  { +    json = json_loadb (db->buf, +                       db->buf_size, +                       JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK, +                       &error); +    if (NULL == json) +    { +      JSON_WARN (error); +      *response_code = 0; +    } +  } +  GNUNET_free_non_null (db->buf); +  db->buf = NULL; +  db->buf_size = 0; +  if (NULL != json) +  { +    if (CURLE_OK != +        curl_easy_getinfo (eh, +                           CURLINFO_RESPONSE_CODE, +                           response_code)) +    { +      /* unexpected error... */ +      GNUNET_break (0); +      *response_code = 0; +    } +  } +  return json; +} + + +/** + * Initial global setup logic, specifically runs the Curl setup. + */ +__attribute__ ((constructor)) +void +TALER_EXCHANGE_constructor__ (void) +{ +  CURLcode ret; + +  if (CURLE_OK != (ret = curl_global_init (CURL_GLOBAL_DEFAULT))) +  { +    CURL_STRERROR (GNUNET_ERROR_TYPE_ERROR, +                   "curl_global_init", +                   ret); +    TALER_EXCHANGE_curl_fail = 1; +  } +} + + +/** + * Cleans up after us, specifically runs the Curl cleanup. + */ +__attribute__ ((destructor)) +void +TALER_EXCHANGE_destructor__ (void) +{ +  if (TALER_EXCHANGE_curl_fail) +    return; +  curl_global_cleanup (); +} + +/* end of exchange_api_context.c */ diff --git a/src/exchange-lib/exchange_api_context.h b/src/exchange-lib/exchange_api_context.h new file mode 100644 index 00000000..3c54bfe0 --- /dev/null +++ b/src/exchange-lib/exchange_api_context.h @@ -0,0 +1,169 @@ +/* +  This file is part of TALER +  Copyright (C) 2014, 2015 GNUnet e.V. + +  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, If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/exchange_api_context.h + * @brief Internal interface to the context part of the exchange's HTTP API + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * @author Christian Grothoff + */ +#include "platform.h" +#include <curl/curl.h> +#include <gnunet/gnunet_util_lib.h> +#include "taler_exchange_service.h" +#include "taler_signatures.h" + + +/** + * Entry in the context's job queue. + */ +struct MAC_Job; + +/** + * Function to call upon completion of a job. + * + * @param cls closure + * @param eh original easy handle (for inspection) + */ +typedef void +(*MAC_JobCompletionCallback)(void *cls, +                             CURL *eh); + + +/** + * Schedule a CURL request to be executed and call the given @a jcc + * upon its completion. Note that the context will make use of the + * CURLOPT_PRIVATE facility of the CURL @a eh.  Applications can + * instead use #MAC_easy_to_closure to extract the @a jcc_cls argument + * from a valid @a eh afterwards. + * + * This function modifies the CURL handle to add the + * "Content-Type: application/json" header if @a add_json is set. + * + * @param ctx context to execute the job in + * @param eh curl easy handle for the request, will + *           be executed AND cleaned up + * @param add_json add "application/json" content type header + * @param jcc callback to invoke upon completion + * @param jcc_cls closure for @a jcc + */ +struct MAC_Job * +MAC_job_add (struct TALER_EXCHANGE_Context *ctx, +             CURL *eh, +             int add_json, +             MAC_JobCompletionCallback jcc, +             void *jcc_cls); + + +/** + * Obtain the `jcc_cls` argument from an `eh` that was + * given to #MAC_job_add(). + * + * @param eh easy handle that was used + * @return the `jcc_cls` that was given to #MAC_job_add(). + */ +void * +MAC_easy_to_closure (CURL *eh); + + +/** + * Cancel a job.  Must only be called before the job completion + * callback is called for the respective job. + * + * @param job job to cancel + */ +void +MAC_job_cancel (struct MAC_Job *job); + + +/** + * @brief Buffer data structure we use to buffer the HTTP download + * before giving it to the JSON parser. + */ +struct MAC_DownloadBuffer +{ + +  /** +   * Download buffer +   */ +  void *buf; + +  /** +   * The size of the download buffer +   */ +  size_t buf_size; + +  /** +   * Error code (based on libc errno) if we failed to download +   * (i.e. response too large). +   */ +  int eno; + +}; + + +/** + * Callback used when downloading the reply to an HTTP request. + * Just appends all of the data to the `buf` in the + * `struct MAC_DownloadBuffer` for further processing. The size of + * the download is limited to #GNUNET_MAX_MALLOC_CHECKED, if + * the download exceeds this size, we abort with an error. + * + * Should be used by the various routines as the + * CURLOPT_WRITEFUNCTION.  A `struct MAC_DownloadBuffer` needs to be + * passed to the CURLOPT_WRITEDATA. + * + * Afterwards, `eno` needs to be checked to ensure that the download + * completed correctly. + * + * @param bufptr data downloaded via HTTP + * @param size size of an item in @a bufptr + * @param nitems number of items in @a bufptr + * @param cls the `struct KeysRequest` + * @return number of bytes processed from @a bufptr + */ +size_t +MAC_download_cb (char *bufptr, +                 size_t size, +                 size_t nitems, +                 void *cls); + + +/** + * Obtain information about the final result about the + * HTTP download. If the download was successful, parses + * the JSON in the @a db and returns it. Also returns + * the HTTP @a response_code.  If the download failed, + * the return value is NULL.  The response code is set + * in any case, on download errors to zero. + * + * Calling this function also cleans up @a db. + * + * @param db download buffer + * @param eh CURL handle (to get the response code) + * @param[out] response_code set to the HTTP response code + *             (or zero if we aborted the download, i.e. + *              because the response was too big, or if + *              the JSON we received was malformed). + * @return NULL if downloading a JSON reply failed + */ +json_t * +MAC_download_get_result (struct MAC_DownloadBuffer *db, +                         CURL *eh, +                         long *response_code); + + +/* end of exchange_api_context.h */ diff --git a/src/exchange-lib/exchange_api_deposit.c b/src/exchange-lib/exchange_api_deposit.c new file mode 100644 index 00000000..deba4877 --- /dev/null +++ b/src/exchange-lib/exchange_api_deposit.c @@ -0,0 +1,569 @@ +/* +  This file is part of TALER +  Copyright (C) 2014, 2015 GNUnet e.V. + +  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, If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/exchange_api_deposit.c + * @brief Implementation of the /deposit request of the exchange's HTTP API + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * @author Christian Grothoff + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include "taler_exchange_service.h" +#include "exchange_api_common.h" +#include "exchange_api_json.h" +#include "exchange_api_context.h" +#include "exchange_api_handle.h" +#include "taler_signatures.h" + + +/** + * @brief A Deposit Handle + */ +struct TALER_EXCHANGE_DepositHandle +{ + +  /** +   * The connection to exchange this request handle will use +   */ +  struct TALER_EXCHANGE_Handle *exchange; + +  /** +   * The url for this request. +   */ +  char *url; + +  /** +   * JSON encoding of the request to POST. +   */ +  char *json_enc; + +  /** +   * Handle for the request. +   */ +  struct MAC_Job *job; + +  /** +   * Function to call with the result. +   */ +  TALER_EXCHANGE_DepositResultCallback cb; + +  /** +   * Closure for @a cb. +   */ +  void *cb_cls; + +  /** +   * Download buffer +   */ +  struct MAC_DownloadBuffer db; + +  /** +   * Information the exchange should sign in response. +   */ +  struct TALER_DepositConfirmationPS depconf; + +  /** +   * Value of the /deposit transaction, including fee. +   */ +  struct TALER_Amount amount_with_fee; + +  /** +   * Total value of the coin being transacted with. +   */ +  struct TALER_Amount coin_value; + +}; + + +/** + * Verify that the signature on the "200 OK" response + * from the exchange is valid. + * + * @param dh deposit handle + * @param json json reply with the signature + * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not + */ +static int +verify_deposit_signature_ok (const struct TALER_EXCHANGE_DepositHandle *dh, +                             json_t *json) +{ +  struct TALER_ExchangeSignatureP exchange_sig; +  struct TALER_ExchangePublicKeyP exchange_pub; +  const struct TALER_EXCHANGE_Keys *key_state; +  struct MAJ_Specification spec[] = { +    MAJ_spec_fixed_auto ("sig", &exchange_sig), +    MAJ_spec_fixed_auto ("pub", &exchange_pub), +    MAJ_spec_end +  }; + +  if (GNUNET_OK != +      MAJ_parse_json (json, +                      spec)) +  { +    GNUNET_break_op (0); +    return GNUNET_SYSERR; +  } +  key_state = TALER_EXCHANGE_get_keys (dh->exchange); +  if (GNUNET_OK != +      TALER_EXCHANGE_test_signing_key (key_state, +                                   &exchange_pub)) +  { +    GNUNET_break_op (0); +    return GNUNET_SYSERR; +  } +  if (GNUNET_OK != +      GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT, +                                  &dh->depconf.purpose, +                                  &exchange_sig.eddsa_signature, +                                  &exchange_pub.eddsa_pub)) +  { +    GNUNET_break_op (0); +    return GNUNET_SYSERR; +  } +  return GNUNET_OK; +} + + +/** + * Verify that the signatures on the "403 FORBIDDEN" response from the + * exchange demonstrating customer double-spending are valid. + * + * @param dh deposit handle + * @param json json reply with the signature(s) and transaction history + * @return #GNUNET_OK if the signature(s) is valid, #GNUNET_SYSERR if not + */ +static int +verify_deposit_signature_forbidden (const struct TALER_EXCHANGE_DepositHandle *dh, +                                    json_t *json) +{ +  json_t *history; +  struct TALER_Amount total; + +  history = json_object_get (json, +                             "history"); +  if (GNUNET_OK != +      TALER_EXCHANGE_verify_coin_history_ (dh->coin_value.currency, +                                       &dh->depconf.coin_pub, +                                       history, +                                       &total)) +  { +    GNUNET_break_op (0); +    return GNUNET_SYSERR; +  } +  if (GNUNET_OK != +      TALER_amount_add (&total, +                        &total, +                        &dh->amount_with_fee)) +  { +    /* clearly not OK if our transaction would have caused +       the overflow... */ +    return GNUNET_OK; +  } + +  if (0 >= TALER_amount_cmp (&total, +                             &dh->coin_value)) +  { +    /* transaction should have still fit */ +    GNUNET_break (0); +    return GNUNET_SYSERR; +  } +  /* everything OK, proof of double-spending was provided */ +  return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /deposit request. + * + * @param cls the `struct TALER_EXCHANGE_DepositHandle` + * @param eh the curl request handle + */ +static void +handle_deposit_finished (void *cls, +                         CURL *eh) +{ +  struct TALER_EXCHANGE_DepositHandle *dh = cls; +  long response_code; +  json_t *json; + +  dh->job = NULL; +  json = MAC_download_get_result (&dh->db, +                                  eh, +                                  &response_code); +  switch (response_code) +  { +  case 0: +    break; +  case MHD_HTTP_OK: +    if (GNUNET_OK != +        verify_deposit_signature_ok (dh, +                                     json)) +    { +      GNUNET_break_op (0); +      response_code = 0; +    } +    break; +  case MHD_HTTP_BAD_REQUEST: +    /* This should never happen, either us or the exchange is buggy +       (or API version conflict); just pass JSON reply to the application */ +    break; +  case MHD_HTTP_FORBIDDEN: +    /* Double spending; check signatures on transaction history */ +    if (GNUNET_OK != +        verify_deposit_signature_forbidden (dh, +                                            json)) +    { +      GNUNET_break_op (0); +      response_code = 0; +    } +    break; +  case MHD_HTTP_UNAUTHORIZED: +    /* Nothing really to verify, exchange says one of the signatures is +       invalid; as we checked them, this should never happen, we +       should pass the JSON reply to the application */ +    break; +  case MHD_HTTP_NOT_FOUND: +    /* Nothing really to verify, this should never +       happen, we should pass the JSON reply to the application */ +    break; +  case MHD_HTTP_INTERNAL_SERVER_ERROR: +    /* Server had an internal issue; we should retry, but this API +       leaves this to the application */ +    break; +  default: +    /* unexpected response code */ +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected response code %u\n", +                response_code); +    GNUNET_break (0); +    response_code = 0; +    break; +  } +  dh->cb (dh->cb_cls, +          response_code, +          json); +  json_decref (json); +  TALER_EXCHANGE_deposit_cancel (dh); +} + + +/** + * Verify signature information about the deposit. + * + * @param dki public key information + * @param amount the amount to be deposited + * @param h_wire hash of the merchant’s account details + * @param h_contract hash of the contact of the merchant with the customer (further details are never disclosed to the exchange) + * @param coin_pub coin’s public key + * @param denom_pub denomination key with which the coin is signed + * @param denom_sig exchange’s unblinded signature of the coin + * @param timestamp timestamp when the contract was finalized, must match approximately the current time of the exchange + * @param transaction_id transaction id for the transaction between merchant and customer + * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests) + * @param refund_deadline date until which the merchant can issue a refund to the customer via the exchange (can be zero if refunds are not allowed) + * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s private key. + * @return #GNUNET_OK if signatures are OK, #GNUNET_SYSERR if not + */ +static int +verify_signatures (const struct TALER_EXCHANGE_DenomPublicKey *dki, +                   const struct TALER_Amount *amount, +                   const struct GNUNET_HashCode *h_wire, +                   const struct GNUNET_HashCode *h_contract, +                   const struct TALER_CoinSpendPublicKeyP *coin_pub, +                   const struct TALER_DenominationSignature *denom_sig, +                   const struct TALER_DenominationPublicKey *denom_pub, +                   struct GNUNET_TIME_Absolute timestamp, +                   uint64_t transaction_id, +                   const struct TALER_MerchantPublicKeyP *merchant_pub, +                   struct GNUNET_TIME_Absolute refund_deadline, +                   const struct TALER_CoinSpendSignatureP *coin_sig) +{ +  struct TALER_DepositRequestPS dr; +  struct TALER_CoinPublicInfo coin_info; + +  dr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT); +  dr.purpose.size = htonl (sizeof (struct TALER_DepositRequestPS)); +  dr.h_contract = *h_contract; +  dr.h_wire = *h_wire; +  dr.timestamp = GNUNET_TIME_absolute_hton (timestamp); +  dr.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline); +  dr.transaction_id = GNUNET_htonll (transaction_id); +  TALER_amount_hton (&dr.amount_with_fee, +                     amount); +  TALER_amount_hton (&dr.deposit_fee, +                     &dki->fee_deposit); +  dr.merchant = *merchant_pub; +  dr.coin_pub = *coin_pub; +  if (GNUNET_OK != +      GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_DEPOSIT, +                                  &dr.purpose, +                                  &coin_sig->eddsa_signature, +                                  &coin_pub->eddsa_pub)) +  { +    TALER_LOG_WARNING ("Invalid coin signature on /deposit request\n"); +    { +      char *s; +      s = TALER_amount_to_string (amount); +      TALER_LOG_DEBUG ("... amount_with_fee was %s\n", s); +      GNUNET_free (s); +      s = TALER_amount_to_string (&dki->fee_deposit); +      TALER_LOG_DEBUG ("... deposit_fee was %s\n", s); +      GNUNET_free (s); +    } + +    return GNUNET_SYSERR; +  } + +  /* check coin signature */ +  coin_info.coin_pub = *coin_pub; +  coin_info.denom_pub = *denom_pub; +  coin_info.denom_sig = *denom_sig; +  if (GNUNET_YES != +      TALER_test_coin_valid (&coin_info)) +  { +    TALER_LOG_WARNING ("Invalid coin passed for /deposit\n"); +    return GNUNET_SYSERR; +  } +  if (0 < TALER_amount_cmp (&dki->fee_deposit, +                            amount)) +  { +    TALER_LOG_WARNING ("Deposit amount smaller than fee\n"); +    return GNUNET_SYSERR; +  } +  return GNUNET_OK; +} + + +/** + * Submit a deposit permission to the exchange and get the exchange's response. + * Note that while we return the response verbatim to the caller for + * further processing, we do already verify that the response is + * well-formed (i.e. that signatures included in the response are all + * valid).  If the exchange's reply is not well-formed, we return an + * HTTP status code of zero to @a cb. + * + * We also verify that the @a coin_sig is valid for this deposit + * request, and that the @a ub_sig is a valid signature for @a + * coin_pub.  Also, the @a exchange must be ready to operate (i.e.  have + * finished processing the /keys reply).  If either check fails, we do + * NOT initiate the transaction with the exchange and instead return NULL. + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param amount the amount to be deposited + * @param wire_deadline date until which the merchant would like the exchange to settle the balance (advisory, the exchange cannot be + *        forced to settle in the past or upon very short notice, but of course a well-behaved exchange will limit aggregation based on the advice received) + * @param wire_details the merchant’s account details, in a format supported by the exchange + * @param h_contract hash of the contact of the merchant with the customer (further details are never disclosed to the exchange) + * @param coin_pub coin’s public key + * @param denom_pub denomination key with which the coin is signed + * @param denom_sig exchange’s unblinded signature of the coin + * @param timestamp timestamp when the contract was finalized, must match approximately the current time of the exchange + * @param transaction_id transaction id for the transaction between merchant and customer + * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests) + * @param refund_deadline date until which the merchant can issue a refund to the customer via the exchange (can be zero if refunds are not allowed) + * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s private key. + * @param cb the callback to call when a reply for this request is available + * @param cb_cls closure for the above callback + * @return a handle for this request; NULL if the inputs are invalid (i.e. + *         signatures fail to verify).  In this case, the callback is not called. + */ +struct TALER_EXCHANGE_DepositHandle * +TALER_EXCHANGE_deposit (struct TALER_EXCHANGE_Handle *exchange, +                    const struct TALER_Amount *amount, +                    struct GNUNET_TIME_Absolute wire_deadline, +                    json_t *wire_details, +                    const struct GNUNET_HashCode *h_contract, +                    const struct TALER_CoinSpendPublicKeyP *coin_pub, +                    const struct TALER_DenominationSignature *denom_sig, +                    const struct TALER_DenominationPublicKey *denom_pub, +                    struct GNUNET_TIME_Absolute timestamp, +                    uint64_t transaction_id, +                    const struct TALER_MerchantPublicKeyP *merchant_pub, +                    struct GNUNET_TIME_Absolute refund_deadline, +                    const struct TALER_CoinSpendSignatureP *coin_sig, +                    TALER_EXCHANGE_DepositResultCallback cb, +                    void *cb_cls) +{ +  const struct TALER_EXCHANGE_Keys *key_state; +  const struct TALER_EXCHANGE_DenomPublicKey *dki; +  struct TALER_EXCHANGE_DepositHandle *dh; +  struct TALER_EXCHANGE_Context *ctx; +  json_t *deposit_obj; +  CURL *eh; +  struct GNUNET_HashCode h_wire; +  struct TALER_Amount amount_without_fee; + +  (void) TALER_round_abs_time (&wire_deadline); +  if (GNUNET_YES != +      MAH_handle_is_ready (exchange)) +  { +    GNUNET_break (0); +    return NULL; +  } +  /* initialize h_wire */ +  if (GNUNET_OK != +      TALER_hash_json (wire_details, +                       &h_wire)) +  { +    GNUNET_break (0); +    return NULL; +  } +  key_state = TALER_EXCHANGE_get_keys (exchange); +  dki = TALER_EXCHANGE_get_denomination_key (key_state, +                                         denom_pub); +  if (NULL == dki) +  { +    TALER_LOG_WARNING ("Denomination key unknown to exchange\n"); +    return NULL; +  } +  if (GNUNET_SYSERR == +      TALER_amount_subtract (&amount_without_fee, +                             amount, +                             &dki->fee_deposit)) +  { +    GNUNET_break (0); +    return NULL; +  } + +  if (GNUNET_OK != +      verify_signatures (dki, +                         amount, +                         &h_wire, +                         h_contract, +                         coin_pub, +                         denom_sig, +                         denom_pub, +                         timestamp, +                         transaction_id, +                         merchant_pub, +                         refund_deadline, +                         coin_sig)) +  { +    GNUNET_break_op (0); +    return NULL; +  } + +  deposit_obj = json_pack ("{s:o, s:O," /* f/wire */ +                           " s:o, s:o," /* H_wire, H_contract */ +                           " s:o, s:o," /* coin_pub, denom_pub */ +                           " s:o, s:o," /* ub_sig, timestamp */ +                           " s:I, s:o," /* transaction id, merchant_pub */ +                           " s:o, s:o," /* refund_deadline, wire_deadline */ +                           " s:o}",     /* coin_sig */ +                           "f", TALER_json_from_amount (amount), +                           "wire", wire_details, +                           "H_wire", TALER_json_from_data (&h_wire, +                                                           sizeof (h_wire)), +                           "H_contract", TALER_json_from_data (h_contract, +                                                               sizeof (struct GNUNET_HashCode)), +                           "coin_pub", TALER_json_from_data (coin_pub, +                                                             sizeof (*coin_pub)), +                           "denom_pub", TALER_json_from_rsa_public_key (denom_pub->rsa_public_key), +                           "ub_sig", TALER_json_from_rsa_signature (denom_sig->rsa_signature), +                           "timestamp", TALER_json_from_abs (timestamp), +                           "transaction_id", (json_int_t) transaction_id, +                           "merchant_pub", TALER_json_from_data (merchant_pub, +                                                                 sizeof (*merchant_pub)), +                           "refund_deadline", TALER_json_from_abs (refund_deadline), +                           "edate", TALER_json_from_abs (wire_deadline), +                           "coin_sig", TALER_json_from_data (coin_sig, +                                                             sizeof (*coin_sig)) +                           ); + +  dh = GNUNET_new (struct TALER_EXCHANGE_DepositHandle); +  dh->exchange = exchange; +  dh->cb = cb; +  dh->cb_cls = cb_cls; +  dh->url = MAH_path_to_url (exchange, "/deposit"); +  dh->depconf.purpose.size = htonl (sizeof (struct TALER_DepositConfirmationPS)); +  dh->depconf.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT); +  dh->depconf.h_contract = *h_contract; +  dh->depconf.h_wire = h_wire; +  dh->depconf.transaction_id = GNUNET_htonll (transaction_id); +  dh->depconf.timestamp = GNUNET_TIME_absolute_hton (timestamp); +  dh->depconf.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline); +  TALER_amount_hton (&dh->depconf.amount_without_fee, +                     &amount_without_fee); +  dh->depconf.coin_pub = *coin_pub; +  dh->depconf.merchant = *merchant_pub; +  dh->amount_with_fee = *amount; +  dh->coin_value = dki->value; + +  eh = curl_easy_init (); +  GNUNET_assert (NULL != (dh->json_enc = +                          json_dumps (deposit_obj, +                                      JSON_COMPACT))); +  json_decref (deposit_obj); +  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, +              "URL for deposit: `%s'\n", +              dh->url); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_URL, +                                   dh->url)); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_POSTFIELDS, +                                   dh->json_enc)); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_POSTFIELDSIZE, +                                   strlen (dh->json_enc))); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_WRITEFUNCTION, +                                   &MAC_download_cb)); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_WRITEDATA, +                                   &dh->db)); +  ctx = MAH_handle_to_context (exchange); +  dh->job = MAC_job_add (ctx, +                         eh, +                         GNUNET_YES, +                         &handle_deposit_finished, +                         dh); +  return dh; +} + + +/** + * Cancel a deposit permission request.  This function cannot be used + * on a request handle if a response is already served for it. + * + * @param deposit the deposit permission request handle + */ +void +TALER_EXCHANGE_deposit_cancel (struct TALER_EXCHANGE_DepositHandle *deposit) +{ +  if (NULL != deposit->job) +  { +    MAC_job_cancel (deposit->job); +    deposit->job = NULL; +  } +  GNUNET_free_non_null (deposit->db.buf); +  GNUNET_free (deposit->url); +  GNUNET_free (deposit->json_enc); +  GNUNET_free (deposit); +} + + +/* end of exchange_api_deposit.c */ diff --git a/src/exchange-lib/exchange_api_deposit_wtid.c b/src/exchange-lib/exchange_api_deposit_wtid.c new file mode 100644 index 00000000..83beb03a --- /dev/null +++ b/src/exchange-lib/exchange_api_deposit_wtid.c @@ -0,0 +1,379 @@ +/* +  This file is part of TALER +  Copyright (C) 2014, 2015, 2016 GNUnet e.V. + +  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, If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/exchange_api_deposit_wtid.c + * @brief Implementation of the /deposit/wtid request of the exchange's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include "taler_exchange_service.h" +#include "exchange_api_common.h" +#include "exchange_api_json.h" +#include "exchange_api_context.h" +#include "exchange_api_handle.h" +#include "taler_signatures.h" + + +/** + * @brief A Deposit Wtid Handle + */ +struct TALER_EXCHANGE_DepositWtidHandle +{ + +  /** +   * The connection to exchange this request handle will use +   */ +  struct TALER_EXCHANGE_Handle *exchange; + +  /** +   * The url for this request. +   */ +  char *url; + +  /** +   * JSON encoding of the request to POST. +   */ +  char *json_enc; + +  /** +   * Handle for the request. +   */ +  struct MAC_Job *job; + +  /** +   * Function to call with the result. +   */ +  TALER_EXCHANGE_DepositWtidCallback cb; + +  /** +   * Closure for @a cb. +   */ +  void *cb_cls; + +  /** +   * Download buffer +   */ +  struct MAC_DownloadBuffer db; + +  /** +   * Information the exchange should sign in response. +   * (with pre-filled fields from the request). +   */ +  struct TALER_ConfirmWirePS depconf; + +}; + + +/** + * Verify that the signature on the "200 OK" response + * from the exchange is valid. + * + * @param dwh deposit wtid handle + * @param json json reply with the signature + * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not + */ +static int +verify_deposit_wtid_signature_ok (const struct TALER_EXCHANGE_DepositWtidHandle *dwh, +                                  json_t *json) +{ +  struct TALER_ExchangeSignatureP exchange_sig; +  struct TALER_ExchangePublicKeyP exchange_pub; +  const struct TALER_EXCHANGE_Keys *key_state; +  struct MAJ_Specification spec[] = { +    MAJ_spec_fixed_auto ("exchange_sig", &exchange_sig), +    MAJ_spec_fixed_auto ("exchange_pub", &exchange_pub), +    MAJ_spec_end +  }; + +  if (GNUNET_OK != +      MAJ_parse_json (json, +                      spec)) +  { +    GNUNET_break_op (0); +    return GNUNET_SYSERR; +  } +  key_state = TALER_EXCHANGE_get_keys (dwh->exchange); +  if (GNUNET_OK != +      TALER_EXCHANGE_test_signing_key (key_state, +                                   &exchange_pub)) +  { +    GNUNET_break_op (0); +    return GNUNET_SYSERR; +  } +  if (GNUNET_OK != +      GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE, +                                  &dwh->depconf.purpose, +                                  &exchange_sig.eddsa_signature, +                                  &exchange_pub.eddsa_pub)) +  { +    GNUNET_break_op (0); +    return GNUNET_SYSERR; +  } +  return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /deposit/wtid request. + * + * @param cls the `struct TALER_EXCHANGE_DepositWtidHandle` + * @param eh the curl request handle + */ +static void +handle_deposit_wtid_finished (void *cls, +                              CURL *eh) +{ +  struct TALER_EXCHANGE_DepositWtidHandle *dwh = cls; +  long response_code; +  json_t *json; +  const struct TALER_WireTransferIdentifierRawP *wtid = NULL; +  struct GNUNET_TIME_Absolute execution_time = GNUNET_TIME_UNIT_FOREVER_ABS; +  const struct TALER_Amount *coin_contribution = NULL; +  struct TALER_Amount coin_contribution_s; + +  dwh->job = NULL; +  json = MAC_download_get_result (&dwh->db, +                                  eh, +                                  &response_code); +  switch (response_code) +  { +  case 0: +    break; +  case MHD_HTTP_OK: +    { +      struct MAJ_Specification spec[] = { +        MAJ_spec_fixed_auto ("wtid", &dwh->depconf.wtid), +        MAJ_spec_absolute_time ("execution_time", &execution_time), +        MAJ_spec_amount ("coin_contribution", &coin_contribution_s), +        MAJ_spec_end +      }; + +      if (GNUNET_OK != +          MAJ_parse_json (json, +                          spec)) +      { +        GNUNET_break_op (0); +        response_code = 0; +        break; +      } +      wtid = &dwh->depconf.wtid; +      dwh->depconf.execution_time = GNUNET_TIME_absolute_hton (execution_time); +      TALER_amount_hton (&dwh->depconf.coin_contribution, +                         &coin_contribution_s); +      coin_contribution = &coin_contribution_s; +      if (GNUNET_OK != +          verify_deposit_wtid_signature_ok (dwh, +                                            json)) +      { +        GNUNET_break_op (0); +        response_code = 0; +      } +    } +    break; +  case MHD_HTTP_ACCEPTED: +    { +      /* Transaction known, but not executed yet */ +      struct MAJ_Specification spec[] = { +        MAJ_spec_absolute_time ("execution_time", &execution_time), +        MAJ_spec_end +      }; + +      if (GNUNET_OK != +          MAJ_parse_json (json, +                          spec)) +      { +        GNUNET_break_op (0); +        response_code = 0; +        break; +      } +    } +    break; +  case MHD_HTTP_BAD_REQUEST: +    /* This should never happen, either us or the exchange is buggy +       (or API version conflict); just pass JSON reply to the application */ +    break; +  case MHD_HTTP_UNAUTHORIZED: +    /* Nothing really to verify, exchange says one of the signatures is +       invalid; as we checked them, this should never happen, we +       should pass the JSON reply to the application */ +    break; +  case MHD_HTTP_NOT_FOUND: +    /* Exchange does not know about transaction; +       we should pass the reply to the application */ +    break; +  case MHD_HTTP_INTERNAL_SERVER_ERROR: +    /* Server had an internal issue; we should retry, but this API +       leaves this to the application */ +    break; +  default: +    /* unexpected response code */ +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected response code %u\n", +                response_code); +    GNUNET_break (0); +    response_code = 0; +    break; +  } +  dwh->cb (dwh->cb_cls, +           response_code, +           json, +           wtid, +           execution_time, +           coin_contribution); +  json_decref (json); +  TALER_EXCHANGE_deposit_wtid_cancel (dwh); +} + + +/** + * Obtain wire transfer details about an existing deposit operation. + * + * @param exchange the exchange to query + * @param merchant_priv the merchant's private key + * @param h_wire hash of merchant's wire transfer details + * @param h_contract hash of the contract + * @param coin_pub public key of the coin + * @param transaction_id transaction identifier + * @param cb function to call with the result + * @param cb_cls closure for @a cb + * @return handle to abort request + */ +struct TALER_EXCHANGE_DepositWtidHandle * +TALER_EXCHANGE_deposit_wtid (struct TALER_EXCHANGE_Handle *exchange, +                         const struct TALER_MerchantPrivateKeyP *merchant_priv, +                         const struct GNUNET_HashCode *h_wire, +                         const struct GNUNET_HashCode *h_contract, +                         const struct TALER_CoinSpendPublicKeyP *coin_pub, +                         uint64_t transaction_id, +                         TALER_EXCHANGE_DepositWtidCallback cb, +                         void *cb_cls) +{ +  struct TALER_DepositTrackPS dtp; +  struct TALER_MerchantSignatureP merchant_sig; +  struct TALER_EXCHANGE_DepositWtidHandle *dwh; +  struct TALER_EXCHANGE_Context *ctx; +  json_t *deposit_wtid_obj; +  CURL *eh; + +  if (GNUNET_YES != +      MAH_handle_is_ready (exchange)) +  { +    GNUNET_break (0); +    return NULL; +  } +  dtp.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_DEPOSIT_WTID); +  dtp.purpose.size = htonl (sizeof (dtp)); +  dtp.h_contract = *h_contract; +  dtp.h_wire = *h_wire; +  dtp.transaction_id = GNUNET_htonll (transaction_id); +  GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv, +                                      &dtp.merchant.eddsa_pub); + +  dtp.coin_pub = *coin_pub; +  GNUNET_assert (GNUNET_OK == +                 GNUNET_CRYPTO_eddsa_sign (&merchant_priv->eddsa_priv, +                                           &dtp.purpose, +                                           &merchant_sig.eddsa_sig)); +  deposit_wtid_obj = json_pack ("{s:o, s:o," /* H_wire, H_contract */ +                                " s:o, s:I," /* coin_pub, transaction_id */ +                                " s:o, s:o}", /* merchant_pub, merchant_sig */ +                                "H_wire", TALER_json_from_data (h_wire, +                                                                sizeof (struct GNUNET_HashCode)), +                                "H_contract", TALER_json_from_data (h_contract, +                                                                    sizeof (struct GNUNET_HashCode)), +                                "coin_pub", TALER_json_from_data (coin_pub, +                                                                  sizeof (*coin_pub)), +                                "transaction_id", (json_int_t) transaction_id, +                                "merchant_pub", TALER_json_from_data (&dtp.merchant, +                                                                      sizeof (struct TALER_MerchantPublicKeyP)), +                                "merchant_sig", TALER_json_from_data (&merchant_sig, +                                                                      sizeof (merchant_sig))); + +  dwh = GNUNET_new (struct TALER_EXCHANGE_DepositWtidHandle); +  dwh->exchange = exchange; +  dwh->cb = cb; +  dwh->cb_cls = cb_cls; +  dwh->url = MAH_path_to_url (exchange, "/deposit/wtid"); +  dwh->depconf.purpose.size = htonl (sizeof (struct TALER_DepositConfirmationPS)); +  dwh->depconf.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE); +  dwh->depconf.h_wire = *h_wire; +  dwh->depconf.h_contract = *h_contract; +  dwh->depconf.coin_pub = *coin_pub; +  dwh->depconf.transaction_id = GNUNET_htonll (transaction_id); + +  eh = curl_easy_init (); +  GNUNET_assert (NULL != (dwh->json_enc = +                          json_dumps (deposit_wtid_obj, +                                      JSON_COMPACT))); +  json_decref (deposit_wtid_obj); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_URL, +                                   dwh->url)); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_POSTFIELDS, +                                   dwh->json_enc)); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_POSTFIELDSIZE, +                                   strlen (dwh->json_enc))); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_WRITEFUNCTION, +                                   &MAC_download_cb)); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_WRITEDATA, +                                   &dwh->db)); +  ctx = MAH_handle_to_context (exchange); +  dwh->job = MAC_job_add (ctx, +                          eh, +                          GNUNET_YES, +                          &handle_deposit_wtid_finished, +                          dwh); +  return dwh; +} + + +/** + * Cancel deposit wtid request.  This function cannot be used on a request + * handle if a response is already served for it. + * + * @param dwh the wire deposits request handle + */ +void +TALER_EXCHANGE_deposit_wtid_cancel (struct TALER_EXCHANGE_DepositWtidHandle *dwh) +{ +  if (NULL != dwh->job) +  { +    MAC_job_cancel (dwh->job); +    dwh->job = NULL; +  } +  GNUNET_free_non_null (dwh->db.buf); +  GNUNET_free (dwh->url); +  GNUNET_free (dwh->json_enc); +  GNUNET_free (dwh); +} + + +/* end of exchange_api_deposit_wtid.c */ diff --git a/src/exchange-lib/exchange_api_handle.c b/src/exchange-lib/exchange_api_handle.c new file mode 100644 index 00000000..d4b3e4de --- /dev/null +++ b/src/exchange-lib/exchange_api_handle.c @@ -0,0 +1,902 @@ +/* +  This file is part of TALER +  Copyright (C) 2014, 2015 GNUnet e.V. + +  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, If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/exchange_api_handle.c + * @brief Implementation of the "handle" component of the exchange's HTTP API + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * @author Christian Grothoff + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include "taler_exchange_service.h" +#include "taler_signatures.h" +#include "exchange_api_context.h" +#include "exchange_api_json.h" +#include "exchange_api_handle.h" + + +/** + * Log error related to CURL operations. + * + * @param type log level + * @param function which function failed to run + * @param code what was the curl error code + */ +#define CURL_STRERROR(type, function, code)      \ + GNUNET_log (type, "Curl function `%s' has failed at `%s:%d' with error: %s", \ +             function, __FILE__, __LINE__, curl_easy_strerror (code)); + + +/** + * Stages of initialization for the `struct TALER_EXCHANGE_Handle` + */ +enum ExchangeHandleState +{ +  /** +   * Just allocated. +   */ +  MHS_INIT = 0, + +  /** +   * Obtained the exchange's certification data and keys. +   */ +  MHS_CERT = 1, + +  /** +   * Failed to initialize (fatal). +   */ +  MHS_FAILED = 2 +}; + + +/** + * Data for the request to get the /keys of a exchange. + */ +struct KeysRequest; + + +/** + * Handle to the exchange + */ +struct TALER_EXCHANGE_Handle +{ +  /** +   * The context of this handle +   */ +  struct TALER_EXCHANGE_Context *ctx; + +  /** +   * The URL of the exchange (i.e. "http://exchange.taler.net/") +   */ +  char *url; + +  /** +   * Function to call with the exchange's certification data, +   * NULL if this has already been done. +   */ +  TALER_EXCHANGE_CertificationCallback cert_cb; + +  /** +   * Closure to pass to @e cert_cb. +   */ +  void *cert_cb_cls; + +  /** +   * Data for the request to get the /keys of a exchange, +   * NULL once we are past stage #MHS_INIT. +   */ +  struct KeysRequest *kr; + +  /** +   * Key data of the exchange, only valid if +   * @e handshake_complete is past stage #MHS_CERT. +   */ +  struct TALER_EXCHANGE_Keys key_data; + +  /** +   * Stage of the exchange's initialization routines. +   */ +  enum ExchangeHandleState state; + +}; + + +/* ***************** Internal /keys fetching ************* */ + +/** + * Data for the request to get the /keys of a exchange. + */ +struct KeysRequest +{ +  /** +   * The connection to exchange this request handle will use +   */ +  struct TALER_EXCHANGE_Handle *exchange; + +  /** +   * The url for this handle +   */ +  char *url; + +  /** +   * Entry for this request with the `struct TALER_EXCHANGE_Context`. +   */ +  struct MAC_Job *job; + +  /** +   * Data structure for the download. +   */ +  struct MAC_DownloadBuffer db; + +}; + + +/** + * Release memory occupied by a keys request. + * Note that this does not cancel the request + * itself. + * + * @param kr request to free + */ +static void +free_keys_request (struct KeysRequest *kr) +{ +  GNUNET_free_non_null (kr->db.buf); +  GNUNET_free (kr->url); +  GNUNET_free (kr); +} + + +#define EXITIF(cond)                                              \ +  do {                                                            \ +    if (cond) { GNUNET_break (0); goto EXITIF_exit; }             \ +  } while (0) + + +/** + * Parse a exchange's signing key encoded in JSON. + * + * @param[out] sign_key where to return the result + * @param[in] sign_key_obj json to parse + * @param master_key master key to use to verify signature + * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is + *        invalid or the json malformed. + */ +static int +parse_json_signkey (struct TALER_EXCHANGE_SigningPublicKey *sign_key, +                    json_t *sign_key_obj, +                    const struct TALER_MasterPublicKeyP *master_key) +{ +  struct TALER_ExchangeSigningKeyValidityPS sign_key_issue; +  struct GNUNET_CRYPTO_EddsaSignature sig; +  struct GNUNET_TIME_Absolute valid_from; +  struct GNUNET_TIME_Absolute valid_until; +  struct GNUNET_TIME_Absolute valid_legal; +  struct MAJ_Specification spec[] = { +    MAJ_spec_fixed_auto ("master_sig", +                         &sig), +    MAJ_spec_fixed_auto ("key", +                         &sign_key_issue.signkey_pub), +    MAJ_spec_absolute_time ("stamp_start", +                            &valid_from), +    MAJ_spec_absolute_time ("stamp_expire", +                            &valid_until), +    MAJ_spec_absolute_time ("stamp_end", +                            &valid_legal), +    MAJ_spec_end +  }; + +  if (GNUNET_OK != +      MAJ_parse_json (sign_key_obj, +                      spec)) +  { +    GNUNET_break_op (0); +    return GNUNET_SYSERR; +  } + +  sign_key_issue.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY); +  sign_key_issue.purpose.size = +    htonl (sizeof (struct TALER_ExchangeSigningKeyValidityPS) +           - offsetof (struct TALER_ExchangeSigningKeyValidityPS, +                       purpose)); +  sign_key_issue.master_public_key = *master_key; +  sign_key_issue.start = GNUNET_TIME_absolute_hton (valid_from); +  sign_key_issue.expire = GNUNET_TIME_absolute_hton (valid_until); +  sign_key_issue.end = GNUNET_TIME_absolute_hton (valid_legal); +  if (GNUNET_OK != +      GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY, +                                  &sign_key_issue.purpose, +                                  &sig, +                                  &master_key->eddsa_pub)) +  { +    GNUNET_break_op (0); +    return GNUNET_SYSERR; +  } +  sign_key->valid_from = valid_from; +  sign_key->valid_until = valid_until; +  sign_key->key = sign_key_issue.signkey_pub; +  return GNUNET_OK; +} + + +/** + * Parse a exchange's denomination key encoded in JSON. + * + * @param[out] denom_key where to return the result + * @param[in] denom_key_obj json to parse + * @param master_key master key to use to verify signature + * @param hash_context where to accumulate data for signature verification + * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is + *        invalid or the json malformed. + */ +static int +parse_json_denomkey (struct TALER_EXCHANGE_DenomPublicKey *denom_key, +                     json_t *denom_key_obj, +                     struct TALER_MasterPublicKeyP *master_key, +                     struct GNUNET_HashContext *hash_context) +{ +  struct GNUNET_TIME_Absolute valid_from; +  struct GNUNET_TIME_Absolute withdraw_valid_until; +  struct GNUNET_TIME_Absolute deposit_valid_until; +  struct GNUNET_TIME_Absolute expire_legal; +  struct TALER_Amount value; +  struct TALER_Amount fee_withdraw; +  struct TALER_Amount fee_deposit; +  struct TALER_Amount fee_refresh; +  struct TALER_DenominationKeyValidityPS denom_key_issue; +  struct GNUNET_CRYPTO_rsa_PublicKey *pk; +  struct GNUNET_CRYPTO_EddsaSignature sig; + +  struct MAJ_Specification spec[] = { +    MAJ_spec_fixed_auto ("master_sig", +                         &sig), +    MAJ_spec_absolute_time ("stamp_expire_deposit", +                            &deposit_valid_until), +    MAJ_spec_absolute_time ("stamp_expire_withdraw", +                            &withdraw_valid_until), +    MAJ_spec_absolute_time ("stamp_start", +                            &valid_from), +    MAJ_spec_absolute_time ("stamp_expire_legal", +                            &expire_legal), +    MAJ_spec_amount ("value", +                     &value), +    MAJ_spec_amount ("fee_withdraw", +                     &fee_withdraw), +    MAJ_spec_amount ("fee_deposit", +                     &fee_deposit), +    MAJ_spec_amount ("fee_refresh", +                     &fee_refresh), +    MAJ_spec_rsa_public_key ("denom_pub", +                             &pk), +    MAJ_spec_end +  }; + +  if (GNUNET_OK != +      MAJ_parse_json (denom_key_obj, +                      spec)) +  { +    GNUNET_break_op (0); +    return GNUNET_SYSERR; +  } + +  memset (&denom_key_issue, 0, sizeof (denom_key_issue)); +  GNUNET_CRYPTO_rsa_public_key_hash (pk, +                                     &denom_key_issue.denom_hash); +  denom_key_issue.purpose.purpose +    = htonl (TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY); +  denom_key_issue.purpose.size +    = htonl (sizeof (struct TALER_DenominationKeyValidityPS)); +  denom_key_issue.master = *master_key; +  denom_key_issue.start = GNUNET_TIME_absolute_hton (valid_from); +  denom_key_issue.expire_withdraw = GNUNET_TIME_absolute_hton (withdraw_valid_until); +  denom_key_issue.expire_spend = GNUNET_TIME_absolute_hton (deposit_valid_until); +  denom_key_issue.expire_legal = GNUNET_TIME_absolute_hton (expire_legal); +  TALER_amount_hton (&denom_key_issue.value, +                     &value); +  TALER_amount_hton (&denom_key_issue.fee_withdraw, +                     &fee_withdraw); +  TALER_amount_hton (&denom_key_issue.fee_deposit, +                     &fee_deposit); +  TALER_amount_hton (&denom_key_issue.fee_refresh, +                     &fee_refresh); +  EXITIF (GNUNET_SYSERR == +          GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY, +                                      &denom_key_issue.purpose, +                                      &sig, +                                      &master_key->eddsa_pub)); +  GNUNET_CRYPTO_hash_context_read (hash_context, +                                   &denom_key_issue.denom_hash, +                                   sizeof (struct GNUNET_HashCode)); +  denom_key->key.rsa_public_key = pk; +  denom_key->h_key = denom_key_issue.denom_hash; +  denom_key->valid_from = valid_from; +  denom_key->withdraw_valid_until = withdraw_valid_until; +  denom_key->deposit_valid_until = deposit_valid_until; +  denom_key->expire_legal = expire_legal; +  denom_key->value = value; +  denom_key->fee_withdraw = fee_withdraw; +  denom_key->fee_deposit = fee_deposit; +  denom_key->fee_refresh = fee_refresh; +  return GNUNET_OK; + + EXITIF_exit: +  MAJ_parse_free (spec); +  return GNUNET_SYSERR; +} + + +/** + * Parse a exchange's auditor information encoded in JSON. + * + * @param[out] auditor where to return the result + * @param[in] auditor_obj json to parse + * @param key_data information about denomination keys + * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is + *        invalid or the json malformed. + */ +static int +parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor, +                    json_t *auditor_obj, +                    const struct TALER_EXCHANGE_Keys *key_data) +{ +  json_t *keys; +  json_t *key; +  unsigned int len; +  unsigned int off; +  unsigned int i; +  struct TALER_ExchangeKeyValidityPS kv; +  struct MAJ_Specification spec[] = { +    MAJ_spec_fixed_auto ("auditor_pub", +                         &auditor->auditor_pub), +    MAJ_spec_json ("denomination_keys", +                   &keys), +    MAJ_spec_end +  }; + +  auditor->auditor_url = NULL; /* #3987 */ +  if (GNUNET_OK != +      MAJ_parse_json (auditor_obj, +                      spec)) +  { +    GNUNET_break_op (0); +    return GNUNET_SYSERR; +  } +  kv.purpose.purpose = htonl (TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS); +  kv.purpose.size = htonl (sizeof (struct TALER_ExchangeKeyValidityPS)); +  kv.master = key_data->master_pub; +  len = json_array_size (keys); +  auditor->denom_keys = GNUNET_new_array (len, +                                          const struct TALER_EXCHANGE_DenomPublicKey *); +  i = 0; +  off = 0; +  json_array_foreach (keys, i, key) { +    struct TALER_AuditorSignatureP auditor_sig; +    struct GNUNET_HashCode denom_h; +    const struct TALER_EXCHANGE_DenomPublicKey *dk; +    unsigned int j; +    struct MAJ_Specification spec[] = { +      MAJ_spec_fixed_auto ("denom_pub_h", +                           &denom_h), +      MAJ_spec_fixed_auto ("auditor_sig", +                           &auditor_sig), +      MAJ_spec_end +    }; + +    if (GNUNET_OK != +        MAJ_parse_json (key, +                        spec)) +      { +      GNUNET_break_op (0); +      continue; +    } +    dk = NULL; +    for (j=0;j<key_data->num_denom_keys;j++) +    { +      if (0 == memcmp (&denom_h, +                       &key_data->denom_keys[j].h_key, +                       sizeof (struct GNUNET_HashCode))) +      { +        dk = &key_data->denom_keys[j]; +        break; +      } +    } +    if (NULL == dk) +    { +      GNUNET_break_op (0); +      continue; +    } +    kv.start = GNUNET_TIME_absolute_hton (dk->valid_from); +    kv.expire_withdraw = GNUNET_TIME_absolute_hton (dk->withdraw_valid_until); +    kv.expire_spend = GNUNET_TIME_absolute_hton (dk->deposit_valid_until); +    kv.expire_legal = GNUNET_TIME_absolute_hton (dk->expire_legal); +    TALER_amount_hton (&kv.value, +                       &dk->value); +    TALER_amount_hton (&kv.fee_withdraw, +                       &dk->fee_withdraw); +    TALER_amount_hton (&kv.fee_deposit, +                       &dk->fee_deposit); +    TALER_amount_hton (&kv.fee_refresh, +                       &dk->fee_refresh); +    kv.denom_hash = dk->h_key; +    if (GNUNET_OK != +        GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS, +                                    &kv.purpose, +                                    &auditor_sig.eddsa_sig, +                                    &auditor->auditor_pub.eddsa_pub)) +    { +      GNUNET_break_op (0); +      continue; +    } +    auditor->denom_keys[off] = dk; +    off++; +  } +  auditor->num_denom_keys = off; +  return GNUNET_OK; +} + + +/** + * Decode the JSON in @a resp_obj from the /keys response and store the data + * in the @a key_data. + * + * @param[in] resp_obj JSON object to parse + * @param[out] key_data where to store the results we decoded + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error (malformed JSON) + */ +static int +decode_keys_json (json_t *resp_obj, +                  struct TALER_EXCHANGE_Keys *key_data) +{ +  struct GNUNET_TIME_Absolute list_issue_date; +  struct TALER_ExchangeSignatureP sig; +  struct TALER_ExchangeKeySetPS ks; +  struct GNUNET_HashContext *hash_context; +  struct TALER_ExchangePublicKeyP pub; + +  if (JSON_OBJECT != json_typeof (resp_obj)) +    return GNUNET_SYSERR; + +  hash_context = GNUNET_CRYPTO_hash_context_start (); +  /* parse the master public key and issue date of the response */ +  { +    struct MAJ_Specification spec[] = { +      MAJ_spec_fixed_auto ("master_public_key", +                           &key_data->master_pub), +      MAJ_spec_fixed_auto ("eddsa_sig", +                           &sig), +      MAJ_spec_fixed_auto ("eddsa_pub", +                           &pub), +      MAJ_spec_absolute_time ("list_issue_date", +                              &list_issue_date), +      MAJ_spec_end +    }; + +    EXITIF (GNUNET_OK != +            MAJ_parse_json (resp_obj, +                            spec)); +  } + +  /* parse the signing keys */ +  { +    json_t *sign_keys_array; +    json_t *sign_key_obj; +    unsigned int index; + +    EXITIF (NULL == (sign_keys_array = +                     json_object_get (resp_obj, +                                      "signkeys"))); +    EXITIF (JSON_ARRAY != json_typeof (sign_keys_array)); +    EXITIF (0 == (key_data->num_sign_keys = +                  json_array_size (sign_keys_array))); +    key_data->sign_keys +      = GNUNET_new_array (key_data->num_sign_keys, +                          struct TALER_EXCHANGE_SigningPublicKey); +    index = 0; +    json_array_foreach (sign_keys_array, index, sign_key_obj) { +      EXITIF (GNUNET_SYSERR == +              parse_json_signkey (&key_data->sign_keys[index], +                                  sign_key_obj, +                                  &key_data->master_pub)); +    } +  } + +  /* parse the denomination keys */ +  { +    json_t *denom_keys_array; +    json_t *denom_key_obj; +    unsigned int index; + +    EXITIF (NULL == (denom_keys_array = +                     json_object_get (resp_obj, "denoms"))); +    EXITIF (JSON_ARRAY != json_typeof (denom_keys_array)); +    EXITIF (0 == (key_data->num_denom_keys = json_array_size (denom_keys_array))); +    key_data->denom_keys = GNUNET_new_array (key_data->num_denom_keys, +                                             struct TALER_EXCHANGE_DenomPublicKey); +    index = 0; +    json_array_foreach (denom_keys_array, index, denom_key_obj) { +      EXITIF (GNUNET_SYSERR == +              parse_json_denomkey (&key_data->denom_keys[index], +                                   denom_key_obj, +                                   &key_data->master_pub, +                                   hash_context)); +    } +  } + +  /* parse the auditor information */ +  { +    json_t *auditors_array; +    json_t *auditor_info; +    unsigned int len; +    unsigned int index; + +    EXITIF (NULL == (auditors_array = +                     json_object_get (resp_obj, "auditors"))); +    EXITIF (JSON_ARRAY != json_typeof (auditors_array)); +    len = json_array_size (auditors_array); +    if (0 != len) +    { +      key_data->auditors = GNUNET_new_array (len, +                                             struct TALER_EXCHANGE_AuditorInformation); +      index = 0; +      json_array_foreach (auditors_array, index, auditor_info) { +        EXITIF (GNUNET_SYSERR == +                parse_json_auditor (&key_data->auditors[index], +                                    auditor_info, +                                    key_data)); +      } +    } +  } + +  /* Validate signature... */ +  ks.purpose.size = htonl (sizeof (ks)); +  ks.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_KEY_SET); +  ks.list_issue_date = GNUNET_TIME_absolute_hton (list_issue_date); +  GNUNET_CRYPTO_hash_context_finish (hash_context, +                                     &ks.hc); +  hash_context = NULL; +  EXITIF (GNUNET_OK != +          TALER_EXCHANGE_test_signing_key (key_data, +                                       &pub)); +  EXITIF (GNUNET_OK != +          GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_KEY_SET, +                                      &ks.purpose, +                                      &sig.eddsa_signature, +                                      &pub.eddsa_pub)); +  return GNUNET_OK; + EXITIF_exit: + +  if (NULL != hash_context) +    GNUNET_CRYPTO_hash_context_abort (hash_context); +  return GNUNET_SYSERR; +} + + +/** + * Callback used when downloading the reply to a /keys request + * is complete. + * + * @param cls the `struct KeysRequest` + * @param eh easy handle of the original request + */ +static void +keys_completed_cb (void *cls, +                   CURL *eh) +{ +  struct KeysRequest *kr = cls; +  struct TALER_EXCHANGE_Handle *exchange = kr->exchange; +  json_t *resp_obj; +  long response_code; +  TALER_EXCHANGE_CertificationCallback cb; + +  resp_obj = MAC_download_get_result (&kr->db, +                                      eh, +                                      &response_code); +  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, +              "Received keys from URL `%s' with status %ld.\n", +              kr->url, +              response_code); +  switch (response_code) { +  case 0: +    break; +  case MHD_HTTP_OK: +    if ( (NULL == resp_obj) || +         (GNUNET_OK != +          decode_keys_json (resp_obj, +                            &kr->exchange->key_data)) ) +      response_code = 0; +    break; +  default: +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected response code %u\n", +                response_code); +    break; +  } +  if (NULL != resp_obj) +    json_decref (resp_obj); + +  if (MHD_HTTP_OK != response_code) +  { +    exchange->kr = NULL; +    free_keys_request (kr); +    exchange->state = MHS_FAILED; +    /* notify application that we failed */ +    if (NULL != (cb = exchange->cert_cb)) +    { +      exchange->cert_cb = NULL; +      cb (exchange->cert_cb_cls, +	  NULL); +    } +    return; +  } +  exchange->kr = NULL; +  free_keys_request (kr); +  exchange->state = MHS_CERT; +  /* notify application about the key information */ +  if (NULL != (cb = exchange->cert_cb)) +  { +    exchange->cert_cb = NULL; +    cb (exchange->cert_cb_cls, +	&exchange->key_data); +  } +} + + +/* ********************* library internal API ********* */ + + +/** + * Get the context of a exchange. + * + * @param h the exchange handle to query + * @return ctx context to execute jobs in + */ +struct TALER_EXCHANGE_Context * +MAH_handle_to_context (struct TALER_EXCHANGE_Handle *h) +{ +  return h->ctx; +} + + +/** + * Check if the handle is ready to process requests. + * + * @param h the exchange handle to query + * @return #GNUNET_YES if we are ready, #GNUNET_NO if not + */ +int +MAH_handle_is_ready (struct TALER_EXCHANGE_Handle *h) +{ +  return (MHS_CERT == h->state) ? GNUNET_YES : GNUNET_NO; +} + + +/** + * Obtain the URL to use for an API request. + * + * @param h the exchange handle to query + * @param path Taler API path (i.e. "/reserve/withdraw") + * @return the full URI to use with cURL + */ +char * +MAH_path_to_url (struct TALER_EXCHANGE_Handle *h, +                 const char *path) +{ +  char *url; + +  if ( ('/' == path[0]) && +       (0 < strlen (h->url)) && +       ('/' == h->url[strlen (h->url) - 1]) ) +    path++; /* avoid generating URL with "//" from concat */ +  GNUNET_asprintf (&url, +                   "%s%s", +                   h->url, +                   path); +  return url; +} + + +/* ********************* public API ******************* */ + +/** + * Initialise a connection to the exchange. Will connect to the + * exchange and obtain information about the exchange's master public + * key and the exchange's auditor.  The respective information will + * be passed to the @a cert_cb once available, and all future + * interactions with the exchange will be checked to be signed + * (where appropriate) by the respective master key. + * + * @param ctx the context + * @param url HTTP base URL for the exchange + * @param cert_cb function to call with the exchange's certification information + * @param cert_cb_cls closure for @a cert_cb + * @param ... list of additional arguments, terminated by #TALER_EXCHANGE_OPTION_END. + * @return the exchange handle; NULL upon error + */ +struct TALER_EXCHANGE_Handle * +TALER_EXCHANGE_connect (struct TALER_EXCHANGE_Context *ctx, +                    const char *url, +                    TALER_EXCHANGE_CertificationCallback cert_cb, +                    void *cert_cb_cls, +                    ...) +{ +  struct TALER_EXCHANGE_Handle *exchange; +  struct KeysRequest *kr; +  CURL *c; + +  exchange = GNUNET_new (struct TALER_EXCHANGE_Handle); +  exchange->ctx = ctx; +  exchange->url = GNUNET_strdup (url); +  exchange->cert_cb = cert_cb; +  exchange->cert_cb_cls = cert_cb_cls; +  kr = GNUNET_new (struct KeysRequest); +  kr->exchange = exchange; +  kr->url = MAH_path_to_url (exchange, "/keys"); +  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, +              "Requesting keys with URL `%s'.\n", +              kr->url); +  c = curl_easy_init (); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (c, +                                   CURLOPT_VERBOSE, +                                   0)); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (c, +                                   CURLOPT_STDERR, +                                   stdout)); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (c, +                                   CURLOPT_URL, +                                   kr->url)); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (c, +                                   CURLOPT_WRITEFUNCTION, +                                   &MAC_download_cb)); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (c, +                                   CURLOPT_WRITEDATA, +                                   &kr->db)); +  kr->job = MAC_job_add (exchange->ctx, +                         c, +                         GNUNET_NO, +                         &keys_completed_cb, +                         kr); +  exchange->kr = kr; +  return exchange; +} + + +/** + * Disconnect from the exchange + * + * @param exchange the exchange handle + */ +void +TALER_EXCHANGE_disconnect (struct TALER_EXCHANGE_Handle *exchange) +{ +  unsigned int i; + +  if (NULL != exchange->kr) +  { +    MAC_job_cancel (exchange->kr->job); +    free_keys_request (exchange->kr); +    exchange->kr = NULL; +  } +  GNUNET_array_grow (exchange->key_data.sign_keys, +                     exchange->key_data.num_sign_keys, +                     0); +  for (i=0;i<exchange->key_data.num_denom_keys;i++) +    GNUNET_CRYPTO_rsa_public_key_free (exchange->key_data.denom_keys[i].key.rsa_public_key); +  GNUNET_array_grow (exchange->key_data.denom_keys, +                     exchange->key_data.num_denom_keys, +                     0); +  GNUNET_array_grow (exchange->key_data.auditors, +                     exchange->key_data.num_auditors, +                     0); +  GNUNET_free (exchange->url); +  GNUNET_free (exchange); +} + + +/** + * Test if the given @a pub is a the current signing key from the exchange + * according to @a keys. + * + * @param keys the exchange's key set + * @param pub claimed current online signing key for the exchange + * @return #GNUNET_OK if @a pub is (according to /keys) a current signing key + */ +int +TALER_EXCHANGE_test_signing_key (const struct TALER_EXCHANGE_Keys *keys, +                             const struct TALER_ExchangePublicKeyP *pub) +{ +  struct GNUNET_TIME_Absolute now; +  unsigned int i; + +  /* we will check using a tolerance of 1h for the time */ +  now = GNUNET_TIME_absolute_get (); +  for (i=0;i<keys->num_sign_keys;i++) +    if ( (keys->sign_keys[i].valid_from.abs_value_us <= now.abs_value_us + 60 * 60 * 1000LL * 1000LL) && +         (keys->sign_keys[i].valid_until.abs_value_us > now.abs_value_us - 60 * 60 * 1000LL * 1000LL) && +         (0 == memcmp (pub, +                       &keys->sign_keys[i].key, +                       sizeof (struct TALER_ExchangePublicKeyP))) ) +      return GNUNET_OK; +  return GNUNET_SYSERR; +} + + +/** + * Obtain the denomination key details from the exchange. + * + * @param keys the exchange's key set + * @param pk public key of the denomination to lookup + * @return details about the given denomination key, NULL if the key is + * not found + */ +const struct TALER_EXCHANGE_DenomPublicKey * +TALER_EXCHANGE_get_denomination_key (const struct TALER_EXCHANGE_Keys *keys, +                                 const struct TALER_DenominationPublicKey *pk) +{ +  unsigned int i; + +  for (i=0;i<keys->num_denom_keys;i++) +    if (0 == GNUNET_CRYPTO_rsa_public_key_cmp (pk->rsa_public_key, +                                               keys->denom_keys[i].key.rsa_public_key)) +      return &keys->denom_keys[i]; +  return NULL; +} + + +/** + * Obtain the denomination key details from the exchange. + * + * @param keys the exchange's key set + * @param hc hash of the public key of the denomination to lookup + * @return details about the given denomination key + */ +const struct TALER_EXCHANGE_DenomPublicKey * +TALER_EXCHANGE_get_denomination_key_by_hash (const struct TALER_EXCHANGE_Keys *keys, +                                         const struct GNUNET_HashCode *hc) +{ +  unsigned int i; + +  for (i=0;i<keys->num_denom_keys;i++) +    if (0 == memcmp (hc, +                     &keys->denom_keys[i].h_key, +                     sizeof (struct GNUNET_HashCode))) +      return &keys->denom_keys[i]; +  return NULL; +} + + +/** + * Obtain the keys from the exchange. + * + * @param exchange the exchange handle + * @return the exchange's key set + */ +const struct TALER_EXCHANGE_Keys * +TALER_EXCHANGE_get_keys (const struct TALER_EXCHANGE_Handle *exchange) +{ +  return &exchange->key_data; +} + + +/* end of exchange_api_handle.c */ diff --git a/src/exchange-lib/exchange_api_handle.h b/src/exchange-lib/exchange_api_handle.h new file mode 100644 index 00000000..48423a7b --- /dev/null +++ b/src/exchange-lib/exchange_api_handle.h @@ -0,0 +1,59 @@ +/* +  This file is part of TALER +  Copyright (C) 2014, 2015 GNUnet e.V. + +  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, If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/exchange_api_handle.h + * @brief Internal interface to the handle part of the exchange's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include <curl/curl.h> +#include "taler_exchange_service.h" + + +/** + * Get the context of a exchange. + * + * @param h the exchange handle to query + * @return ctx context to execute jobs in + */ +struct TALER_EXCHANGE_Context * +MAH_handle_to_context (struct TALER_EXCHANGE_Handle *h); + + +/** + * Check if the handle is ready to process requests. + * + * @param h the exchange handle to query + * @return #GNUNET_YES if we are ready, #GNUNET_NO if not + */ +int +MAH_handle_is_ready (struct TALER_EXCHANGE_Handle *h); + + +/** + * Obtain the URL to use for an API request. + * + * @param h the exchange handle to query + * @param path Taler API path (i.e. "/reserve/withdraw") + * @return the full URI to use with cURL + */ +char * +MAH_path_to_url (struct TALER_EXCHANGE_Handle *h, +                 const char *path); + + +/* end of exchange_api_handle.h */ diff --git a/src/exchange-lib/exchange_api_json.c b/src/exchange-lib/exchange_api_json.c new file mode 100644 index 00000000..d91feba0 --- /dev/null +++ b/src/exchange-lib/exchange_api_json.c @@ -0,0 +1,525 @@ +/* +  This file is part of TALER +  Copyright (C) 2014, 2015 GNUnet e.V. + +  TALER is free software; you can redistribute it and/or modify it under the +  terms of the GNU Affero 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 Affero General Public License for more details. + +  You should have received a copy of the GNU Affero General Public License along with +  TALER; see the file COPYING.  If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/exchange_api_json.c + * @brief functions to parse incoming requests (JSON snippets) + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include "exchange_api_json.h" + +/** + * Navigate and parse data in a JSON tree. + * + * @param root the JSON node to start the navigation at. + * @param spec parse specification array + * @return offset in @a spec where parsing failed, -1 on success (!) + */ +static int +parse_json (json_t *root, +            struct MAJ_Specification *spec) +{ +  int i; +  json_t *pos; /* what's our current position? */ + +  pos = root; +  for (i=0;MAJ_CMD_END != spec[i].cmd;i++) +  { +    pos = json_object_get (root, +                           spec[i].field); +    if (NULL == pos) +    { +      GNUNET_break_op (0); +      return i; +    } +    switch (spec[i].cmd) +    { +    case MAJ_CMD_END: +      GNUNET_assert (0); +      return i; +    case MAJ_CMD_AMOUNT: +      if (GNUNET_OK != +          TALER_json_to_amount (pos, +                                spec[i].details.amount)) +      { +        GNUNET_break_op (0); +        return i; +      } +      break; +    case MAJ_CMD_TIME_ABSOLUTE: +      if (GNUNET_OK != +          TALER_json_to_abs (pos, +                             spec[i].details.abs_time)) +      { +        GNUNET_break_op (0); +        return i; +      } +      break; + +    case MAJ_CMD_STRING: +      { +        const char *str; + +        str = json_string_value (pos); +        if (NULL == str) +        { +          GNUNET_break_op (0); +          return i; +        } +        *spec[i].details.strptr = str; +      } +      break; + +    case MAJ_CMD_BINARY_FIXED: +      { +        const char *str; +        int res; + +        str = json_string_value (pos); +        if (NULL == str) +        { +          GNUNET_break_op (0); +          return i; +        } +        res = GNUNET_STRINGS_string_to_data (str, strlen (str), +                                             spec[i].details.fixed_data.dest, +                                             spec[i].details.fixed_data.dest_size); +        if (GNUNET_OK != res) +        { +          GNUNET_break_op (0); +          return i; +        } +      } +      break; + +    case MAJ_CMD_BINARY_VARIABLE: +      { +        const char *str; +        size_t size; +        void *data; +        int res; + +        str = json_string_value (pos); +        if (NULL == str) +        { +          GNUNET_break_op (0); +          return i; +        } +        size = (strlen (str) * 5) / 8; +        if (size >= 1024) +        { +          GNUNET_break_op (0); +          return i; +        } +        data = GNUNET_malloc (size); +        res = GNUNET_STRINGS_string_to_data (str, +                                             strlen (str), +                                             data, +                                             size); +        if (GNUNET_OK != res) +        { +          GNUNET_break_op (0); +          GNUNET_free (data); +          return i; +        } +        *spec[i].details.variable_data.dest_p = data; +        *spec[i].details.variable_data.dest_size_p = size; +      } +      break; + +    case MAJ_CMD_RSA_PUBLIC_KEY: +      { +        size_t size; +        const char *str; +        int res; +        void *buf; + +        str = json_string_value (pos); +        if (NULL == str) +        { +          GNUNET_break_op (0); +          return i; +        } +        size = (strlen (str) * 5) / 8; +        buf = GNUNET_malloc (size); +        res = GNUNET_STRINGS_string_to_data (str, +                                             strlen (str), +                                             buf, +                                             size); +        if (GNUNET_OK != res) +        { +          GNUNET_free (buf); +          GNUNET_break_op (0); +          return i; +        } +        *spec[i].details.rsa_public_key +          = GNUNET_CRYPTO_rsa_public_key_decode (buf, +                                                 size); +        GNUNET_free (buf); +        if (NULL == spec[i].details.rsa_public_key) +        { +          GNUNET_break_op (0); +          return i; +        } +      } +      break; + +    case MAJ_CMD_RSA_SIGNATURE: +      { +        size_t size; +        const char *str; +        int res; +        void *buf; + +        str = json_string_value (pos); +        if (NULL == str) +        { +          GNUNET_break_op (0); +          return i; +        } +        size = (strlen (str) * 5) / 8; +        buf = GNUNET_malloc (size); +        res = GNUNET_STRINGS_string_to_data (str, +                                             strlen (str), +                                             buf, +                                             size); +        if (GNUNET_OK != res) +        { +          GNUNET_free (buf); +          GNUNET_break_op (0); +          return i; +        } +        *spec[i].details.rsa_signature +          = GNUNET_CRYPTO_rsa_signature_decode (buf, +                                                size); +        GNUNET_free (buf); +        if (NULL == spec[i].details.rsa_signature) +          return i; +      } +      break; + +    case MAJ_CMD_UINT16: +      { +        json_int_t val; + +        if (! json_is_integer (pos)) +        { +          GNUNET_break_op (0); +          return i; +        } +        val = json_integer_value (pos); +        if ( (0 > val) || (val > UINT16_MAX) ) +        { +          GNUNET_break_op (0); +          return i; +        } +        *spec[i].details.u16 = (uint16_t) val; +      } +      break; + +    case MAJ_CMD_UINT64: +      { +        json_int_t val; + +        if (! json_is_integer (pos)) +        { +          GNUNET_break_op (0); +          return i; +        } +        val = json_integer_value (pos); +        *spec[i].details.u64 = (uint64_t) val; +      } +      break; + +    case MAJ_CMD_JSON_OBJECT: +      { +        if (! (json_is_object (pos) || json_is_array (pos)) ) +        { +          GNUNET_break_op (0); +          return i; +        } +        json_incref (pos); +        *spec[i].details.obj = pos; +      } +      break; + +    default: +      GNUNET_break (0); +      return i; +    } +  } +  return -1; /* all OK! */ +} + + +/** + * Free all elements allocated during a + * #MAJ_parse_json() operation. + * + * @param spec specification of the parse operation + * @param end number of elements in @a spec to process + */ +static void +parse_free (struct MAJ_Specification *spec, +            int end) +{ +  int i; + +  for (i=0;i<end;i++) +  { +    switch (spec[i].cmd) +    { +    case MAJ_CMD_END: +      GNUNET_assert (0); +      return; +    case MAJ_CMD_AMOUNT: +      break; +    case MAJ_CMD_TIME_ABSOLUTE: +      break; +    case MAJ_CMD_BINARY_FIXED: +      break; +    case MAJ_CMD_STRING: +      break; +    case MAJ_CMD_BINARY_VARIABLE: +      GNUNET_free (*spec[i].details.variable_data.dest_p); +      *spec[i].details.variable_data.dest_p = NULL; +      *spec[i].details.variable_data.dest_size_p = 0; +      break; +    case MAJ_CMD_RSA_PUBLIC_KEY: +      GNUNET_CRYPTO_rsa_public_key_free (*spec[i].details.rsa_public_key); +      *spec[i].details.rsa_public_key = NULL; +      break; +    case MAJ_CMD_RSA_SIGNATURE: +      GNUNET_CRYPTO_rsa_signature_free (*spec[i].details.rsa_signature); +      *spec[i].details.rsa_signature = NULL; +      break; +    case MAJ_CMD_JSON_OBJECT: +      json_decref (*spec[i].details.obj); +      *spec[i].details.obj = NULL; +      break; +    default: +      GNUNET_break (0); +      break; +    } +  } +} + + +/** + * Navigate and parse data in a JSON tree. + * + * @param root the JSON node to start the navigation at. + * @param spec parse specification array + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +int +MAJ_parse_json (const json_t *root, +                struct MAJ_Specification *spec) +{ +  int ret; + +  ret = parse_json ((json_t *) root, +                    spec); +  if (-1 == ret) +    return GNUNET_OK; +  GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +              "JSON field `%s` (%d) had unexpected value\n", +              spec[ret].field, +              ret); +  parse_free (spec, ret); +  return GNUNET_SYSERR; +} + + +/** + * Free all elements allocated during a + * #MAJ_parse_json() operation. + * + * @param spec specification of the parse operation + */ +void +MAJ_parse_free (struct MAJ_Specification *spec) +{ +  int i; + +  for (i=0;MAJ_CMD_END != spec[i].cmd;i++) ; +  parse_free (spec, i); +} + + +/** + * The expected field stores a string. + * + * @param name name of the JSON field + * @param strptr where to store a pointer to the field + */ +struct MAJ_Specification +MAJ_spec_string (const char *name, +                 const char **strptr) +{ +  struct MAJ_Specification ret = +    { +      .cmd = MAJ_CMD_STRING, +      .field = name, +      .details.strptr = strptr +    }; +  return ret; +} + + +/** + * Specification for parsing an absolute time value. + * + * @param name name of the JSON field + * @param at where to store the absolute time found under @a name + */ +struct MAJ_Specification +MAJ_spec_absolute_time (const char *name, +                        struct GNUNET_TIME_Absolute *at) +{ +  struct MAJ_Specification ret = +    { +      .cmd = MAJ_CMD_TIME_ABSOLUTE, +      .field = name, +      .details.abs_time = at +    }; +  return ret; +} + + +/** + * Specification for parsing an amount value. + * + * @param name name of the JSON field + * @param amount where to store the amount found under @a name + */ +struct MAJ_Specification +MAJ_spec_amount (const char *name, +                 struct TALER_Amount *amount) +{ +  struct MAJ_Specification ret = +    { +      .cmd = MAJ_CMD_AMOUNT, +      .field = name, +      .details.amount = amount +    }; +  return ret; +} + + +/** + * 16-bit integer. + * + * @param name name of the JSON field + * @param[out] u16 where to store the integer found under @a name + */ +struct MAJ_Specification +MAJ_spec_uint16 (const char *name, +                 uint16_t *u16) +{ +  struct MAJ_Specification ret = +    { +      .cmd = MAJ_CMD_UINT16, +      .field = name, +      .details.u16 = u16 +    }; +  return ret; +} + + +/** + * 64-bit integer. + * + * @param name name of the JSON field + * @param[out] u64 where to store the integer found under @a name + */ +struct MAJ_Specification +MAJ_spec_uint64 (const char *name, +                 uint64_t *u64) +{ +  struct MAJ_Specification ret = +    { +      .cmd = MAJ_CMD_UINT64, +      .field = name, +      .details.u64 = u64 +    }; +  return ret; +} + + +/** + * JSON object. + * + * @param name name of the JSON field + * @param[out] jsonp where to store the JSON found under @a name + */ +struct MAJ_Specification +MAJ_spec_json (const char *name, +               json_t **jsonp) +{ +  struct MAJ_Specification ret = +    { +      .cmd = MAJ_CMD_JSON_OBJECT, +      .field = name, +      .details.obj = jsonp +    }; +  return ret; +} + + +/** + * Specification for parsing an RSA public key. + * + * @param name name of the JSON field + * @param pk where to store the RSA key found under @a name + */ +struct MAJ_Specification +MAJ_spec_rsa_public_key (const char *name, +                         struct GNUNET_CRYPTO_rsa_PublicKey **pk) +{ +  struct MAJ_Specification ret = +    { +      .cmd = MAJ_CMD_RSA_PUBLIC_KEY, +      .field = name, +      .details.rsa_public_key = pk +    }; +  return ret; +} + + +/** + * Specification for parsing an RSA signature. + * + * @param name name of the JSON field + * @param sig where to store the RSA signature found under @a name + */ +struct MAJ_Specification +MAJ_spec_rsa_signature (const char *name, +                        struct GNUNET_CRYPTO_rsa_Signature **sig) +{ +  struct MAJ_Specification ret = +    { +      .cmd = MAJ_CMD_RSA_SIGNATURE, +      .field = name, +      .details.rsa_signature = sig +    }; +  return ret; +} + + +/* end of exchange_api_json.c */ diff --git a/src/exchange-lib/exchange_api_json.h b/src/exchange-lib/exchange_api_json.h new file mode 100644 index 00000000..41efcea6 --- /dev/null +++ b/src/exchange-lib/exchange_api_json.h @@ -0,0 +1,352 @@ +/* +  This file is part of TALER +  Copyright (C) 2014, 2015 GNUnet e.V. + +  TALER is free software; you can redistribute it and/or modify it under the +  terms of the GNU Affero 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 Affero General Public License for more details. + +  You should have received a copy of the GNU Affero General Public License along with +  TALER; see the file COPYING.  If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/exchange_api_json.h + * @brief functions to parse incoming requests (JSON snippets) + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include "taler_util.h" +#include <jansson.h> + + +/** + * Enumeration with the various commands for the + * #MAJ_parse_json interpreter. + */ +enum MAJ_Command +{ + +  /** +   * End of command list. +   */ +  MAJ_CMD_END, + +  /** +   * Parse amount at current position. +   */ +  MAJ_CMD_AMOUNT, + +  /** +   * Parse absolute time at current position. +   */ +  MAJ_CMD_TIME_ABSOLUTE, + +  /** +   * Parse fixed binary value at current position. +   */ +  MAJ_CMD_BINARY_FIXED, + +  /** +   * Parse variable-size binary value at current position. +   */ +  MAJ_CMD_BINARY_VARIABLE, + +  /** +   * Parse RSA public key at current position. +   */ +  MAJ_CMD_RSA_PUBLIC_KEY, + +  /** +   * Parse RSA signature at current position. +   */ +  MAJ_CMD_RSA_SIGNATURE, + +  /** +   * Parse `const char *` JSON string at current position. +   */ +  MAJ_CMD_STRING, + +  /** +   * Parse `uint16_t` integer at the current position. +   */ +  MAJ_CMD_UINT16, + +  /** +   * Parse `uint64_t` integer at the current position. +   */ +  MAJ_CMD_UINT64, + +  /** +   * Parse JSON object at the current position. +   */ +  MAJ_CMD_JSON_OBJECT, + +  /** +   * Parse ??? at current position. +   */ +  MAJ_CMD_C + +}; + + +/** + * @brief Entry in parser specification for #MAJ_parse_json. + */ +struct MAJ_Specification +{ + +  /** +   * Command to execute. +   */ +  enum MAJ_Command cmd; + +  /** +   * Name of the field to access. +   */ +  const char *field; + +  /** +   * Further details for the command. +   */ +  union { + +    /** +     * Where to store amount for #MAJ_CMD_AMOUNT. +     */ +    struct TALER_Amount *amount; + +    /** +     * Where to store time, for #MAJ_CMD_TIME_ABSOLUTE. +     */ +    struct GNUNET_TIME_Absolute *abs_time; + +    /** +     * Where to write binary data, for #MAJ_CMD_BINARY_FIXED. +     */ +    struct { +      /** +       * Where to write the data. +       */ +      void *dest; + +      /** +       * How many bytes to write to @e dest. +       */ +      size_t dest_size; + +    } fixed_data; + +    /** +     * Where to write binary data, for #MAJ_CMD_BINARY_VARIABLE. +     */ +    struct { +      /** +       * Where to store the pointer with the data (is allocated). +       */ +      void **dest_p; + +      /** +       * Where to store the number of bytes allocated at `*dest`. +       */ +      size_t *dest_size_p; + +    } variable_data; + +    /** +     * Where to store the RSA public key for #MAJ_CMD_RSA_PUBLIC_KEY +     */ +    struct GNUNET_CRYPTO_rsa_PublicKey **rsa_public_key; + +    /** +     * Where to store the RSA signature for #MAJ_CMD_RSA_SIGNATURE +     */ +    struct GNUNET_CRYPTO_rsa_Signature **rsa_signature; + +    /** +     * Details for #MAJ_CMD_EDDSA_SIGNATURE +     */ +    struct { + +      /** +       * Where to store the purpose. +       */ +      struct GNUNET_CRYPTO_EccSignaturePurpose **purpose_p; + +      /** +       * Key to verify the signature against. +       */ +      const struct GNUNET_CRYPTO_EddsaPublicKey *pub_key; + +    } eddsa_signature; + +    /** +     * Where to store a pointer to the string. +     */ +    const char **strptr; + +    /** +     * Where to store 16-bit integer. +     */ +    uint16_t *u16; + +    /** +     * Where to store 64-bit integer. +     */ +    uint64_t *u64; + +    /** +     * Where to store a JSON object. +     */ +    json_t **obj; + +  } details; + +}; + + +/** + * Navigate and parse data in a JSON tree. + * + * @param root the JSON node to start the navigation at. + * @param spec parse specification array + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +int +MAJ_parse_json (const json_t *root, +                struct MAJ_Specification *spec); + + +/** + * Free all elements allocated during a + * #MAJ_parse_json() operation. + * + * @param spec specification of the parse operation + */ +void +MAJ_parse_free (struct MAJ_Specification *spec); + + +/** + * End of a parser specification. + */ +#define MAJ_spec_end { .cmd = MAJ_CMD_END } + +/** + * Fixed size object (in network byte order, encoded using Crockford + * Base32hex encoding). + * + * @param name name of the JSON field + * @param obj pointer where to write the data (type of `*obj` will determine size) + */ +#define MAJ_spec_fixed_auto(name,obj) { .cmd = MAJ_CMD_BINARY_FIXED, .field = name, .details.fixed_data.dest = obj, .details.fixed_data.dest_size = sizeof (*obj) } + + +/** + * Variable size object (in network byte order, encoded using Crockford + * Base32hex encoding). + * + * @param name name of the JSON field + * @param obj pointer where to write the data (a `void **`) + * @param size where to store the number of bytes allocated for @a obj (of type `size_t *` + */ +#define MAJ_spec_varsize(name,obj,size) { .cmd = MAJ_CMD_BINARY_VARIABLE, .field = name, .details.variable_data.dest_p = obj, .details.variable_data.dest_size_p = size } + + +/** + * The expected field stores a string. + * + * @param name name of the JSON field + * @param strptr where to store a pointer to the field + */ +struct MAJ_Specification +MAJ_spec_string (const char *name, +                 const char **strptr); + + +/** + * Absolute time. + * + * @param name name of the JSON field + * @param[out] at where to store the absolute time found under @a name + */ +struct MAJ_Specification +MAJ_spec_absolute_time (const char *name, +                        struct GNUNET_TIME_Absolute *at); + + +/** + * 16-bit integer. + * + * @param name name of the JSON field + * @param[out] u16 where to store the integer found under @a name + */ +struct MAJ_Specification +MAJ_spec_uint16 (const char *name, +                 uint16_t *u16); + + +/** + * 64-bit integer. + * + * @param name name of the JSON field + * @param[out] u64 where to store the integer found under @a name + */ +struct MAJ_Specification +MAJ_spec_uint64 (const char *name, +                 uint64_t *u64); + + +/** + * JSON object. + * + * @param name name of the JSON field + * @param[out] jsonp where to store the JSON found under @a name + */ +struct MAJ_Specification +MAJ_spec_json (const char *name, +               json_t **jsonp); + + +/** + * Specification for parsing an amount value. + * + * @param name name of the JSON field + * @param amount where to store the amount under @a name + */ +struct MAJ_Specification +MAJ_spec_amount (const char *name, +                 struct TALER_Amount *amount); + + +/** + * Specification for parsing an RSA public key. + * + * @param name name of the JSON field + * @param pk where to store the RSA key found under @a name + */ +struct MAJ_Specification +MAJ_spec_rsa_public_key (const char *name, +                         struct GNUNET_CRYPTO_rsa_PublicKey **pk); + + +/** + * Specification for parsing an RSA signature. + * + * @param name name of the JSON field + * @param sig where to store the RSA signature found under @a name + */ +struct MAJ_Specification +MAJ_spec_rsa_signature (const char *name, +                        struct GNUNET_CRYPTO_rsa_Signature **sig); + + + + +/* end of exchange_api_json.h */ diff --git a/src/exchange-lib/exchange_api_refresh.c b/src/exchange-lib/exchange_api_refresh.c new file mode 100644 index 00000000..03d59ea3 --- /dev/null +++ b/src/exchange-lib/exchange_api_refresh.c @@ -0,0 +1,2061 @@ +/* +  This file is part of TALER +  Copyright (C) 2015 GNUnet e.V. + +  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, If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/exchange_api_refresh.c + * @brief Implementation of the /refresh/melt+reveal requests of the exchange's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include "taler_exchange_service.h" +#include "exchange_api_common.h" +#include "exchange_api_json.h" +#include "exchange_api_context.h" +#include "exchange_api_handle.h" +#include "taler_signatures.h" + + +/* ********************* /refresh/ common ***************************** */ + +/* structures for committing refresh data to disk before doing the +   network interaction(s) */ + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Header of serialized information about a coin we are melting. + */ +struct MeltedCoinP +{ +  /** +   * Private key of the coin. +   */ +  struct TALER_CoinSpendPrivateKeyP coin_priv; + +  /** +   * Amount this coin contributes to the melt, including fee. +   */ +  struct TALER_AmountNBO melt_amount_with_fee; + +  /** +   * The applicable fee for withdrawing a coin of this denomination +   */ +  struct TALER_AmountNBO fee_melt; + +  /** +   * The original value of the coin. +   */ +  struct TALER_AmountNBO original_value; + +  /** +   * Transfer private keys for each cut-and-choose dimension. +   */ +  struct TALER_TransferPrivateKeyP transfer_priv[TALER_CNC_KAPPA]; + +  /** +   * Timestamp indicating when coins of this denomination become invalid. +   */ +  struct GNUNET_TIME_AbsoluteNBO deposit_valid_until; + +  /** +   * Size of the encoded public key that follows. +   */ +  uint16_t pbuf_size; + +  /** +   * Size of the encoded signature that follows. +   */ +  uint16_t sbuf_size; + +  /* Followed by serializations of: +     1) struct TALER_DenominationPublicKey pub_key; +     2) struct TALER_DenominationSignature sig; +  */ +}; + + +/** + * Header for serializations of coin-specific information about the + * fresh coins we generate during a melt. + */ +struct FreshCoinP +{ + +  /** +   * Private key of the coin. +   */ +  struct TALER_CoinSpendPrivateKeyP coin_priv; + +  /** +   * Size of the encoded blinding key that follows. +   */ +  uint32_t bbuf_size; + +  /* Followed by serialization of: +     - struct TALER_DenominationBlindingKey blinding_key; +  */ + +}; + + +/** + * Header of serialized data about a melt operation, suitable for + * persisting it on disk. + */ +struct MeltDataP +{ + +  /** +   * Hash over the melting session. +   */ +  struct GNUNET_HashCode melt_session_hash; + +  /** +   * Link secret used to encrypt the @a coin_priv and the blinding +   * key in the linkage data for the respective cut-and-choose dimension. +   */ +  struct TALER_LinkSecretP link_secrets[TALER_CNC_KAPPA]; + +  /** +   * Number of coins we are melting, in NBO +   */ +  uint16_t num_melted_coins GNUNET_PACKED; + +  /** +   * Number of coins we are creating, in NBO +   */ +  uint16_t num_fresh_coins GNUNET_PACKED; + +  /* Followed by serializations of: +     1) struct MeltedCoinP melted_coins[num_melted_coins]; +     2) struct TALER_EXCHANGE_DenomPublicKey fresh_pks[num_fresh_coins]; +     3) TALER_CNC_KAPPA times: +        3a) struct FreshCoinP fresh_coins[num_fresh_coins]; +  */ +}; + + +GNUNET_NETWORK_STRUCT_END + + +/** + * Information about a coin we are melting. + */ +struct MeltedCoin +{ +  /** +   * Private key of the coin. +   */ +  struct TALER_CoinSpendPrivateKeyP coin_priv; + +  /** +   * Amount this coin contributes to the melt, including fee. +   */ +  struct TALER_Amount melt_amount_with_fee; + +  /** +   * The applicable fee for melting a coin of this denomination +   */ +  struct TALER_Amount fee_melt; + +  /** +   * The original value of the coin. +   */ +  struct TALER_Amount original_value; + +  /** +   * Transfer private keys for each cut-and-choose dimension. +   */ +  struct TALER_TransferPrivateKeyP transfer_priv[TALER_CNC_KAPPA]; + +  /** +   * Timestamp indicating when coins of this denomination become invalid. +   */ +  struct GNUNET_TIME_Absolute deposit_valid_until; + +  /** +   * Denomination key of the original coin. +   */ +  struct TALER_DenominationPublicKey pub_key; + +  /** +   * Exchange's signature over the coin. +   */ +  struct TALER_DenominationSignature sig; + +}; + + +/** + * Coin-specific information about the fresh coins we generate during + * a melt. + */ +struct FreshCoin +{ + +  /** +   * Private key of the coin. +   */ +  struct TALER_CoinSpendPrivateKeyP coin_priv; + +  /** +   * Blinding key used for blinding during blind signing. +   */ +  struct TALER_DenominationBlindingKey blinding_key; + +}; + + +/** + * Melt data in non-serialized format for convenient processing. + */ +struct MeltData +{ + +  /** +   * Hash over the melting session. +   */ +  struct GNUNET_HashCode melt_session_hash; + +  /** +   * Link secrets for each cut-and-choose dimension. +   */ +  struct TALER_LinkSecretP link_secrets[TALER_CNC_KAPPA]; + +  /** +   * Number of coins we are melting +   */ +  uint16_t num_melted_coins; + +  /** +   * Number of coins we are creating +   */ +  uint16_t num_fresh_coins; + +  /** +   * Information about the melted coins in an array of length @e +   * num_melted_coins. +   */ +  struct MeltedCoin *melted_coins; + +  /** +   * Array of @e num_fresh_coins denomination keys for the coins to be +   * freshly exchangeed. +   */ +  struct TALER_DenominationPublicKey *fresh_pks; + +  /** +   * Arrays of @e num_fresh_coins with information about the fresh +   * coins to be created, for each cut-and-choose dimension. +   */ +  struct FreshCoin *fresh_coins[TALER_CNC_KAPPA]; +}; + + +/** + * Free all information associated with a melted coin session. + * + * @param mc melted coin to release, the pointer itself is NOT + *           freed (as it is typically not allocated by itself) + */ +static void +free_melted_coin (struct MeltedCoin *mc) +{ +  if (NULL == mc) +    return; +  if (NULL != mc->pub_key.rsa_public_key) +    GNUNET_CRYPTO_rsa_public_key_free (mc->pub_key.rsa_public_key); +  if (NULL != mc->sig.rsa_signature) +    GNUNET_CRYPTO_rsa_signature_free (mc->sig.rsa_signature); +} + + +/** + * Free all information associated with a fresh coin. + * + * @param fc fresh coin to release, the pointer itself is NOT + *           freed (as it is typically not allocated by itself) + */ +static void +free_fresh_coin (struct FreshCoin *fc) +{ +  if (NULL == fc) +    return; +  if (NULL != fc->blinding_key.rsa_blinding_key) +    GNUNET_CRYPTO_rsa_blinding_key_free (fc->blinding_key.rsa_blinding_key); +} + + +/** + * Free all information associated with a melting session.  Note + * that we allow the melting session to be only partially initialized, + * as we use this function also when freeing melt data that was not + * fully initialized (i.e. due to failures in #deserialize_melt_data()). + * + * @param md melting data to release, the pointer itself is NOT + *           freed (as it is typically not allocated by itself) + */ +static void +free_melt_data (struct MeltData *md) +{ +  unsigned int i; +  unsigned int j; + +  if (NULL != md->melted_coins) +  { +    for (i=0;i<md->num_melted_coins;i++) +      free_melted_coin (&md->melted_coins[i]); +    GNUNET_free (md->melted_coins); +  } +  if (NULL != md->fresh_pks) +  { +    for (i=0;i<md->num_fresh_coins;i++) +      if (NULL != md->fresh_pks[i].rsa_public_key) +        GNUNET_CRYPTO_rsa_public_key_free (md->fresh_pks[i].rsa_public_key); +    GNUNET_free (md->fresh_pks); +  } + +  for (i=0;i<TALER_CNC_KAPPA;i++) +  { +    for (j=0;j<md->num_fresh_coins;j++) +      free_fresh_coin (&md->fresh_coins[i][j]); +    GNUNET_free (md->fresh_coins[i]); +  } +  /* Finally, clean up a bit... +     (NOTE: compilers might optimize this away, so this is +     not providing any strong assurances that the key material +     is purged.) */ +  memset (md, +          0, +          sizeof (struct MeltData)); +} + + +/** + * Serialize information about a coin we are melting. + * + * @param mc information to serialize + * @param buf buffer to write data in, NULL to just compute + *            required size + * @param off offeset at @a buf to use + * @return number of bytes written to @a buf at @a off, or if + *        @a buf is NULL, number of bytes required; 0 on error + */ +static size_t +serialize_melted_coin (const struct MeltedCoin *mc, +                       char *buf, +                       size_t off) +{ +  struct MeltedCoinP mcp; +  unsigned int i; +  char *pbuf; +  size_t pbuf_size; +  char *sbuf; +  size_t sbuf_size; + +  sbuf_size = GNUNET_CRYPTO_rsa_signature_encode (mc->sig.rsa_signature, +                                                  &sbuf); +  pbuf_size = GNUNET_CRYPTO_rsa_public_key_encode (mc->pub_key.rsa_public_key, +                                                   &pbuf); +  if (NULL == buf) +  { +    GNUNET_free (sbuf); +    GNUNET_free (pbuf); +    return sizeof (struct MeltedCoinP) + sbuf_size + pbuf_size; +  } +  if ( (sbuf_size > UINT16_MAX) || +       (pbuf_size > UINT16_MAX) ) +  { +    GNUNET_break (0); +    return 0; +  } +  mcp.coin_priv = mc->coin_priv; +  TALER_amount_hton (&mcp.melt_amount_with_fee, +                     &mc->melt_amount_with_fee); +  TALER_amount_hton (&mcp.fee_melt, +                     &mc->fee_melt); +  TALER_amount_hton (&mcp.original_value, +                     &mc->original_value); +  for (i=0;i<TALER_CNC_KAPPA;i++) +    mcp.transfer_priv[i] = mc->transfer_priv[i]; +  mcp.deposit_valid_until = GNUNET_TIME_absolute_hton (mc->deposit_valid_until); +  mcp.pbuf_size = htons ((uint16_t) pbuf_size); +  mcp.sbuf_size = htons ((uint16_t) sbuf_size); +  memcpy (&buf[off], +          &mcp, +          sizeof (struct MeltedCoinP)); +  memcpy (&buf[off + sizeof (struct MeltedCoinP)], +          pbuf, +          pbuf_size); +  memcpy (&buf[off + sizeof (struct MeltedCoinP) + pbuf_size], +          sbuf, +          sbuf_size); +  GNUNET_free (sbuf); +  GNUNET_free (pbuf); +  return sizeof (struct MeltedCoinP) + sbuf_size + pbuf_size; +} + + +/** + * Deserialize information about a coin we are melting. + * + * @param[out] mc information to deserialize + * @param buf buffer to read data from + * @param size number of bytes available at @a buf to use + * @param[out] ok set to #GNUNET_NO to report errors + * @return number of bytes read from @a buf, 0 on error + */ +static size_t +deserialize_melted_coin (struct MeltedCoin *mc, +                         const char *buf, +                         size_t size, +                         int *ok) +{ +  struct MeltedCoinP mcp; +  unsigned int i; +  size_t pbuf_size; +  size_t sbuf_size; +  size_t off; + +  if (size < sizeof (struct MeltedCoinP)) +  { +    GNUNET_break (0); +    *ok = GNUNET_NO; +    return 0; +  } +  memcpy (&mcp, +          buf, +          sizeof (struct MeltedCoinP)); +  pbuf_size = ntohs (mcp.pbuf_size); +  sbuf_size = ntohs (mcp.sbuf_size); +  if (size < sizeof (struct MeltedCoinP) + pbuf_size + sbuf_size) +  { +    GNUNET_break (0); +    *ok = GNUNET_NO; +    return 0; +  } +  off = sizeof (struct MeltedCoinP); +  mc->pub_key.rsa_public_key +    = GNUNET_CRYPTO_rsa_public_key_decode (&buf[off], +                                           pbuf_size); +  off += pbuf_size; +  mc->sig.rsa_signature +    = GNUNET_CRYPTO_rsa_signature_decode (&buf[off], +                                          sbuf_size); +  off += sbuf_size; +  if ( (NULL == mc->pub_key.rsa_public_key) || +       (NULL == mc->sig.rsa_signature) ) +  { +    GNUNET_break (0); +    *ok = GNUNET_NO; +    return 0; +  } + +  mc->coin_priv = mcp.coin_priv; +  TALER_amount_ntoh (&mc->melt_amount_with_fee, +                     &mcp.melt_amount_with_fee); +  TALER_amount_ntoh (&mc->fee_melt, +                     &mcp.fee_melt); +  TALER_amount_ntoh (&mc->original_value, +                     &mcp.original_value); +  for (i=0;i<TALER_CNC_KAPPA;i++) +    mc->transfer_priv[i] = mcp.transfer_priv[i]; +  mc->deposit_valid_until = GNUNET_TIME_absolute_ntoh (mcp.deposit_valid_until); +  return off; +} + + +/** + * Serialize information about a denomination key. + * + * @param dk information to serialize + * @param buf buffer to write data in, NULL to just compute + *            required size + * @param off offeset at @a buf to use + * @return number of bytes written to @a buf at @a off, or if + *        @a buf is NULL, number of bytes required + */ +static size_t +serialize_denomination_key (const struct TALER_DenominationPublicKey *dk, +                            char *buf, +                            size_t off) +{ +  char *pbuf; +  size_t pbuf_size; +  uint32_t be; + +  pbuf_size = GNUNET_CRYPTO_rsa_public_key_encode (dk->rsa_public_key, +                                                   &pbuf); +  if (NULL == buf) +  { +    GNUNET_free (pbuf); +    return pbuf_size + sizeof (uint32_t); +  } +  be = htonl ((uint32_t) pbuf_size); +  memcpy (&buf[off], +          &be, +          sizeof (uint32_t)); +  memcpy (&buf[off + sizeof (uint32_t)], +          pbuf, +          pbuf_size); +  GNUNET_free (pbuf); +  return pbuf_size + sizeof (uint32_t); +} + + +/** + * Deserialize information about a denomination key. + * + * @param[out] dk information to deserialize + * @param buf buffer to read data from + * @param size number of bytes available at @a buf to use + * @param[out] ok set to #GNUNET_NO to report errors + * @return number of bytes read from @a buf, 0 on error + */ +static size_t +deserialize_denomination_key (struct TALER_DenominationPublicKey *dk, +                              const char *buf, +                              size_t size, +                              int *ok) +{ +  size_t pbuf_size; +  uint32_t be; + +  if (size < sizeof (uint32_t)) +  { +    GNUNET_break (0); +    *ok = GNUNET_NO; +    return 0; +  } +  memcpy (&be, +          buf, +          sizeof (uint32_t)); +  pbuf_size = ntohl (be); +  if (size < sizeof (uint32_t) + pbuf_size) +  { +    GNUNET_break (0); +    *ok = GNUNET_NO; +    return 0; +  } +  dk->rsa_public_key +    = GNUNET_CRYPTO_rsa_public_key_decode (&buf[sizeof (uint32_t)], +                                           pbuf_size); + +  if (NULL == dk->rsa_public_key) +  { +    GNUNET_break (0); +    *ok = GNUNET_NO; +    return 0; +  } +  return sizeof (uint32_t) + pbuf_size; +} + + +/** + * Serialize information about a fresh coin we are generating. + * + * @param fc information to serialize + * @param buf buffer to write data in, NULL to just compute + *            required size + * @param off offeset at @a buf to use + * @return number of bytes written to @a buf at @a off, or if + *        @a buf is NULL, number of bytes required + */ +static size_t +serialize_fresh_coin (const struct FreshCoin *fc, +                      char *buf, +                      size_t off) +{ +  struct FreshCoinP fcp; +  char *bbuf; +  size_t bbuf_size; + +  bbuf_size = GNUNET_CRYPTO_rsa_blinding_key_encode (fc->blinding_key.rsa_blinding_key, +                                                     &bbuf); +  if (NULL == buf) +  { +    GNUNET_free (bbuf); +    return sizeof (struct FreshCoinP) + bbuf_size; +  } +  fcp.coin_priv = fc->coin_priv; +  fcp.bbuf_size = htonl ((uint32_t) bbuf_size); +  memcpy (&buf[off], +          &fcp, +          sizeof (struct FreshCoinP)); +  memcpy (&buf[off + sizeof (struct FreshCoinP)], +          bbuf, +          bbuf_size); +  GNUNET_free (bbuf); +  return sizeof (struct FreshCoinP) + bbuf_size; +} + + +/** + * Deserialize information about a fresh coin we are generating. + * + * @param[out] fc information to deserialize + * @param buf buffer to read data from + * @param size number of bytes available at @a buf to use + * @param[out] ok set to #GNUNET_NO to report errors + * @return number of bytes read from @a buf, 0 on error + */ +static size_t +deserialize_fresh_coin (struct FreshCoin *fc, +                        const char *buf, +                        size_t size, +                        int *ok) +{ +  struct FreshCoinP fcp; +  size_t bbuf_size; + +  if (size < sizeof (struct FreshCoinP)) +  { +    GNUNET_break (0); +    *ok = GNUNET_NO; +    return 0; +  } +  memcpy (&fcp, +          buf, +          sizeof (struct FreshCoinP)); +  bbuf_size = ntohl (fcp.bbuf_size); +  if (size < sizeof (struct FreshCoinP) + bbuf_size) +  { +    GNUNET_break (0); +    *ok = GNUNET_NO; +    return 0; +  } +  fc->blinding_key.rsa_blinding_key +    = GNUNET_CRYPTO_rsa_blinding_key_decode (&buf[sizeof (struct FreshCoinP)], +                                             bbuf_size); +  if (NULL == fc->blinding_key.rsa_blinding_key) +  { +    GNUNET_break (0); +    *ok = GNUNET_NO; +    return 0; +  } +  fc->coin_priv = fcp.coin_priv; +  return sizeof (struct FreshCoinP) + bbuf_size; +} + + +/** + * Serialize melt data. + * + * @param md data to serialize + * @param[out] res_size size of buffer returned + * @return serialized melt data + */ +static char * +serialize_melt_data (const struct MeltData *md, +                     size_t *res_size) +{ +  size_t size; +  size_t asize; +  char *buf; +  unsigned int i; +  unsigned int j; + +  size = 0; +  asize = (size_t) -1; /* make the compiler happy */ +  buf = NULL; +  /* we do 2 iterations, #1 to determine total size, #2 to +     actually construct the buffer */ +  do { +    if (0 == size) +    { +      size = sizeof (struct MeltDataP); +    } +    else +    { +      struct MeltDataP *mdp; + +      buf = GNUNET_malloc (size); +      asize = size; /* just for invariant check later */ +      size = sizeof (struct MeltDataP); +      mdp = (struct MeltDataP *) buf; +      mdp->melt_session_hash = md->melt_session_hash; +      for (i=0;i<TALER_CNC_KAPPA;i++) +        mdp->link_secrets[i] = md->link_secrets[i]; +      mdp->num_melted_coins = htons (md->num_melted_coins); +      mdp->num_fresh_coins = htons (md->num_fresh_coins); +    } +    for (i=0;i<md->num_melted_coins;i++) +      size += serialize_melted_coin (&md->melted_coins[i], +                                     buf, +                                     size); +    for (i=0;i<md->num_fresh_coins;i++) +      size += serialize_denomination_key (&md->fresh_pks[i], +                                          buf, +                                          size); +    for (i=0;i<TALER_CNC_KAPPA;i++) +      for(j=0;j<md->num_fresh_coins;j++) +        size += serialize_fresh_coin (&md->fresh_coins[i][j], +                                      buf, +                                      size); +  } while (NULL == buf); +  GNUNET_assert (size == asize); +  *res_size = size; +  return buf; +} + + +/** + * Deserialize melt data. + * + * @param buf serialized data + * @param buf_size size of @a buf + * @return deserialized melt data, NULL on error + */ +static struct MeltData * +deserialize_melt_data (const char *buf, +                       size_t buf_size) +{ +  struct MeltData *md; +  struct MeltDataP mdp; +  unsigned int i; +  unsigned int j; +  size_t off; +  int ok; + +  if (buf_size < sizeof (struct MeltDataP)) +    return NULL; +  memcpy (&mdp, +          buf, +          sizeof (struct MeltDataP)); +  md = GNUNET_new (struct MeltData); +  md->melt_session_hash = mdp.melt_session_hash; +  for (i=0;i<TALER_CNC_KAPPA;i++) +    md->link_secrets[i] = mdp.link_secrets[i]; +  md->num_melted_coins = ntohs (mdp.num_melted_coins); +  md->num_fresh_coins = ntohs (mdp.num_fresh_coins); +  md->melted_coins = GNUNET_new_array (md->num_melted_coins, +                                       struct MeltedCoin); +  md->fresh_pks = GNUNET_new_array (md->num_fresh_coins, +                                    struct TALER_DenominationPublicKey); +  for (i=0;i<TALER_CNC_KAPPA;i++) +    md->fresh_coins[i] = GNUNET_new_array (md->num_fresh_coins, +                                           struct FreshCoin); +  off = sizeof (struct MeltDataP); +  ok = GNUNET_YES; +  for (i=0;(i<md->num_melted_coins)&&(GNUNET_YES == ok);i++) +    off += deserialize_melted_coin (&md->melted_coins[i], +                                    &buf[off], +                                    buf_size - off, +                                    &ok); +  for (i=0;(i<md->num_fresh_coins)&&(GNUNET_YES == ok);i++) +    off += deserialize_denomination_key (&md->fresh_pks[i], +                                         &buf[off], +                                         buf_size - off, +                                         &ok); + +  for (i=0;i<TALER_CNC_KAPPA;i++) +    for(j=0;(j<md->num_fresh_coins)&&(GNUNET_YES == ok);j++) +      off += deserialize_fresh_coin (&md->fresh_coins[i][j], +                                     &buf[off], +                                     buf_size - off, +                                     &ok); +  if (off != buf_size) +  { +    GNUNET_break (0); +    ok = GNUNET_NO; +  } +  if (GNUNET_YES != ok) +  { +    free_melt_data (md); +    GNUNET_free (md); +    return NULL; +  } +  return md; +} + + +/** + * Setup information for a fresh coin. + * + * @param[out] fc value to initialize + * @param pk denomination information for the fresh coin + */ +static void +setup_fresh_coin (struct FreshCoin *fc, +                  const struct TALER_EXCHANGE_DenomPublicKey *pk) +{ +  struct GNUNET_CRYPTO_EddsaPrivateKey *epk; +  unsigned int len; + +  epk = GNUNET_CRYPTO_eddsa_key_create (); +  fc->coin_priv.eddsa_priv = *epk; +  GNUNET_free (epk); +  len = GNUNET_CRYPTO_rsa_public_key_len (pk->key.rsa_public_key); +  fc->blinding_key.rsa_blinding_key +    = GNUNET_CRYPTO_rsa_blinding_key_create (len); +} + + +/** + * Melt (partially spent) coins to obtain fresh coins that are + * unlinkable to the original coin(s).  Note that melting more + * than one coin in a single request will make those coins linkable, + * so the safest operation only melts one coin at a time. + * + * This API is typically used by a wallet.  Note that to ensure that + * no money is lost in case of hardware failures, is operation does + * not actually initiate the request. Instead, it generates a buffer + * which the caller must store before proceeding with the actual call + * to #TALER_EXCHANGE_refresh_melt() that will generate the request. + * + * This function does verify that the given request data is internally + * consistent.  However, the @a melts_sigs are only verified if + * @a check_sigs is set to #GNUNET_YES, as this may be relatively + * expensive and should be redundant. + * + * Aside from some non-trivial cryptographic operations that might + * take a bit of CPU time to complete, this function returns + * its result immediately and does not start any asynchronous + * processing.  This function is also thread-safe. + * + * @param num_melts number of coins that are being melted (typically 1) + * @param melt_privs array of @a num_melts private keys of the coins to melt + * @param melt_amounts array of @a num_melts amounts specifying how much + *                     each coin will contribute to the melt (including fee) + * @param melt_sigs array of @a num_melts signatures affirming the + *                   validity of the public keys corresponding to the + *                   @a melt_privs private keys + * @param melt_pks array of @a num_melts denomination key information + *                   records corresponding to the @a melt_sigs + *                   validity of the keys + * @param check_sigs verify the validity of the signatures of @a melt_sigs + * @param fresh_pks_len length of the @a pks array + * @param fresh_pks array of @a pks_len denominations of fresh coins to create + * @param[out] res_size set to the size of the return value, or 0 on error + * @return NULL + *         if the inputs are invalid (i.e. denomination key not with this exchange). + *         Otherwise, pointer to a buffer of @a res_size to store persistently + *         before proceeding to #TALER_EXCHANGE_refresh_melt(). + *         Non-null results should be freed using #GNUNET_free(). + */ +char * +TALER_EXCHANGE_refresh_prepare (unsigned int num_melts, +                            const struct TALER_CoinSpendPrivateKeyP *melt_privs, +                            const struct TALER_Amount *melt_amounts, +                            const struct TALER_DenominationSignature *melt_sigs, +                            const struct TALER_EXCHANGE_DenomPublicKey *melt_pks, +                            int check_sigs, +                            unsigned int fresh_pks_len, +                            const struct TALER_EXCHANGE_DenomPublicKey *fresh_pks, +                            size_t *res_size) +{ +  struct MeltData md; +  char *buf; +  unsigned int i; +  unsigned int j; +  struct GNUNET_HashContext *hash_context; + +  /* build up melt data structure */ +  for (i=0;i<TALER_CNC_KAPPA;i++) +    GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG, +                                &md.link_secrets[i], +                                sizeof (struct TALER_LinkSecretP)); +  md.num_melted_coins = num_melts; +  md.num_fresh_coins = fresh_pks_len; +  md.melted_coins = GNUNET_new_array (num_melts, +                                      struct MeltedCoin); +  for (i=0;i<num_melts;i++) +  { +    md.melted_coins[i].coin_priv = melt_privs[i]; +    md.melted_coins[i].melt_amount_with_fee = melt_amounts[i]; +    md.melted_coins[i].fee_melt = melt_pks[i].fee_refresh; +    md.melted_coins[i].original_value = melt_pks[i].value; +    for (j=0;j<TALER_CNC_KAPPA;j++) +    { +      struct GNUNET_CRYPTO_EcdhePrivateKey *tpk; + +      tpk = GNUNET_CRYPTO_ecdhe_key_create (); +      md.melted_coins[i].transfer_priv[j].ecdhe_priv = *tpk; +      GNUNET_free (tpk); +    } +    md.melted_coins[i].deposit_valid_until +      = melt_pks[i].deposit_valid_until; +    md.melted_coins[i].pub_key.rsa_public_key +      = GNUNET_CRYPTO_rsa_public_key_dup (melt_pks[i].key.rsa_public_key); +    md.melted_coins[i].sig.rsa_signature +      = GNUNET_CRYPTO_rsa_signature_dup (melt_sigs[i].rsa_signature); +  } +  md.fresh_pks = GNUNET_new_array (fresh_pks_len, +                                   struct TALER_DenominationPublicKey); +  for (i=0;i<fresh_pks_len;i++) +    md.fresh_pks[i].rsa_public_key +      = GNUNET_CRYPTO_rsa_public_key_dup (fresh_pks[i].key.rsa_public_key); +  for (i=0;i<TALER_CNC_KAPPA;i++) +  { +    md.fresh_coins[i] = GNUNET_new_array (fresh_pks_len, +                                          struct FreshCoin); +    for (j=0;j<fresh_pks_len;j++) +      setup_fresh_coin (&md.fresh_coins[i][j], +                        &fresh_pks[j]); +  } + +  /* now compute melt session hash */ +  hash_context = GNUNET_CRYPTO_hash_context_start (); +  for (i=0;i<fresh_pks_len;i++) +  { +    char *buf; +    size_t buf_size; + +    buf_size = GNUNET_CRYPTO_rsa_public_key_encode (fresh_pks[i].key.rsa_public_key, +                                                    &buf); +    GNUNET_CRYPTO_hash_context_read (hash_context, +                                     buf, +                                     buf_size); +    GNUNET_free (buf); +  } +  for (i=0;i<num_melts;i++) +  { +    struct TALER_CoinSpendPublicKeyP coin_pub; +    struct TALER_AmountNBO melt_amount; + +    GNUNET_CRYPTO_eddsa_key_get_public (&melt_privs[i].eddsa_priv, +                                        &coin_pub.eddsa_pub); +    GNUNET_CRYPTO_hash_context_read (hash_context, +                                     &coin_pub, +                                     sizeof (struct TALER_CoinSpendPublicKeyP)); +    TALER_amount_hton (&melt_amount, +                       &melt_amounts[i]); +    GNUNET_CRYPTO_hash_context_read (hash_context, +                                     &melt_amount, +                                     sizeof (struct TALER_AmountNBO)); + +  } +  for (i = 0; i < TALER_CNC_KAPPA; i++) +  { +    for (j = 0; j < fresh_pks_len; j++) +    { +      const struct FreshCoin *fc; /* coin this is about */ +      struct TALER_CoinSpendPublicKeyP coin_pub; +      struct GNUNET_HashCode coin_hash; +      char *coin_ev; /* blinded message to be signed (in envelope) for each coin */ +      size_t coin_ev_size; +      struct TALER_RefreshLinkDecrypted rld; +      struct TALER_RefreshLinkEncrypted *rle; +      char *link_enc; /* encrypted link data */ +      size_t link_enc_size; + +      fc = &md.fresh_coins[i][j]; +      GNUNET_CRYPTO_eddsa_key_get_public (&fc->coin_priv.eddsa_priv, +                                          &coin_pub.eddsa_pub); +      GNUNET_CRYPTO_hash (&coin_pub.eddsa_pub, +                          sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey), +                          &coin_hash); +      coin_ev_size = GNUNET_CRYPTO_rsa_blind (&coin_hash, +                                              fc->blinding_key.rsa_blinding_key, +                                              md.fresh_pks[j].rsa_public_key, +                                              &coin_ev); +      GNUNET_CRYPTO_hash_context_read (hash_context, +                                       coin_ev, +                                       coin_ev_size); +      GNUNET_free (coin_ev); + +      rld.coin_priv = fc->coin_priv; +      rld.blinding_key = fc->blinding_key; +      rle = TALER_refresh_encrypt (&rld, +                                   &md.link_secrets[i]); +      link_enc = TALER_refresh_link_encrypted_encode (rle, +                                                      &link_enc_size); + +      GNUNET_CRYPTO_hash_context_read (hash_context, +                                       link_enc, +                                       link_enc_size); +      GNUNET_free (link_enc); +    } +  } +  for (i = 0; i < TALER_CNC_KAPPA; i++) +  { +    for (j = 0; j < num_melts; j++) +    { +      struct TALER_RefreshCommitLinkP rcl; +      struct TALER_TransferSecretP trans_sec; + +      GNUNET_CRYPTO_ecdhe_key_get_public (&md.melted_coins[j].transfer_priv[i].ecdhe_priv, +                                          &rcl.transfer_pub.ecdhe_pub); +      TALER_link_derive_transfer_secret  (&melt_privs[j], +                                          &md.melted_coins[j].transfer_priv[i], +                                          &trans_sec); +      TALER_transfer_encrypt (&md.link_secrets[i], +                              &trans_sec, +                              &rcl.shared_secret_enc); +      GNUNET_CRYPTO_hash_context_read (hash_context, +                                       &rcl, +                                       sizeof (struct TALER_RefreshCommitLinkP)); +    } +  } + +  GNUNET_CRYPTO_hash_context_finish (hash_context, +                                     &md.melt_session_hash); + +  /* finally, serialize everything */ +  buf = serialize_melt_data (&md, +                             res_size); +  free_melt_data (&md); +  return buf; +} + + +/* ********************* /refresh/melt ***************************** */ + + +/** + * @brief A /refresh/melt Handle + */ +struct TALER_EXCHANGE_RefreshMeltHandle +{ + +  /** +   * The connection to exchange this request handle will use +   */ +  struct TALER_EXCHANGE_Handle *exchange; + +  /** +   * The url for this request. +   */ +  char *url; + +  /** +   * JSON encoding of the request to POST. +   */ +  char *json_enc; + +  /** +   * Handle for the request. +   */ +  struct MAC_Job *job; + +  /** +   * Function to call with refresh melt failure results. +   */ +  TALER_EXCHANGE_RefreshMeltCallback melt_cb; + +  /** +   * Closure for @e result_cb and @e melt_failure_cb. +   */ +  void *melt_cb_cls; + +  /** +   * Download buffer +   */ +  struct MAC_DownloadBuffer db; + +  /** +   * Actual information about the melt operation. +   */ +  struct MeltData *md; +}; + + +/** + * Verify that the signature on the "200 OK" response + * from the exchange is valid. + * + * @param rmh melt handle + * @param json json reply with the signature + * @param[out] noreveal_index set to the noreveal index selected by the exchange + * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not + */ +static int +verify_refresh_melt_signature_ok (struct TALER_EXCHANGE_RefreshMeltHandle *rmh, +                                  json_t *json, +                                  uint16_t *noreveal_index) +{ +  struct TALER_ExchangeSignatureP exchange_sig; +  struct TALER_ExchangePublicKeyP exchange_pub; +  const struct TALER_EXCHANGE_Keys *key_state; +  struct MAJ_Specification spec[] = { +    MAJ_spec_fixed_auto ("exchange_sig", &exchange_sig), +    MAJ_spec_fixed_auto ("exchange_pub", &exchange_pub), +    MAJ_spec_uint16 ("noreveal_index", noreveal_index), +    MAJ_spec_end +  }; +  struct TALER_RefreshMeltConfirmationPS confirm; + +  if (GNUNET_OK != +      MAJ_parse_json (json, +                      spec)) +  { +    GNUNET_break_op (0); +    return GNUNET_SYSERR; +  } + +  /* check that exchange signing key is permitted */ +  key_state = TALER_EXCHANGE_get_keys (rmh->exchange); +  if (GNUNET_OK != +      TALER_EXCHANGE_test_signing_key (key_state, +                                   &exchange_pub)) +  { +    GNUNET_break_op (0); +    return GNUNET_SYSERR; +  } + +  /* check that noreveal index is in permitted range */ +  if (TALER_CNC_KAPPA <= *noreveal_index) +  { +    GNUNET_break_op (0); +    return GNUNET_SYSERR; +  } + +  /* verify signature by exchange */ +  confirm.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT); +  confirm.purpose.size = htonl (sizeof (struct TALER_RefreshMeltConfirmationPS)); +  confirm.session_hash = rmh->md->melt_session_hash; +  confirm.noreveal_index = htons (*noreveal_index); +  confirm.reserved = htons (0); +  if (GNUNET_OK != +      GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT, +                                  &confirm.purpose, +                                  &exchange_sig.eddsa_signature, +                                  &exchange_pub.eddsa_pub)) +  { +    GNUNET_break_op (0); +    return GNUNET_SYSERR; +  } +  return GNUNET_OK; +} + + +/** + * Verify that the signatures on the "403 FORBIDDEN" response from the + * exchange demonstrating customer double-spending are valid. + * + * @param rmh melt handle + * @param json json reply with the signature(s) and transaction history + * @return #GNUNET_OK if the signature(s) is valid, #GNUNET_SYSERR if not + */ +static int +verify_refresh_melt_signature_forbidden (struct TALER_EXCHANGE_RefreshMeltHandle *rmh, +                                         json_t *json) +{ +  json_t *history; +  struct TALER_Amount original_value; +  struct TALER_Amount melt_value_with_fee; +  struct TALER_Amount total; +  struct TALER_CoinSpendPublicKeyP coin_pub; +  unsigned int i; +  struct MAJ_Specification spec[] = { +    MAJ_spec_json ("history", &history), +    MAJ_spec_fixed_auto ("coin_pub", &coin_pub), +    MAJ_spec_amount ("original_value", &original_value), +    MAJ_spec_amount ("requested_value", &melt_value_with_fee), +    MAJ_spec_end +  }; +  const struct MeltedCoin *mc; + +  /* parse JSON reply */ +  if (GNUNET_OK != +      MAJ_parse_json (json, +                      spec)) +  { +    GNUNET_break_op (0); +    return GNUNET_SYSERR; +  } + +  /* Find out which coin was deemed problematic by the exchange */ +  mc = NULL; +  for (i=0;i<rmh->md->num_melted_coins;i++) +  { +    if (0 == TALER_amount_cmp (&melt_value_with_fee, +                               &rmh->md->melted_coins[i].melt_amount_with_fee)) +    { +      struct TALER_CoinSpendPublicKeyP mc_pub; + +      GNUNET_CRYPTO_eddsa_key_get_public (&rmh->md->melted_coins[i].coin_priv.eddsa_priv, +                                          &mc_pub.eddsa_pub); +      if (0 == memcmp (&mc_pub, +                       &coin_pub, +                       sizeof (struct TALER_CoinSpendPublicKeyP))) +      { +        mc = &rmh->md->melted_coins[i]; +        break; +      } +    } +  } +  if (NULL == mc) +  { +    /* coin not found in our original request */ +    GNUNET_break_op (0); +    json_decref (history); +    return GNUNET_SYSERR; +  } + +  /* check basic coin properties */ +  if (0 != TALER_amount_cmp (&original_value, +                             &mc->original_value)) +  { +    /* We disagree on the value of the coin */ +    GNUNET_break_op (0); +    json_decref (history); +    return GNUNET_SYSERR; +  } +  if (0 != TALER_amount_cmp (&melt_value_with_fee, +                             &mc->melt_amount_with_fee)) +  { +    /* We disagree on the value of the coin */ +    GNUNET_break_op (0); +    json_decref (history); +    return GNUNET_SYSERR; +  } + +  /* verify coin history */ +  history = json_object_get (json, +                             "history"); +  if (GNUNET_OK != +      TALER_EXCHANGE_verify_coin_history_ (original_value.currency, +                                       &coin_pub, +                                       history, +                                       &total)) +  { +    GNUNET_break_op (0); +    json_decref (history); +    return GNUNET_SYSERR; +  } +  json_decref (history); + +  /* check if melt operation was really too expensive given history */ +  if (GNUNET_OK != +      TALER_amount_add (&total, +                        &total, +                        &melt_value_with_fee)) +  { +    /* clearly not OK if our transaction would have caused +       the overflow... */ +    return GNUNET_OK; +  } + +  if (0 >= TALER_amount_cmp (&total, +                             &original_value)) +  { +    /* transaction should have still fit */ +    GNUNET_break (0); +    return GNUNET_SYSERR; +  } + +  /* everything OK, valid proof of double-spending was provided */ +  return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /refresh/melt request. + * + * @param cls the `struct TALER_EXCHANGE_RefreshMeltHandle` + * @param eh the curl request handle + */ +static void +handle_refresh_melt_finished (void *cls, +                              CURL *eh) +{ +  struct TALER_EXCHANGE_RefreshMeltHandle *rmh = cls; +  long response_code; +  json_t *json; +  uint16_t noreveal_index = TALER_CNC_KAPPA; /* invalid value */ + +  rmh->job = NULL; +  json = MAC_download_get_result (&rmh->db, +                                  eh, +                                  &response_code); +  switch (response_code) +  { +  case 0: +    break; +  case MHD_HTTP_OK: +    if (GNUNET_OK != +        verify_refresh_melt_signature_ok (rmh, +                                          json, +                                          &noreveal_index)) +    { +      GNUNET_break_op (0); +      response_code = 0; +    } +    if (NULL != rmh->melt_cb) +    { +      rmh->melt_cb (rmh->melt_cb_cls, +                    response_code, +                    noreveal_index, +                    json); +      rmh->melt_cb = NULL; +    } +    break; +  case MHD_HTTP_BAD_REQUEST: +    /* This should never happen, either us or the exchange is buggy +       (or API version conflict); just pass JSON reply to the application */ +    break; +  case MHD_HTTP_FORBIDDEN: +    /* Double spending; check signatures on transaction history */ +    if (GNUNET_OK != +        verify_refresh_melt_signature_forbidden (rmh, +                                                 json)) +    { +      GNUNET_break_op (0); +      response_code = 0; +    } +    break; +  case MHD_HTTP_UNAUTHORIZED: +    /* Nothing really to verify, exchange says one of the signatures is +       invalid; assuming we checked them, this should never happen, we +       should pass the JSON reply to the application */ +    break; +  case MHD_HTTP_NOT_FOUND: +    /* Nothing really to verify, this should never +       happen, we should pass the JSON reply to the application */ +    break; +  case MHD_HTTP_INTERNAL_SERVER_ERROR: +    /* Server had an internal issue; we should retry, but this API +       leaves this to the application */ +    break; +  default: +    /* unexpected response code */ +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected response code %u\n", +                response_code); +    GNUNET_break (0); +    response_code = 0; +    break; +  } +  if (NULL != rmh->melt_cb) +    rmh->melt_cb (rmh->melt_cb_cls, +                  response_code, +                  UINT16_MAX, +                  json); +  json_decref (json); +  TALER_EXCHANGE_refresh_melt_cancel (rmh); +} + + +/** + * Convert a coin to be melted to the respective JSON encoding. + * + * @param melt_session_hash session hash to use + * @param mc coin to be melted + * @return JSON encoding of the melting request + */ +static json_t * +melted_coin_to_json (const struct GNUNET_HashCode *melt_session_hash, +                     const struct MeltedCoin *mc) +{ +  struct TALER_CoinSpendSignatureP confirm_sig; +  struct TALER_RefreshMeltCoinAffirmationPS melt; + +  melt.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT); +  melt.purpose.size = htonl (sizeof (struct TALER_RefreshMeltCoinAffirmationPS)); +  melt.session_hash = *melt_session_hash; +  TALER_amount_hton (&melt.amount_with_fee, +                     &mc->melt_amount_with_fee); +  TALER_amount_hton (&melt.melt_fee, +                     &mc->fee_melt); +  GNUNET_CRYPTO_eddsa_key_get_public (&mc->coin_priv.eddsa_priv, +                                      &melt.coin_pub.eddsa_pub); +  GNUNET_CRYPTO_eddsa_sign (&mc->coin_priv.eddsa_priv, +                            &melt.purpose, +                            &confirm_sig.eddsa_signature); +  return json_pack ("{s:o, s:o, s:o, s:o, s:o}", +                    "coin_pub", +                    TALER_json_from_data (&melt.coin_pub, +                                          sizeof (melt.coin_pub)), +                    "denom_pub", +                    TALER_json_from_rsa_public_key (mc->pub_key.rsa_public_key), +                    "denom_sig", +                    TALER_json_from_rsa_signature (mc->sig.rsa_signature), +                    "confirm_sig", +                    TALER_json_from_data (&confirm_sig, +                                          sizeof (confirm_sig)), +                    "value_with_fee", +                    TALER_json_from_amount (&mc->melt_amount_with_fee)); +} + + +/** + * Submit a melt request to the exchange and get the exchange's + * response. + * + * This API is typically used by a wallet.  Note that to ensure that + * no money is lost in case of hardware failures, the provided + * argument should have been constructed using + * #TALER_EXCHANGE_refresh_prepare and committed to persistent storage + * prior to calling this function. + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param refresh_data_length size of the @a refresh_data (returned + *        in the `res_size` argument from #TALER_EXCHANGE_refresh_prepare()) + * @param refresh_data the refresh data as returned from +          #TALER_EXCHANGE_refresh_prepare()) + * @param melt_cb the callback to call with the result + * @param melt_cb_cls closure for @a melt_cb + * @return a handle for this request; NULL if the argument was invalid. + *         In this case, neither callback will be called. + */ +struct TALER_EXCHANGE_RefreshMeltHandle * +TALER_EXCHANGE_refresh_melt (struct TALER_EXCHANGE_Handle *exchange, +                         size_t refresh_data_length, +                         const char *refresh_data, +                         TALER_EXCHANGE_RefreshMeltCallback melt_cb, +                         void *melt_cb_cls) +{ +  json_t *melt_obj; +  json_t *new_denoms; +  json_t *melt_coins; +  json_t *coin_evs; +  json_t *transfer_pubs; +  json_t *secret_encs; +  json_t *link_encs; +  json_t *tmp; +  struct TALER_EXCHANGE_RefreshMeltHandle *rmh; +  CURL *eh; +  struct TALER_EXCHANGE_Context *ctx; +  struct MeltData *md; +  unsigned int i; +  unsigned int j; + +  if (GNUNET_YES != +      MAH_handle_is_ready (exchange)) +  { +    GNUNET_break (0); +    return NULL; +  } +  md = deserialize_melt_data (refresh_data, +                              refresh_data_length); +  if (NULL == md) +  { +    GNUNET_break (0); +    return NULL; +  } + +  /* build JSON request, each of the 6 arrays first */ +  new_denoms = json_array (); +  melt_coins = json_array (); +  coin_evs = json_array (); +  transfer_pubs = json_array (); +  secret_encs = json_array (); +  link_encs = json_array (); +  for (i=0;i<md->num_melted_coins;i++) +  { +    const struct MeltedCoin *mc = &md->melted_coins[i]; + +    /* now melt_coins */ +    json_array_append (melt_coins, +                       melted_coin_to_json (&md->melt_session_hash, +                                            mc)); +  } + +  /* now transfer_pubs */ +  for (j=0;j<TALER_CNC_KAPPA;j++) +  { +    tmp = json_array (); +    for (i=0;i<md->num_melted_coins;i++) +    { +      const struct MeltedCoin *mc = &md->melted_coins[i]; +      struct TALER_TransferPublicKeyP transfer_pub; + +      GNUNET_CRYPTO_ecdhe_key_get_public (&mc->transfer_priv[j].ecdhe_priv, +                                          &transfer_pub.ecdhe_pub); +      json_array_append (tmp, +                         TALER_json_from_data (&transfer_pub, +                                               sizeof (transfer_pub))); +    } +    json_array_append (transfer_pubs, +                       tmp); +  } + +  /* now secret_encs */ +  for (j=0;j<TALER_CNC_KAPPA;j++) +  { +    tmp = json_array (); +    for (i=0;i<md->num_melted_coins;i++) +    { +      const struct MeltedCoin *mc = &md->melted_coins[i]; +      struct TALER_EncryptedLinkSecretP els; +      struct TALER_TransferSecretP trans_sec; + +      TALER_link_derive_transfer_secret (&mc->coin_priv, +                                         &mc->transfer_priv[j], +                                         &trans_sec); +      GNUNET_assert (GNUNET_OK == +                     TALER_transfer_encrypt (&md->link_secrets[j], +                                             &trans_sec, +                                             &els)); +      json_array_append (tmp, +                         TALER_json_from_data (&els, +                                               sizeof (els))); +    } +    json_array_append (secret_encs, +                       tmp); +  } + +  /* now new_denoms */ +  for (i=0;i<md->num_fresh_coins;i++) +  { +    json_array_append (new_denoms, +                       TALER_json_from_rsa_public_key +                       (md->fresh_pks[i].rsa_public_key)); +  } + +  /* now link_encs */ +  for (j=0;j<TALER_CNC_KAPPA;j++) +  { +    tmp = json_array (); +    for (i=0;i<md->num_fresh_coins;i++) +    { +      const struct FreshCoin *fc = &md->fresh_coins[j][i]; +      struct TALER_RefreshLinkDecrypted rld; +      struct TALER_RefreshLinkEncrypted *rle; +      char *buf; +      size_t buf_len; + +      rld.coin_priv = fc->coin_priv; +      rld.blinding_key = fc->blinding_key; +      rle = TALER_refresh_encrypt (&rld, +                                   &md->link_secrets[j]); +      GNUNET_assert (NULL != rle); +      buf = TALER_refresh_link_encrypted_encode (rle, +                                                 &buf_len); +      GNUNET_assert (NULL != buf); +      json_array_append (tmp, +                         TALER_json_from_data (buf, +                                               buf_len)); +      GNUNET_free (buf); +      GNUNET_free (rle); +    } +    json_array_append (link_encs, +                       tmp); +  } + +  /* now coin_evs */ +  for (j=0;j<TALER_CNC_KAPPA;j++) +  { +    tmp = json_array (); +    for (i=0;i<md->num_fresh_coins;i++) +    { +      const struct FreshCoin *fc = &md->fresh_coins[j][i]; +      struct TALER_CoinSpendPublicKeyP coin_pub; +      struct GNUNET_HashCode coin_hash; +      char *coin_ev; /* blinded message to be signed (in envelope) for each coin */ +      size_t coin_ev_size; + +      GNUNET_CRYPTO_eddsa_key_get_public (&fc->coin_priv.eddsa_priv, +                                          &coin_pub.eddsa_pub); +      GNUNET_CRYPTO_hash (&coin_pub.eddsa_pub, +                          sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey), +                          &coin_hash); +      coin_ev_size = GNUNET_CRYPTO_rsa_blind (&coin_hash, +                                              fc->blinding_key.rsa_blinding_key, +                                              md->fresh_pks[i].rsa_public_key, +                                              &coin_ev); +      json_array_append (tmp, +                         TALER_json_from_data (coin_ev, +                                               coin_ev_size)); +      GNUNET_free (coin_ev); +    } +    json_array_append (coin_evs, +                       tmp); +  } + +  /* finally, assemble main JSON request from constitutent arrays */ +  melt_obj = json_pack ("{s:o, s:o, s:o, s:o, s:o, s:o}", +                        "new_denoms", new_denoms, +                        "melt_coins", melt_coins, +                        "coin_evs", coin_evs, +                        "transfer_pubs", transfer_pubs, +                        "secret_encs", secret_encs, +                        "link_encs", link_encs); + +  /* and now we can at last begin the actual request handling */ +  rmh = GNUNET_new (struct TALER_EXCHANGE_RefreshMeltHandle); +  rmh->exchange = exchange; +  rmh->melt_cb = melt_cb; +  rmh->melt_cb_cls = melt_cb_cls; +  rmh->md = md; +  rmh->url = MAH_path_to_url (exchange, +                              "/refresh/melt"); + +  eh = curl_easy_init (); +  GNUNET_assert (NULL != (rmh->json_enc = +                          json_dumps (melt_obj, +                                      JSON_COMPACT))); +  json_decref (melt_obj); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_URL, +                                   rmh->url)); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_POSTFIELDS, +                                   rmh->json_enc)); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_POSTFIELDSIZE, +                                   strlen (rmh->json_enc))); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_WRITEFUNCTION, +                                   &MAC_download_cb)); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_WRITEDATA, +                                   &rmh->db)); +  ctx = MAH_handle_to_context (exchange); +  rmh->job = MAC_job_add (ctx, +                          eh, +                          GNUNET_YES, +                          &handle_refresh_melt_finished, +                          rmh); +  return rmh; +} + + +/** + * Cancel a refresh execute request.  This function cannot be used + * on a request handle if either callback was already invoked. + * + * @param rmh the refresh melt handle + */ +void +TALER_EXCHANGE_refresh_melt_cancel (struct TALER_EXCHANGE_RefreshMeltHandle *rmh) +{ +  if (NULL != rmh->job) +  { +    MAC_job_cancel (rmh->job); +    rmh->job = NULL; +  } +  GNUNET_free_non_null (rmh->db.buf); +  free_melt_data (rmh->md); /* does not free 'md' itself */ +  GNUNET_free (rmh->md); +  GNUNET_free (rmh->url); +  GNUNET_free (rmh->json_enc); +  GNUNET_free (rmh); +} + + +/* ********************* /refresh/reveal ***************************** */ + + +/** + * @brief A /refresh/reveal Handle + */ +struct TALER_EXCHANGE_RefreshRevealHandle +{ + +  /** +   * The connection to exchange this request handle will use +   */ +  struct TALER_EXCHANGE_Handle *exchange; + +  /** +   * The url for this request. +   */ +  char *url; + +  /** +   * JSON encoding of the request to POST. +   */ +  char *json_enc; + +  /** +   * Handle for the request. +   */ +  struct MAC_Job *job; + +  /** +   * Function to call with the result. +   */ +  TALER_EXCHANGE_RefreshRevealCallback reveal_cb; + +  /** +   * Closure for @e reveal_cb. +   */ +  void *reveal_cb_cls; + +  /** +   * Download buffer +   */ +  struct MAC_DownloadBuffer db; + +  /** +   * Actual information about the melt operation. +   */ +  struct MeltData *md; + +  /** +   * The index selected by the exchange in cut-and-choose to not be revealed. +   */ +  uint16_t noreveal_index; + +}; + + +/** + * We got a 200 OK response for the /refresh/reveal operation. + * Extract the coin signatures and return them to the caller. + * The signatures we get from the exchange is for the blinded value. + * Thus, we first must unblind them and then should verify their + * validity. + * + * If everything checks out, we return the unblinded signatures + * to the application via the callback. + * + * @param rrh operation handle + * @param json reply from the exchange + * @param[out] coin_privs array of length `num_fresh_coins`, initialized to contain private keys + * @param[out] sigs array of length `num_fresh_coins`, initialized to cointain RSA signatures + * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors + */ +static int +refresh_reveal_ok (struct TALER_EXCHANGE_RefreshRevealHandle *rrh, +                   json_t *json, +                   struct TALER_CoinSpendPrivateKeyP *coin_privs, +                   struct TALER_DenominationSignature *sigs) +{ +  unsigned int i; +  json_t *jsona; +  struct MAJ_Specification spec[] = { +    MAJ_spec_json ("ev_sigs", &jsona), +    MAJ_spec_end +  }; + +  if (GNUNET_OK != +      MAJ_parse_json (json, +		      spec)) +  { +    GNUNET_break_op (0); +    return GNUNET_SYSERR; +  } +  if (! json_is_array (jsona)) +  { +    /* We expected an array of coins */ +    GNUNET_break_op (0); +    return GNUNET_SYSERR; +  } +  if (rrh->md->num_fresh_coins != json_array_size (jsona)) +  { +    /* Number of coins generated does not match our expectation */ +    GNUNET_break_op (0); +    return GNUNET_SYSERR; +  } +  for (i=0;i<rrh->md->num_fresh_coins;i++) +  { +    const struct FreshCoin *fc; +    struct TALER_DenominationPublicKey *pk; +    json_t *jsonai; +    struct GNUNET_CRYPTO_rsa_Signature *blind_sig; +    struct GNUNET_CRYPTO_rsa_Signature *sig; +    struct TALER_CoinSpendPublicKeyP coin_pub; +    struct GNUNET_HashCode coin_hash; + +    struct MAJ_Specification spec[] = { +      MAJ_spec_rsa_signature ("ev_sig", &blind_sig), +      MAJ_spec_end +    }; + +    fc = &rrh->md->fresh_coins[rrh->noreveal_index][i]; +    pk = &rrh->md->fresh_pks[i]; +    jsonai = json_array_get (jsona, i); +    GNUNET_assert (NULL != jsonai); + +    if (GNUNET_OK != +        MAJ_parse_json (jsonai, +                        spec)) +    { +      GNUNET_break_op (0); +      return GNUNET_SYSERR; +    } + +    /* unblind the signature */ +    sig = GNUNET_CRYPTO_rsa_unblind (blind_sig, +                                     fc->blinding_key.rsa_blinding_key, +                                     pk->rsa_public_key); +    GNUNET_CRYPTO_rsa_signature_free (blind_sig); + +    /* verify the signature */ +    GNUNET_CRYPTO_eddsa_key_get_public (&fc->coin_priv.eddsa_priv, +                                        &coin_pub.eddsa_pub); +    GNUNET_CRYPTO_hash (&coin_pub.eddsa_pub, +                        sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey), +                        &coin_hash); + +    if (GNUNET_OK != +        GNUNET_CRYPTO_rsa_verify (&coin_hash, +                                  sig, +                                  pk->rsa_public_key)) +    { +      GNUNET_break_op (0); +      GNUNET_CRYPTO_rsa_signature_free (sig); +      return GNUNET_SYSERR; +    } +    coin_privs[i] = fc->coin_priv; +    sigs[i].rsa_signature = sig; +  } +  return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /refresh/reveal request. + * + * @param cls the `struct TALER_EXCHANGE_RefreshHandle` + * @param eh the curl request handle + */ +static void +handle_refresh_reveal_finished (void *cls, +                                CURL *eh) +{ +  struct TALER_EXCHANGE_RefreshRevealHandle *rrh = cls; +  long response_code; +  json_t *json; + +  rrh->job = NULL; +  json = MAC_download_get_result (&rrh->db, +                                  eh, +                                  &response_code); +  switch (response_code) +  { +  case 0: +    break; +  case MHD_HTTP_OK: +    { +      struct TALER_CoinSpendPrivateKeyP coin_privs[rrh->md->num_fresh_coins]; +      struct TALER_DenominationSignature sigs[rrh->md->num_fresh_coins]; +      unsigned int i; +      int ret; + +      memset (sigs, 0, sizeof (sigs)); +      ret = refresh_reveal_ok (rrh, +                               json, +                               coin_privs, +                               sigs); +      if (GNUNET_OK != ret) +      { +        response_code = 0; +      } +      else +      { +        rrh->reveal_cb (rrh->reveal_cb_cls, +                        MHD_HTTP_OK, +                        rrh->md->num_fresh_coins, +                        coin_privs, +                        sigs, +                        json); +        rrh->reveal_cb = NULL; +      } +      for (i=0;i<rrh->md->num_fresh_coins;i++) +        if (NULL != sigs[i].rsa_signature) +          GNUNET_CRYPTO_rsa_signature_free (sigs[i].rsa_signature); +    } +    break; +  case MHD_HTTP_BAD_REQUEST: +    /* This should never happen, either us or the exchange is buggy +       (or API version conflict); just pass JSON reply to the application */ +    break; +  case MHD_HTTP_CONFLICT: +    /* Nothing really to verify, exchange says our reveal is inconsitent +       with our commitment, so either side is buggy; we +       should pass the JSON reply to the application */ +    break; +  case MHD_HTTP_INTERNAL_SERVER_ERROR: +    /* Server had an internal issue; we should retry, but this API +       leaves this to the application */ +    break; +  default: +    /* unexpected response code */ +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected response code %u\n", +                response_code); +    GNUNET_break (0); +    response_code = 0; +    break; +  } +  if (NULL != rrh->reveal_cb) +    rrh->reveal_cb (rrh->reveal_cb_cls, +                    response_code, +                    0, NULL, NULL, +                    json); +  json_decref (json); +  TALER_EXCHANGE_refresh_reveal_cancel (rrh); +} + + +/** + * Submit a /refresh/reval request to the exchange and get the exchange's + * response. + * + * This API is typically used by a wallet.  Note that to ensure that + * no money is lost in case of hardware failures, the provided + * arguments should have been committed to persistent storage + * prior to calling this function. + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param refresh_data_length size of the @a refresh_data (returned + *        in the `res_size` argument from #TALER_EXCHANGE_refresh_prepare()) + * @param refresh_data the refresh data as returned from +          #TALER_EXCHANGE_refresh_prepare()) + * @param noreveal_index response from the exchange to the + *        #TALER_EXCHANGE_refresh_melt() invocation + * @param reveal_cb the callback to call with the final result of the + *        refresh operation + * @param reveal_cb_cls closure for the above callback + * @return a handle for this request; NULL if the argument was invalid. + *         In this case, neither callback will be called. + */ +struct TALER_EXCHANGE_RefreshRevealHandle * +TALER_EXCHANGE_refresh_reveal (struct TALER_EXCHANGE_Handle *exchange, +                           size_t refresh_data_length, +                           const char *refresh_data, +                           uint16_t noreveal_index, +                           TALER_EXCHANGE_RefreshRevealCallback reveal_cb, +                           void *reveal_cb_cls) +{ +  struct TALER_EXCHANGE_RefreshRevealHandle *rrh; +  json_t *transfer_privs; +  json_t *reveal_obj; +  json_t *tmp; +  CURL *eh; +  struct TALER_EXCHANGE_Context *ctx; +  struct MeltData *md; +  unsigned int i; +  unsigned int j; + +  if (GNUNET_YES != +      MAH_handle_is_ready (exchange)) +  { +    GNUNET_break (0); +    return NULL; +  } +  md = deserialize_melt_data (refresh_data, +                              refresh_data_length); +  if (NULL == md) +  { +    GNUNET_break (0); +    return NULL; +  } +  if (noreveal_index >= TALER_CNC_KAPPA) +  { +    /* We check this here, as it would be really bad to below just +       disclose all the transfer keys. Note that this error should +       have been caught way earlier when the exchange replied, but maybe +       we had some internal corruption that changed the value... */ +    GNUNET_break (0); +    return NULL; +  } + +  /* build array of transfer private keys */ +  transfer_privs = json_array (); +  for (j=0;j<TALER_CNC_KAPPA;j++) +  { +    if (j == noreveal_index) +    { +      /* This is crucial: exclude the transfer key for the +	 noreval index! */ +      continue; +    } +    tmp = json_array (); +    for (i=0;i<md->num_melted_coins;i++) +    { +      const struct MeltedCoin *mc = &md->melted_coins[i]; + +      json_array_append (tmp, +                         TALER_json_from_data (&mc->transfer_priv[j], +                                               sizeof (struct TALER_TransferPrivateKeyP))); +    } +    json_array_append (transfer_privs, +                       tmp); +  } + +  /* build main JSON request */ +  reveal_obj = json_pack ("{s:o, s:o}", +                          "session_hash", +                          TALER_json_from_data (&md->melt_session_hash, +                                                sizeof (struct GNUNET_HashCode)), +                          "transfer_privs", +                          transfer_privs); + +  /* finally, we can actually issue the request */ +  rrh = GNUNET_new (struct TALER_EXCHANGE_RefreshRevealHandle); +  rrh->exchange = exchange; +  rrh->noreveal_index = noreveal_index; +  rrh->reveal_cb = reveal_cb; +  rrh->reveal_cb_cls = reveal_cb_cls; +  rrh->md = md; +  rrh->url = MAH_path_to_url (rrh->exchange, +                              "/refresh/reveal"); + +  eh = curl_easy_init (); +  GNUNET_assert (NULL != (rrh->json_enc = +                          json_dumps (reveal_obj, +                                      JSON_COMPACT))); +  json_decref (reveal_obj); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_URL, +                                   rrh->url)); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_POSTFIELDS, +                                   rrh->json_enc)); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_POSTFIELDSIZE, +                                   strlen (rrh->json_enc))); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_WRITEFUNCTION, +                                   &MAC_download_cb)); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_WRITEDATA, +                                   &rrh->db)); +  ctx = MAH_handle_to_context (rrh->exchange); +  rrh->job = MAC_job_add (ctx, +                          eh, +                          GNUNET_YES, +                          &handle_refresh_reveal_finished, +                          rrh); +  return rrh; +} + + +/** + * Cancel a refresh reveal request.  This function cannot be used + * on a request handle if the callback was already invoked. + * + * @param rrh the refresh reval handle + */ +void +TALER_EXCHANGE_refresh_reveal_cancel (struct TALER_EXCHANGE_RefreshRevealHandle *rrh) +{ +  if (NULL != rrh->job) +  { +    MAC_job_cancel (rrh->job); +    rrh->job = NULL; +  } +  GNUNET_free_non_null (rrh->db.buf); +  GNUNET_free (rrh->url); +  GNUNET_free (rrh->json_enc); +  free_melt_data (rrh->md); /* does not free 'md' itself */ +  GNUNET_free (rrh->md); +  GNUNET_free (rrh); +} + + +/* end of exchange_api_refresh.c */ diff --git a/src/exchange-lib/exchange_api_refresh_link.c b/src/exchange-lib/exchange_api_refresh_link.c new file mode 100644 index 00000000..9576916b --- /dev/null +++ b/src/exchange-lib/exchange_api_refresh_link.c @@ -0,0 +1,482 @@ +/* +  This file is part of TALER +  Copyright (C) 2015, 2016 GNUnet e.V. + +  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, If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/exchange_api_refresh_link.c + * @brief Implementation of the /refresh/link request of the exchange's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include "taler_exchange_service.h" +#include "exchange_api_json.h" +#include "exchange_api_context.h" +#include "exchange_api_handle.h" +#include "taler_signatures.h" + + +/** + * @brief A /refresh/link Handle + */ +struct TALER_EXCHANGE_RefreshLinkHandle +{ + +  /** +   * The connection to exchange this request handle will use +   */ +  struct TALER_EXCHANGE_Handle *exchange; + +  /** +   * The url for this request. +   */ +  char *url; + +  /** +   * Handle for the request. +   */ +  struct MAC_Job *job; + +  /** +   * Function to call with the result. +   */ +  TALER_EXCHANGE_RefreshLinkCallback link_cb; + +  /** +   * Closure for @e cb. +   */ +  void *link_cb_cls; + +  /** +   * Download buffer +   */ +  struct MAC_DownloadBuffer db; + +  /** +   * Private key of the coin, required to decode link information. +   */ +  struct TALER_CoinSpendPrivateKeyP coin_priv; + +}; + + +/** + * Parse the provided linkage data from the "200 OK" response + * for one of the coins. + * + * @param rlh refresh link handle + * @param json json reply with the data for one coin + * @param trans_pub our transfer public key + * @param secret_enc encrypted key to decrypt link data + * @param[out] coin_priv where to return private coin key + * @param[out] sig where to return private coin signature + * @param[out] pub where to return the public key for the coin + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +static int +parse_refresh_link_coin (const struct TALER_EXCHANGE_RefreshLinkHandle *rlh, +                         json_t *json, +                         const struct TALER_TransferPublicKeyP *trans_pub, +                         const struct TALER_EncryptedLinkSecretP *secret_enc, +                         struct TALER_CoinSpendPrivateKeyP *coin_priv, +                         struct TALER_DenominationSignature *sig, +                         struct TALER_DenominationPublicKey *pub) +{ +  void *link_enc; +  size_t link_enc_size; +  struct GNUNET_CRYPTO_rsa_Signature *bsig; +  struct GNUNET_CRYPTO_rsa_PublicKey *rpub; +  struct MAJ_Specification spec[] = { +    MAJ_spec_varsize ("link_enc", &link_enc, &link_enc_size), +    MAJ_spec_rsa_public_key ("denom_pub", &rpub), +    MAJ_spec_rsa_signature ("ev_sig", &bsig), +    MAJ_spec_end +  }; +  struct TALER_RefreshLinkEncrypted *rle; +  struct TALER_RefreshLinkDecrypted *rld; +  struct TALER_LinkSecretP secret; + +  /* parse reply */ +  if (GNUNET_OK != +      MAJ_parse_json (json, +                      spec)) +  { +    GNUNET_break_op (0); +    return GNUNET_SYSERR; +  } + +  /* decode and decrypt link data */ +  rle = TALER_refresh_link_encrypted_decode (link_enc, +                                             link_enc_size); +  if (NULL == rle) +  { +    GNUNET_break_op (0); +    MAJ_parse_free (spec); +    return GNUNET_SYSERR; +  } +  if (GNUNET_OK != +      TALER_link_decrypt_secret2 (secret_enc, +                                  trans_pub, +                                  &rlh->coin_priv, +                                  &secret)) +  { +    GNUNET_break_op (0); +    MAJ_parse_free (spec); +    return GNUNET_SYSERR; +  } +  rld = TALER_refresh_decrypt (rle, +                               &secret); +  if (NULL == rld) +  { +    GNUNET_break_op (0); +    MAJ_parse_free (spec); +    return GNUNET_SYSERR; +  } + +  /* extract coin and signature */ +  *coin_priv = rld->coin_priv; +  sig->rsa_signature +    = GNUNET_CRYPTO_rsa_unblind (bsig, +                                 rld->blinding_key.rsa_blinding_key, +                                 rpub); + +  /* clean up */ +  GNUNET_free (rld); +  pub->rsa_public_key = GNUNET_CRYPTO_rsa_public_key_dup (rpub); +  MAJ_parse_free (spec); +  return GNUNET_OK; +} + + +/** + * Parse the provided linkage data from the "200 OK" response + * for one of the coins. + * + * @param[in,out] rlh refresh link handle (callback may be zero'ed out) + * @param json json reply with the data for one coin + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +static int +parse_refresh_link_ok (struct TALER_EXCHANGE_RefreshLinkHandle *rlh, +                       json_t *json) +{ +  unsigned int session; +  unsigned int num_coins; +  int ret; + +  if (! json_is_array (json)) +  { +    GNUNET_break_op (0); +    return GNUNET_SYSERR; +  } +  num_coins = 0; +  /* Theoretically, a coin may have been melted repeatedly +     into different sessions; so the response is an array +     which contains information by melting session.  That +     array contains another array.  However, our API returns +     a single 1d array, so we flatten the 2d array that is +     returned into a single array. Note that usually a coin +     is melted at most once, and so we'll only run this +     loop once for 'session=0' in most cases. + +     num_coins tracks the size of the 1d array we return, +     whilst 'i' and 'session' track the 2d array. */ +  for (session=0;session<json_array_size (json); session++) +  { +    json_t *jsona; +    struct MAJ_Specification spec[] = { +      MAJ_spec_json ("new_coins", &jsona), +      MAJ_spec_end +    }; + +    if (GNUNET_OK != +	MAJ_parse_json (json_array_get (json, +					session), +			spec)) +    { +      GNUNET_break_op (0); +      return GNUNET_SYSERR; +    } +    if (! json_is_array (jsona)) +    { +      GNUNET_break_op (0); +      MAJ_parse_free (spec); +      return GNUNET_SYSERR; +    } + +    /* count all coins over all sessions */ +    num_coins += json_array_size (jsona); +    MAJ_parse_free (spec); +  } +  /* Now that we know how big the 1d array is, allocate +     and fill it. */ +  { +    unsigned int off_coin; /* index into 1d array */ +    unsigned int i; +    struct TALER_CoinSpendPrivateKeyP coin_privs[num_coins]; +    struct TALER_DenominationSignature sigs[num_coins]; +    struct TALER_DenominationPublicKey pubs[num_coins]; + +    memset (sigs, 0, sizeof (sigs)); +    memset (pubs, 0, sizeof (pubs)); +    off_coin = 0; +    for (session=0;session<json_array_size (json); session++) +    { +      json_t *jsona; +      struct TALER_TransferPublicKeyP trans_pub; +      struct TALER_EncryptedLinkSecretP secret_enc; +      struct MAJ_Specification spec[] = { +	MAJ_spec_json ("new_coins", &jsona), +	MAJ_spec_fixed_auto ("transfer_pub", &trans_pub), +	MAJ_spec_fixed_auto ("secret_enc", &secret_enc), +	MAJ_spec_end +      }; + +      if (GNUNET_OK != +	  MAJ_parse_json (json_array_get (json, +					  session), +			  spec)) +      { +	GNUNET_break_op (0); +	return GNUNET_SYSERR; +      } +      if (! json_is_array (jsona)) +      { +	GNUNET_break_op (0); +	MAJ_parse_free (spec); +	return GNUNET_SYSERR; +      } + +      /* decode all coins */ +      for (i=0;i<json_array_size (jsona);i++) +      { +	if (GNUNET_OK != +	    parse_refresh_link_coin (rlh, +				     json_array_get (jsona, +						     i), +				     &trans_pub, +				     &secret_enc, +				     &coin_privs[i+off_coin], +				     &sigs[i+off_coin], +				     &pubs[i+off_coin])) +	{ +	  GNUNET_break_op (0); +	  break; +	} +      } +      /* check if we really got all, then invoke callback */ +      off_coin += i; +      if (i != json_array_size (jsona)) +      { +	GNUNET_break_op (0); +	ret = GNUNET_SYSERR; +	MAJ_parse_free (spec); +	break; +      } +      MAJ_parse_free (spec); +    } /* end of for (session) */ + +    if (off_coin == num_coins) +    { +      rlh->link_cb (rlh->link_cb_cls, +		    MHD_HTTP_OK, +		    num_coins, +		    coin_privs, +		    sigs, +		    pubs, +		    json); +      rlh->link_cb = NULL; +      ret = GNUNET_OK; +    } +    else +    { +      GNUNET_break_op (0); +      ret = GNUNET_SYSERR; +    } + +    /* clean up */ +    for (i=0;i<off_coin;i++) +    { +      if (NULL != sigs[i].rsa_signature) +        GNUNET_CRYPTO_rsa_signature_free (sigs[i].rsa_signature); +      if (NULL != pubs[i].rsa_public_key) +        GNUNET_CRYPTO_rsa_public_key_free (pubs[i].rsa_public_key); +    } +  } +  return ret; +} + + +/** + * Function called when we're done processing the + * HTTP /refresh/link request. + * + * @param cls the `struct TALER_EXCHANGE_RefreshLinkHandle` + * @param eh the curl request handle + */ +static void +handle_refresh_link_finished (void *cls, +                              CURL *eh) +{ +  struct TALER_EXCHANGE_RefreshLinkHandle *rlh = cls; +  long response_code; +  json_t *json; + +  rlh->job = NULL; +  json = MAC_download_get_result (&rlh->db, +                                  eh, +                                  &response_code); +  switch (response_code) +  { +  case 0: +    break; +  case MHD_HTTP_OK: +    if (GNUNET_OK != +        parse_refresh_link_ok (rlh, +                               json)) +    { +      GNUNET_break_op (0); +      response_code = 0; +    } +    break; +  case MHD_HTTP_BAD_REQUEST: +    /* This should never happen, either us or the exchange is buggy +       (or API version conflict); just pass JSON reply to the application */ +    break; +  case MHD_HTTP_NOT_FOUND: +    /* Nothing really to verify, exchange says this coin was not melted; we +       should pass the JSON reply to the application */ +    break; +  case MHD_HTTP_INTERNAL_SERVER_ERROR: +    /* Server had an internal issue; we should retry, but this API +       leaves this to the application */ +    break; +  default: +    /* unexpected response code */ +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected response code %u\n", +                response_code); +    GNUNET_break (0); +    response_code = 0; +    break; +  } +  if (NULL != rlh->link_cb) +    rlh->link_cb (rlh->link_cb_cls, +                  response_code, +                  0, NULL, NULL, NULL, +                  json); +  json_decref (json); +  TALER_EXCHANGE_refresh_link_cancel (rlh); +} + + +/** + * Submit a link request to the exchange and get the exchange's response. + * + * This API is typically not used by anyone, it is more a threat + * against those trying to receive a funds transfer by abusing the + * /refresh protocol. + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param coin_priv private key to request link data for + * @param link_cb the callback to call with the useful result of the + *        refresh operation the @a coin_priv was involved in (if any) + * @param link_cb_cls closure for @a link_cb + * @return a handle for this request + */ +struct TALER_EXCHANGE_RefreshLinkHandle * +TALER_EXCHANGE_refresh_link (struct TALER_EXCHANGE_Handle *exchange, +                         const struct TALER_CoinSpendPrivateKeyP *coin_priv, +                         TALER_EXCHANGE_RefreshLinkCallback link_cb, +                         void *link_cb_cls) +{ +  struct TALER_EXCHANGE_RefreshLinkHandle *rlh; +  CURL *eh; +  struct TALER_EXCHANGE_Context *ctx; +  struct TALER_CoinSpendPublicKeyP coin_pub; +  char *pub_str; +  char *arg_str; + +  if (GNUNET_YES != +      MAH_handle_is_ready (exchange)) +  { +    GNUNET_break (0); +    return NULL; +  } + +  GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, +                                      &coin_pub.eddsa_pub); +  pub_str = GNUNET_STRINGS_data_to_string_alloc (&coin_pub, +                                                 sizeof (struct TALER_CoinSpendPublicKeyP)); +  GNUNET_asprintf (&arg_str, +                   "/refresh/link?coin_pub=%s", +                   pub_str); +  GNUNET_free (pub_str); + +  rlh = GNUNET_new (struct TALER_EXCHANGE_RefreshLinkHandle); +  rlh->exchange = exchange; +  rlh->link_cb = link_cb; +  rlh->link_cb_cls = link_cb_cls; +  rlh->coin_priv = *coin_priv; +  rlh->url = MAH_path_to_url (exchange, arg_str); +  GNUNET_free (arg_str); + +  eh = curl_easy_init (); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_URL, +                                   rlh->url)); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_WRITEFUNCTION, +                                   &MAC_download_cb)); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_WRITEDATA, +                                   &rlh->db)); +  ctx = MAH_handle_to_context (exchange); +  rlh->job = MAC_job_add (ctx, +                          eh, +                          GNUNET_YES, +                          &handle_refresh_link_finished, +                          rlh); +  return rlh; +} + + +/** + * Cancel a refresh link request.  This function cannot be used + * on a request handle if the callback was already invoked. + * + * @param rlh the refresh link handle + */ +void +TALER_EXCHANGE_refresh_link_cancel (struct TALER_EXCHANGE_RefreshLinkHandle *rlh) +{ +  if (NULL != rlh->job) +  { +    MAC_job_cancel (rlh->job); +    rlh->job = NULL; +  } +  GNUNET_free_non_null (rlh->db.buf); +  GNUNET_free (rlh->url); +  GNUNET_free (rlh); +} + + +/* end of exchange_api_refresh_link.c */ diff --git a/src/exchange-lib/exchange_api_reserve.c b/src/exchange-lib/exchange_api_reserve.c new file mode 100644 index 00000000..e694b8d9 --- /dev/null +++ b/src/exchange-lib/exchange_api_reserve.c @@ -0,0 +1,930 @@ +/* +  This file is part of TALER +  Copyright (C) 2014, 2015 GNUnet e.V. + +  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, If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/exchange_api_reserve.c + * @brief Implementation of the /reserve requests of the exchange's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include "taler_exchange_service.h" +#include "exchange_api_json.h" +#include "exchange_api_context.h" +#include "exchange_api_handle.h" +#include "taler_signatures.h" + + +/* ********************** /reserve/status ********************** */ + +/** + * @brief A Withdraw Status Handle + */ +struct TALER_EXCHANGE_ReserveStatusHandle +{ + +  /** +   * The connection to exchange this request handle will use +   */ +  struct TALER_EXCHANGE_Handle *exchange; + +  /** +   * The url for this request. +   */ +  char *url; + +  /** +   * Handle for the request. +   */ +  struct MAC_Job *job; + +  /** +   * Function to call with the result. +   */ +  TALER_EXCHANGE_ReserveStatusResultCallback cb; + +  /** +   * Public key of the reserve we are querying. +   */ +  struct TALER_ReservePublicKeyP reserve_pub; + +  /** +   * Closure for @a cb. +   */ +  void *cb_cls; + +  /** +   * Download buffer +   */ +  struct MAC_DownloadBuffer db; + +}; + + +/** + * Parse history given in JSON format and return it in binary + * format. + * + * @param[in] history JSON array with the history + * @param reserve_pub public key of the reserve to inspect + * @param currency currency we expect the balance to be in + * @param[out] balance final balance + * @param history_length number of entries in @a history + * @param[out] rhistory array of length @a history_length, set to the + *             parsed history entries + * @return #GNUNET_OK if history was valid and @a rhistory and @a balance + *         were set, + *         #GNUNET_SYSERR if there was a protocol violation in @a history + */ +static int +parse_reserve_history (json_t *history, +                       const struct TALER_ReservePublicKeyP *reserve_pub, +                       const char *currency, +                       struct TALER_Amount *balance, +                       unsigned int history_length, +                       struct TALER_EXCHANGE_ReserveHistory *rhistory) +{ +  struct GNUNET_HashCode uuid[history_length]; +  unsigned int uuid_off; +  struct TALER_Amount total_in; +  struct TALER_Amount total_out; +  size_t off; + +  TALER_amount_get_zero (currency, +                         &total_in); +  TALER_amount_get_zero (currency, +                         &total_out); +  uuid_off = 0; +  for (off=0;off<history_length;off++) +  { +    json_t *transaction; +    struct TALER_Amount amount; +    const char *type; +    struct MAJ_Specification hist_spec[] = { +      MAJ_spec_string ("type", &type), +      MAJ_spec_amount ("amount", +                       &amount), +      /* 'wire' and 'signature' are optional depending on 'type'! */ +      MAJ_spec_end +    }; + +    transaction = json_array_get (history, +                                  off); +    if (GNUNET_OK != +        MAJ_parse_json (transaction, +                        hist_spec)) +    { +      GNUNET_break_op (0); +      return GNUNET_SYSERR; +    } +    rhistory[off].amount = amount; + +    if (0 == strcasecmp (type, +                         "DEPOSIT")) +    { +      json_t *wire; + +      rhistory[off].type = TALER_EXCHANGE_RTT_DEPOSIT; +      if (GNUNET_OK != +          TALER_amount_add (&total_in, +                            &total_in, +                            &amount)) +      { +        /* overflow in history already!? inconceivable! Bad exchange! */ +        GNUNET_break_op (0); +        return GNUNET_SYSERR; +      } +      wire = json_object_get (transaction, +                              "wire"); +      /* check 'wire' is a JSON object (no need to check wireformat, +         but we do at least expect "some" JSON object here) */ +      if ( (NULL == wire) || +           (! json_is_object (wire)) ) +      { +        /* not even a JSON 'wire' specification, not acceptable */ +        GNUNET_break_op (0); +        return GNUNET_SYSERR; +      } +      rhistory[off].details.wire_in_details = wire; +      /* end type==DEPOSIT */ +    } +    else if (0 == strcasecmp (type, +                              "WITHDRAW")) +    { +      struct TALER_ReserveSignatureP sig; +      struct TALER_WithdrawRequestPS withdraw_purpose; +      struct TALER_Amount amount_from_purpose; +      struct MAJ_Specification withdraw_spec[] = { +        MAJ_spec_fixed_auto ("signature", +                             &sig), +        MAJ_spec_fixed_auto ("details", +                             &withdraw_purpose), +        MAJ_spec_end +      }; +      unsigned int i; + +      rhistory[off].type = TALER_EXCHANGE_RTT_WITHDRAWAL; +      if (GNUNET_OK != +          MAJ_parse_json (transaction, +                          withdraw_spec)) +      { +        GNUNET_break_op (0); +        return GNUNET_SYSERR; +      } +      /* Check that the signature is a valid withdraw request */ +      if (GNUNET_OK != +          GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW, +                                      &withdraw_purpose.purpose, +                                      &sig.eddsa_signature, +                                      &reserve_pub->eddsa_pub)) +      { +        GNUNET_break_op (0); +        MAJ_parse_free (withdraw_spec); +        return GNUNET_SYSERR; +      } +      TALER_amount_ntoh (&amount_from_purpose, +                         &withdraw_purpose.amount_with_fee); +      if (0 != TALER_amount_cmp (&amount, +                                 &amount_from_purpose)) +      { +        GNUNET_break_op (0); +        MAJ_parse_free (withdraw_spec); +        return GNUNET_SYSERR; +      } +      rhistory[off].details.out_authorization_sig = json_object_get (transaction, +                                                                     "signature"); +      /* Check check that the same withdraw transaction +         isn't listed twice by the exchange. We use the +         "uuid" array to remember the hashes of all +         purposes, and compare the hashes to find +         duplicates. */ +      GNUNET_CRYPTO_hash (&withdraw_purpose, +                          ntohl (withdraw_purpose.purpose.size), +                          &uuid[uuid_off]); +      for (i=0;i<uuid_off;i++) +      { +        if (0 == memcmp (&uuid[uuid_off], +                         &uuid[i], +                         sizeof (struct GNUNET_HashCode))) +        { +          GNUNET_break_op (0); +          MAJ_parse_free (withdraw_spec); +          return GNUNET_SYSERR; +        } +      } +      uuid_off++; + +      if (GNUNET_OK != +          TALER_amount_add (&total_out, +                            &total_out, +                            &amount)) +      { +        /* overflow in history already!? inconceivable! Bad exchange! */ +        GNUNET_break_op (0); +        MAJ_parse_free (withdraw_spec); +        return GNUNET_SYSERR; +      } +      /* end type==WITHDRAW */ +    } +    else +    { +      /* unexpected 'type', protocol incompatibility, complain! */ +      GNUNET_break_op (0); +      return GNUNET_SYSERR; +    } +  } + +  /* check balance = total_in - total_out < withdraw-amount */ +  if (GNUNET_SYSERR == +      TALER_amount_subtract (balance, +                             &total_in, +                             &total_out)) +  { +    /* total_in < total_out, why did the exchange ever allow this!? */ +    GNUNET_break_op (0); +    return GNUNET_SYSERR; +  } + +  return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /reserve/status request. + * + * @param cls the `struct TALER_EXCHANGE_ReserveStatusHandle` + * @param eh curl handle of the request that finished + */ +static void +handle_reserve_status_finished (void *cls, +                                CURL *eh) +{ +  struct TALER_EXCHANGE_ReserveStatusHandle *wsh = cls; +  long response_code; +  json_t *json; + +  wsh->job = NULL; +  json = MAC_download_get_result (&wsh->db, +                                  eh, +                                  &response_code); +  switch (response_code) +  { +  case 0: +    break; +  case MHD_HTTP_OK: +    { +      /* TODO: move into separate function... */ +      json_t *history; +      unsigned int len; +      struct TALER_Amount balance; +      struct TALER_Amount balance_from_history; +      struct MAJ_Specification spec[] = { +        MAJ_spec_amount ("balance", &balance), +        MAJ_spec_end +      }; + +      if (GNUNET_OK != +          MAJ_parse_json (json, +                          spec)) +      { +        GNUNET_break_op (0); +        response_code = 0; +        break; +      } +      history = json_object_get (json, +                                 "history"); +      if (NULL == history) +      { +        GNUNET_break_op (0); +        response_code = 0; +        break; +      } +      len = json_array_size (history); +      { +        struct TALER_EXCHANGE_ReserveHistory rhistory[len]; + +        if (GNUNET_OK != +            parse_reserve_history (history, +                                   &wsh->reserve_pub, +                                   balance.currency, +                                   &balance_from_history, +                                   len, +                                   rhistory)) +        { +          GNUNET_break_op (0); +          response_code = 0; +          break; +        } +        if (0 != +            TALER_amount_cmp (&balance_from_history, +                              &balance)) +        { +          /* exchange cannot add up balances!? */ +          GNUNET_break_op (0); +          response_code = 0; +          break; +        } +        wsh->cb (wsh->cb_cls, +                 response_code, +                 json, +                 &balance, +                 len, +                 rhistory); +        wsh->cb = NULL; +      } +    } +    break; +  case MHD_HTTP_BAD_REQUEST: +    /* This should never happen, either us or the exchange is buggy +       (or API version conflict); just pass JSON reply to the application */ +    break; +  case MHD_HTTP_NOT_FOUND: +    /* Nothing really to verify, this should never +       happen, we should pass the JSON reply to the application */ +    break; +  case MHD_HTTP_INTERNAL_SERVER_ERROR: +    /* Server had an internal issue; we should retry, but this API +       leaves this to the application */ +    break; +  default: +    /* unexpected response code */ +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected response code %u\n", +                response_code); +    GNUNET_break (0); +    response_code = 0; +    break; +  } +  if (NULL != wsh->cb) +    wsh->cb (wsh->cb_cls, +             response_code, +             json, +             NULL, +             0, NULL); +  json_decref (json); +  TALER_EXCHANGE_reserve_status_cancel (wsh); +} + + +/** + * Submit a request to obtain the transaction history of a reserve + * from the exchange.  Note that while we return the full response to the + * caller for further processing, we do already verify that the + * response is well-formed (i.e. that signatures included in the + * response are all valid and add up to the balance).  If the exchange's + * reply is not well-formed, we return an HTTP status code of zero to + * @a cb. + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param reserve_pub public key of the reserve to inspect + * @param cb the callback to call when a reply for this request is available + * @param cb_cls closure for the above callback + * @return a handle for this request; NULL if the inputs are invalid (i.e. + *         signatures fail to verify).  In this case, the callback is not called. + */ +struct TALER_EXCHANGE_ReserveStatusHandle * +TALER_EXCHANGE_reserve_status (struct TALER_EXCHANGE_Handle *exchange, +                           const struct TALER_ReservePublicKeyP *reserve_pub, +                           TALER_EXCHANGE_ReserveStatusResultCallback cb, +                           void *cb_cls) +{ +  struct TALER_EXCHANGE_ReserveStatusHandle *wsh; +  struct TALER_EXCHANGE_Context *ctx; +  CURL *eh; +  char *pub_str; +  char *arg_str; + +  if (GNUNET_YES != +      MAH_handle_is_ready (exchange)) +  { +    GNUNET_break (0); +    return NULL; +  } +  pub_str = GNUNET_STRINGS_data_to_string_alloc (reserve_pub, +                                                 sizeof (struct TALER_ReservePublicKeyP)); +  GNUNET_asprintf (&arg_str, +                   "/reserve/status?reserve_pub=%s", +                   pub_str); +  GNUNET_free (pub_str); +  wsh = GNUNET_new (struct TALER_EXCHANGE_ReserveStatusHandle); +  wsh->exchange = exchange; +  wsh->cb = cb; +  wsh->cb_cls = cb_cls; +  wsh->reserve_pub = *reserve_pub; +  wsh->url = MAH_path_to_url (exchange, +                              arg_str); +  GNUNET_free (arg_str); + +  eh = curl_easy_init (); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_URL, +                                   wsh->url)); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_WRITEFUNCTION, +                                   &MAC_download_cb)); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_WRITEDATA, +                                   &wsh->db)); +  ctx = MAH_handle_to_context (exchange); +  wsh->job = MAC_job_add (ctx, +                          eh, +                          GNUNET_NO, +                          &handle_reserve_status_finished, +                          wsh); +  return wsh; +} + + +/** + * Cancel a withdraw status request.  This function cannot be used + * on a request handle if a response is already served for it. + * + * @param wsh the withdraw status request handle + */ +void +TALER_EXCHANGE_reserve_status_cancel (struct TALER_EXCHANGE_ReserveStatusHandle *wsh) +{ +  if (NULL != wsh->job) +  { +    MAC_job_cancel (wsh->job); +    wsh->job = NULL; +  } +  GNUNET_free_non_null (wsh->db.buf); +  GNUNET_free (wsh->url); +  GNUNET_free (wsh); +} + + +/* ********************** /reserve/withdraw ********************** */ + +/** + * @brief A Withdraw Sign Handle + */ +struct TALER_EXCHANGE_ReserveWithdrawHandle +{ + +  /** +   * The connection to exchange this request handle will use +   */ +  struct TALER_EXCHANGE_Handle *exchange; + +  /** +   * The url for this request. +   */ +  char *url; + +  /** +   * JSON encoding of the request to POST. +   */ +  char *json_enc; + +  /** +   * Handle for the request. +   */ +  struct MAC_Job *job; + +  /** +   * Function to call with the result. +   */ +  TALER_EXCHANGE_ReserveWithdrawResultCallback cb; + +  /** +   * Key used to blind the value. +   */ +  const struct TALER_DenominationBlindingKey *blinding_key; + +  /** +   * Denomination key we are withdrawing. +   */ +  const struct TALER_EXCHANGE_DenomPublicKey *pk; + +  /** +   * Closure for @a cb. +   */ +  void *cb_cls; + +  /** +   * Download buffer +   */ +  struct MAC_DownloadBuffer db; + +  /** +   * Hash of the public key of the coin we are signing. +   */ +  struct GNUNET_HashCode c_hash; + +  /** +   * Public key of the reserve we are withdrawing from. +   */ +  struct TALER_ReservePublicKeyP reserve_pub; + +}; + + +/** + * We got a 200 OK response for the /reserve/withdraw operation. + * Extract the coin's signature and return it to the caller. + * The signature we get from the exchange is for the blinded value. + * Thus, we first must unblind it and then should verify its + * validity against our coin's hash. + * + * If everything checks out, we return the unblinded signature + * to the application via the callback. + * + * @param wsh operation handle + * @param json reply from the exchange + * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors + */ +static int +reserve_withdraw_ok (struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh, +                  json_t *json) +{ +  struct GNUNET_CRYPTO_rsa_Signature *blind_sig; +  struct GNUNET_CRYPTO_rsa_Signature *sig; +  struct TALER_DenominationSignature dsig; +  struct MAJ_Specification spec[] = { +    MAJ_spec_rsa_signature ("ev_sig", &blind_sig), +    MAJ_spec_end +  }; + +  if (GNUNET_OK != +      MAJ_parse_json (json, +                      spec)) +  { +    GNUNET_break_op (0); +    return GNUNET_SYSERR; +  } +  sig = GNUNET_CRYPTO_rsa_unblind (blind_sig, +                                   wsh->blinding_key->rsa_blinding_key, +                                   wsh->pk->key.rsa_public_key); +  GNUNET_CRYPTO_rsa_signature_free (blind_sig); +  if (GNUNET_OK != +      GNUNET_CRYPTO_rsa_verify (&wsh->c_hash, +                                sig, +                                wsh->pk->key.rsa_public_key)) +  { +    GNUNET_break_op (0); +    GNUNET_CRYPTO_rsa_signature_free (sig); +    return GNUNET_SYSERR; +  } +  /* signature is valid, return it to the application */ +  dsig.rsa_signature = sig; +  wsh->cb (wsh->cb_cls, +           MHD_HTTP_OK, +           &dsig, +           json); +  /* make sure callback isn't called again after return */ +  wsh->cb = NULL; +  GNUNET_CRYPTO_rsa_signature_free (sig); +  return GNUNET_OK; +} + + +/** + * We got a 402 PAYMENT REQUIRED response for the /reserve/withdraw operation. + * Check the signatures on the withdraw transactions in the provided + * history and that the balances add up.  We don't do anything directly + * with the information, as the JSON will be returned to the application. + * However, our job is ensuring that the exchange followed the protocol, and + * this in particular means checking all of the signatures in the history. + * + * @param wsh operation handle + * @param json reply from the exchange + * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors + */ +static int +reserve_withdraw_payment_required (struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh, +                                   json_t *json) +{ +  struct TALER_Amount balance; +  struct TALER_Amount balance_from_history; +  struct TALER_Amount requested_amount; +  json_t *history; +  size_t len; +  struct MAJ_Specification spec[] = { +    MAJ_spec_amount ("balance", &balance), +    MAJ_spec_end +  }; + +  if (GNUNET_OK != +      MAJ_parse_json (json, +                      spec)) +  { +    GNUNET_break_op (0); +    return GNUNET_SYSERR; +  } +  history = json_object_get (json, +                             "history"); +  if (NULL == history) +  { +    GNUNET_break_op (0); +    return GNUNET_SYSERR; +  } + +  /* go over transaction history and compute +     total incoming and outgoing amounts */ +  len = json_array_size (history); +  { +    struct TALER_EXCHANGE_ReserveHistory rhistory[len]; + +    if (GNUNET_OK != +        parse_reserve_history (history, +                               &wsh->reserve_pub, +                               balance.currency, +                               &balance_from_history, +                               len, +                               rhistory)) +    { +      GNUNET_break_op (0); +      return GNUNET_SYSERR; +    } +  } + +  if (0 != +      TALER_amount_cmp (&balance_from_history, +                        &balance)) +  { +    /* exchange cannot add up balances!? */ +    GNUNET_break_op (0); +    return GNUNET_SYSERR; +  } +  /* Compute how much we expected to charge to the reserve */ +  if (GNUNET_OK != +      TALER_amount_add (&requested_amount, +                        &wsh->pk->value, +                        &wsh->pk->fee_withdraw)) +  { +    /* Overflow here? Very strange, our CPU must be fried... */ +    GNUNET_break (0); +    return GNUNET_SYSERR; +  } +  /* Check that funds were really insufficient */ +  if (0 >= TALER_amount_cmp (&requested_amount, +                             &balance)) +  { +    /* Requested amount is smaller or equal to reported balance, +       so this should not have failed. */ +    GNUNET_break_op (0); +    return GNUNET_SYSERR; +  } +  return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /reserve/withdraw request. + * + * @param cls the `struct TALER_EXCHANGE_ReserveWithdrawHandle` + * @param eh curl handle of the request that finished + */ +static void +handle_reserve_withdraw_finished (void *cls, +                                  CURL *eh) +{ +  struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh = cls; +  long response_code; +  json_t *json; + +  wsh->job = NULL; +  json = MAC_download_get_result (&wsh->db, +                                  eh, +                                  &response_code); +  switch (response_code) +  { +  case 0: +    break; +  case MHD_HTTP_OK: +    if (GNUNET_OK != +        reserve_withdraw_ok (wsh, +                          json)) +    { +      GNUNET_break_op (0); +      response_code = 0; +    } +    break; +  case MHD_HTTP_BAD_REQUEST: +    /* This should never happen, either us or the exchange is buggy +       (or API version conflict); just pass JSON reply to the application */ +    break; +  case MHD_HTTP_PAYMENT_REQUIRED: +    /* The exchange says that the reserve has insufficient funds; +       check the signatures in the history... */ +    if (GNUNET_OK != +        reserve_withdraw_payment_required (wsh, +                                        json)) +    { +      GNUNET_break_op (0); +      response_code = 0; +    } +    break; +  case MHD_HTTP_UNAUTHORIZED: +    GNUNET_break (0); +    /* Nothing really to verify, exchange says one of the signatures is +       invalid; as we checked them, this should never happen, we +       should pass the JSON reply to the application */ +    break; +  case MHD_HTTP_NOT_FOUND: +    /* Nothing really to verify, the exchange basically just says +       that it doesn't know this reserve.  Can happen if we +       query before the wire transfer went through. +       We should simply pass the JSON reply to the application. */ +    break; +  case MHD_HTTP_INTERNAL_SERVER_ERROR: +    /* Server had an internal issue; we should retry, but this API +       leaves this to the application */ +    break; +  default: +    /* unexpected response code */ +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected response code %u\n", +                response_code); +    GNUNET_break (0); +    response_code = 0; +    break; +  } +  if (NULL != wsh->cb) +    wsh->cb (wsh->cb_cls, +             response_code, +             NULL, +             json); +  json_decref (json); +  TALER_EXCHANGE_reserve_withdraw_cancel (wsh); +} + + +/** + * Withdraw a coin from the exchange using a /reserve/withdraw request.  Note + * that to ensure that no money is lost in case of hardware failures, + * the caller must have committed (most of) the arguments to disk + * before calling, and be ready to repeat the request with the same + * arguments in case of failures. + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param pk kind of coin to create + * @param reserve_priv private key of the reserve to withdraw from + * @param coin_priv where to store the coin's private key, + *        caller must have committed this value to disk before the call (with @a pk) + * @param blinding_key where to store the coin's blinding key + *        caller must have committed this value to disk before the call (with @a pk) + * @param res_cb the callback to call when the final result for this request is available + * @param res_cb_cls closure for the above callback + * @return #GNUNET_OK on success, #GNUNET_SYSERR + *         if the inputs are invalid (i.e. denomination key not with this exchange). + *         In this case, the callback is not called. + */ +struct TALER_EXCHANGE_ReserveWithdrawHandle * +TALER_EXCHANGE_reserve_withdraw (struct TALER_EXCHANGE_Handle *exchange, +                             const struct TALER_EXCHANGE_DenomPublicKey *pk, +                             const struct TALER_ReservePrivateKeyP *reserve_priv, +                             const struct TALER_CoinSpendPrivateKeyP *coin_priv, +                             const struct TALER_DenominationBlindingKey *blinding_key, +                             TALER_EXCHANGE_ReserveWithdrawResultCallback res_cb, +                             void *res_cb_cls) +{ +  struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh; +  struct TALER_WithdrawRequestPS req; +  struct TALER_ReserveSignatureP reserve_sig; +  struct TALER_CoinSpendPublicKeyP coin_pub; +  struct TALER_EXCHANGE_Context *ctx; +  struct TALER_Amount amount_with_fee; +  char *coin_ev; +  size_t coin_ev_size; +  json_t *withdraw_obj; +  CURL *eh; + +  wsh = GNUNET_new (struct TALER_EXCHANGE_ReserveWithdrawHandle); +  wsh->exchange = exchange; +  wsh->cb = res_cb; +  wsh->cb_cls = res_cb_cls; +  wsh->pk = pk; + +  GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, +                                      &coin_pub.eddsa_pub); +  GNUNET_CRYPTO_hash (&coin_pub.eddsa_pub, +                      sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey), +                      &wsh->c_hash); +  coin_ev_size = GNUNET_CRYPTO_rsa_blind (&wsh->c_hash, +                                          blinding_key->rsa_blinding_key, +                                          pk->key.rsa_public_key, +                                          &coin_ev); +  GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, +                                      &wsh->reserve_pub.eddsa_pub); +  req.purpose.size = htonl (sizeof (struct TALER_WithdrawRequestPS)); +  req.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW); +  req.reserve_pub = wsh->reserve_pub; +  if (GNUNET_OK != +      TALER_amount_add (&amount_with_fee, +                        &pk->fee_withdraw, +                        &pk->value)) +  { +    /* exchange gave us denomination keys that overflow like this!? */ +    GNUNET_break_op (0); +    GNUNET_free (coin_ev); +    GNUNET_free (wsh); +    return NULL; +  } +  TALER_amount_hton (&req.amount_with_fee, +                     &amount_with_fee); +  TALER_amount_hton (&req.withdraw_fee, +                     &pk->fee_withdraw); +  GNUNET_CRYPTO_rsa_public_key_hash (pk->key.rsa_public_key, +                                     &req.h_denomination_pub); +  GNUNET_CRYPTO_hash (coin_ev, +                      coin_ev_size, +                      &req.h_coin_envelope); +  GNUNET_assert (GNUNET_OK == +                 GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv, +                                           &req.purpose, +                                           &reserve_sig.eddsa_signature)); +  withdraw_obj = json_pack ("{s:o, s:o," /* denom_pub and coin_ev */ +                            " s:o, s:o}",/* reserve_pub and reserve_sig */ +                            "denom_pub", TALER_json_from_rsa_public_key (pk->key.rsa_public_key), +                            "coin_ev", TALER_json_from_data (coin_ev, +                                                             coin_ev_size), +                            "reserve_pub", TALER_json_from_data (&wsh->reserve_pub, +                                                                 sizeof (struct TALER_ReservePublicKeyP)), +                            "reserve_sig", TALER_json_from_data (&reserve_sig, +                                                                 sizeof (reserve_sig))); +  GNUNET_free (coin_ev); + +  wsh->blinding_key = blinding_key; +  wsh->url = MAH_path_to_url (exchange, "/reserve/withdraw"); + +  eh = curl_easy_init (); +  GNUNET_assert (NULL != (wsh->json_enc = +                          json_dumps (withdraw_obj, +                                      JSON_COMPACT))); +  json_decref (withdraw_obj); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_URL, +                                   wsh->url)); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_POSTFIELDS, +                                   wsh->json_enc)); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_POSTFIELDSIZE, +                                   strlen (wsh->json_enc))); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_WRITEFUNCTION, +                                   &MAC_download_cb)); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_WRITEDATA, +                                   &wsh->db)); +  ctx = MAH_handle_to_context (exchange); +  wsh->job = MAC_job_add (ctx, +                          eh, +                          GNUNET_YES, +                          &handle_reserve_withdraw_finished, +                          wsh); +  return wsh; +} + + +/** + * Cancel a withdraw status request.  This function cannot be used + * on a request handle if a response is already served for it. + * + * @param sign the withdraw sign request handle + */ +void +TALER_EXCHANGE_reserve_withdraw_cancel (struct TALER_EXCHANGE_ReserveWithdrawHandle *sign) +{ +  if (NULL != sign->job) +  { +    MAC_job_cancel (sign->job); +    sign->job = NULL; +  } +  GNUNET_free_non_null (sign->db.buf); +  GNUNET_free (sign->url); +  GNUNET_free (sign->json_enc); +  GNUNET_free (sign); +} + + +/* end of exchange_api_reserve.c */ diff --git a/src/exchange-lib/exchange_api_wire.c b/src/exchange-lib/exchange_api_wire.c new file mode 100644 index 00000000..fd40230c --- /dev/null +++ b/src/exchange-lib/exchange_api_wire.c @@ -0,0 +1,620 @@ +/* +  This file is part of TALER +  Copyright (C) 2014, 2015 GNUnet e.V. + +  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, If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/exchange_api_wire.c + * @brief Implementation of the /wire request of the exchange's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include "taler_exchange_service.h" +#include "exchange_api_common.h" +#include "exchange_api_json.h" +#include "exchange_api_context.h" +#include "exchange_api_handle.h" +#include "taler_signatures.h" + + +/** + * @brief A Wire Handle + */ +struct TALER_EXCHANGE_WireHandle +{ + +  /** +   * The connection to exchange this request handle will use +   */ +  struct TALER_EXCHANGE_Handle *exchange; + +  /** +   * The url for this request. +   */ +  char *url; + +  /** +   * Handle for the request. +   */ +  struct MAC_Job *job; + +  /** +   * Function to call with the result. +   */ +  TALER_EXCHANGE_WireResultCallback cb; + +  /** +   * Closure for @a cb. +   */ +  void *cb_cls; + +  /** +   * Download buffer +   */ +  struct MAC_DownloadBuffer db; + +  /** +   * Set to the "methods" JSON array returned by the +   * /wire request. +   */ +  json_t *methods; + +  /** +   * Current iteration offset in the @e methods array. +   */ +  unsigned int methods_off; + +}; + + +/** + * Verify that the signature on the "200 OK" response + * for /wire/sepa from the exchange is valid. + * + * @param wh wire handle + * @param json json reply with the signature + * @return #GNUNET_SYSERR if @a json is invalid, + *         #GNUNET_NO if the method is unknown, + *         #GNUNET_OK if the json is valid + */ +static int +verify_wire_sepa_signature_ok (const struct TALER_EXCHANGE_WireHandle *wh, +                               json_t *json) +{ +  struct TALER_MasterSignatureP exchange_sig; +  struct TALER_MasterWireSepaDetailsPS mp; +  const char *receiver_name; +  const char *iban; +  const char *bic; +  const struct TALER_EXCHANGE_Keys *key_state; +  struct GNUNET_HashContext *hc; +  struct MAJ_Specification spec[] = { +    MAJ_spec_fixed_auto ("sig", &exchange_sig), +    MAJ_spec_string ("receiver_name", &receiver_name), +    MAJ_spec_string ("iban", &iban), +    MAJ_spec_string ("bic", &bic), +    MAJ_spec_end +  }; + +  if (GNUNET_OK != +      MAJ_parse_json (json, +                      spec)) +  { +    GNUNET_break_op (0); +    return GNUNET_SYSERR; +  } + +  key_state = TALER_EXCHANGE_get_keys (wh->exchange); +  mp.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_SEPA_DETAILS); +  mp.purpose.size = htonl (sizeof (struct TALER_MasterWireSepaDetailsPS)); +  hc = GNUNET_CRYPTO_hash_context_start (); +  GNUNET_CRYPTO_hash_context_read (hc, +                                   receiver_name, +                                   strlen (receiver_name) + 1); +  GNUNET_CRYPTO_hash_context_read (hc, +                                   iban, +                                   strlen (iban) + 1); +  GNUNET_CRYPTO_hash_context_read (hc, +                                   bic, +                                   strlen (bic) + 1); +  GNUNET_CRYPTO_hash_context_finish (hc, +                                     &mp.h_sepa_details); + +  if (GNUNET_OK != +      GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_SEPA_DETAILS, +                                  &mp.purpose, +                                  &exchange_sig.eddsa_signature, +                                  &key_state->master_pub.eddsa_pub)) +  { +    GNUNET_break_op (0); +    MAJ_parse_free (spec); +    return GNUNET_SYSERR; +  } +  MAJ_parse_free (spec); +  return GNUNET_OK; +} + + +/** + * Verify that the signature on the "200 OK" response + * for /wire/METHOD from the exchange is valid. + * + * @param wh wire handle with key material + * @param method method to verify the reply for + * @param json json reply with the signature + * @return #GNUNET_SYSERR if @a json is invalid, + *         #GNUNET_NO if the method is unknown, + *         #GNUNET_OK if the json is valid + */ +static int +verify_wire_method_signature_ok (const struct TALER_EXCHANGE_WireHandle *wh, +                                 const char *method, +                                 json_t *json) +{ +  struct +  { +    /** +     * Name fo the method. +     */ +    const char *method; + +    /** +     * Handler to invoke to verify signature. +     * +     * @param wh wire handle with key material +     * @param json json reply with signature to verify +     */ +    int (*handler)(const struct TALER_EXCHANGE_WireHandle *wh, +                   json_t *json); +  } handlers[] = { +    { "sepa", &verify_wire_sepa_signature_ok }, +    { NULL, NULL } +  }; +  unsigned int i; + +  for (i=0;NULL != handlers[i].method; i++) +    if (0 == strcasecmp (handlers[i].method, +                         method)) +      return handlers[i].handler (wh, +                                  json); +  GNUNET_log (GNUNET_ERROR_TYPE_INFO, +              "Wire transfer method `%s' not supported\n", +              method); +  return GNUNET_NO; +} + + +/** + * Perform the next /wire/method request or signal + * the end of the iteration. + * + * @param wh the wire handle + * @return a handle for this request + */ +static void +request_wire_method (struct TALER_EXCHANGE_WireHandle *wh); + + +/** + * Function called when we're done processing the + * HTTP /wire/METHOD request. + * + * @param cls the `struct TALER_EXCHANGE_WireHandle` + * @param eh the curl request handle + */ +static void +handle_wire_method_finished (void *cls, +                             CURL *eh) +{ +  struct TALER_EXCHANGE_WireHandle *wh = cls; +  long response_code; +  json_t *json; + +  wh->job = NULL; +  json = MAC_download_get_result (&wh->db, +                                  eh, +                                  &response_code); +  switch (response_code) +  { +  case 0: +    break; +  case MHD_HTTP_OK: +    { +      const char *method; + +      method = json_string_value (json_array_get (wh->methods, +                                                  wh->methods_off - 1)); +      if (GNUNET_OK != +          verify_wire_method_signature_ok (wh, +                                           method, +                                           json)) +      { +        GNUNET_break_op (0); +        response_code = 0; +        break; +      } +      break; +    } +  case MHD_HTTP_FOUND: +    /* /wire/test returns a 302 redirect, we should just give +       this information back to the callback below */ +    break; +  case MHD_HTTP_BAD_REQUEST: +    /* This should never happen, either us or the exchange is buggy +       (or API version conflict); just pass JSON reply to the application */ +    break; +  case MHD_HTTP_NOT_FOUND: +    /* Nothing really to verify, this should never +       happen, we should pass the JSON reply to the application */ +    break; +  case MHD_HTTP_INTERNAL_SERVER_ERROR: +    /* Server had an internal issue; we should retry, but this API +       leaves this to the application */ +    break; +  default: +    /* unexpected response code */ +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected response code %u\n", +                response_code); +    GNUNET_break (0); +    response_code = 0; +    break; +  } +  if (0 == response_code) +  { +    /* signal end of iteration */ +    wh->cb (wh->cb_cls, +            0, +            NULL, +            NULL); +    json_decref (json); +    TALER_EXCHANGE_wire_cancel (wh); +    return; +  } +  /* pass on successful reply */ +  wh->cb (wh->cb_cls, +          response_code, +          json_string_value (json_array_get (wh->methods, +                                             wh->methods_off-1)), +          json); +  /* trigger request for the next /wire/method */ +  request_wire_method (wh); +} + + +/** + * Perform the next /wire/method request or signal + * the end of the iteration. + * + * @param wh the wire handle + * @return a handle for this request + */ +static void +request_wire_method (struct TALER_EXCHANGE_WireHandle *wh) +{ +  struct TALER_EXCHANGE_Context *ctx; +  CURL *eh; +  char *path; + +  if (json_array_size (wh->methods) <= wh->methods_off) +  { +    /* we are done, signal end of iteration */ +    wh->cb (wh->cb_cls, +            0, +            NULL, +            NULL); +    TALER_EXCHANGE_wire_cancel (wh); +    return; +  } +  GNUNET_free_non_null (wh->db.buf); +  wh->db.buf = NULL; +  wh->db.buf_size = 0; +  wh->db.eno = 0; +  GNUNET_free_non_null (wh->url); +  GNUNET_asprintf (&path, +                   "/wire/%s", +                   json_string_value (json_array_get (wh->methods, +                                                      wh->methods_off++))); +  wh->url = MAH_path_to_url (wh->exchange, +                             path); +  GNUNET_free (path); + +  eh = curl_easy_init (); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_URL, +                                   wh->url)); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_WRITEFUNCTION, +                                   &MAC_download_cb)); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_WRITEDATA, +                                   &wh->db)); +  /* The default is 'disabled', but let's be sure */ +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_FOLLOWLOCATION, +                                   (long) 0)); +  ctx = MAH_handle_to_context (wh->exchange); +  wh->job = MAC_job_add (ctx, +                         eh, +                         GNUNET_YES, +                         &handle_wire_method_finished, +                         wh); +  TALER_EXCHANGE_perform (ctx); +} + + +/** + * Verify that the signature on the "200 OK" response + * for /wire from the exchange is valid. + * + * @param wh wire handle + * @param json json reply with the signature + * @return NULL if @a json is invalid, otherwise the + *         "methods" array (with an RC of 1) + */ +static json_t * +verify_wire_signature_ok (const struct TALER_EXCHANGE_WireHandle *wh, +                          json_t *json) +{ +  struct TALER_ExchangeSignatureP exchange_sig; +  struct TALER_ExchangePublicKeyP exchange_pub; +  struct TALER_ExchangeWireSupportMethodsPS mp; +  json_t *methods; +  const struct TALER_EXCHANGE_Keys *key_state; +  struct GNUNET_HashContext *hc; +  struct MAJ_Specification spec[] = { +    MAJ_spec_fixed_auto ("sig", &exchange_sig), +    MAJ_spec_fixed_auto ("pub", &exchange_pub), +    MAJ_spec_json ("methods", &methods), +    MAJ_spec_end +  }; +  unsigned int i; + +  if (GNUNET_OK != +      MAJ_parse_json (json, +                      spec)) +  { +    GNUNET_break_op (0); +    return NULL; +  } +  if (! json_is_array (methods)) +  { +    GNUNET_break_op (0); +    MAJ_parse_free (spec); +    return NULL; +  } + +  key_state = TALER_EXCHANGE_get_keys (wh->exchange); +  if (GNUNET_OK != +      TALER_EXCHANGE_test_signing_key (key_state, +                                   &exchange_pub)) +  { +    GNUNET_break_op (0); +    return NULL; +  } +  hc = GNUNET_CRYPTO_hash_context_start (); +  for (i=0;i<json_array_size (methods);i++) +  { +    const json_t *element = json_array_get (methods, i); +    const char *method; + +    if (! json_is_string (element)) +    { +      GNUNET_CRYPTO_hash_context_abort (hc); +      GNUNET_break_op (0); +      MAJ_parse_free (spec); +      return NULL; +    } +    method = json_string_value (element); +    GNUNET_CRYPTO_hash_context_read (hc, +                                     method, +                                     strlen (method) + 1); +  } +  mp.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_WIRE_TYPES); +  mp.purpose.size = htonl (sizeof (struct TALER_ExchangeWireSupportMethodsPS)); +  GNUNET_CRYPTO_hash_context_finish (hc, +                                     &mp.h_wire_types); + +  if (GNUNET_OK != +      GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_WIRE_TYPES, +                                  &mp.purpose, +                                  &exchange_sig.eddsa_signature, +                                  &exchange_pub.eddsa_pub)) +  { +    GNUNET_break_op (0); +    MAJ_parse_free (spec); +    return NULL; +  } +  return methods; +} + + +/** + * Function called when we're done processing the + * HTTP /wire request. + * + * @param cls the `struct TALER_EXCHANGE_WireHandle` + * @param eh the curl request handle + */ +static void +handle_wire_finished (void *cls, +                      CURL *eh) +{ +  struct TALER_EXCHANGE_WireHandle *wh = cls; +  long response_code; +  json_t *json; + +  wh->job = NULL; +  json = MAC_download_get_result (&wh->db, +                                  eh, +                                  &response_code); +  switch (response_code) +  { +  case 0: +    break; +  case MHD_HTTP_OK: +    { +      json_t *methods; + +      if (NULL == +          (methods = verify_wire_signature_ok (wh, +                                               json))) +      { +        GNUNET_break_op (0); +        response_code = 0; +        break; +      } +      wh->methods = methods; +      request_wire_method (wh); +      return; +    } +    break; +  case MHD_HTTP_BAD_REQUEST: +    /* This should never happen, either us or the exchange is buggy +       (or API version conflict); just pass JSON reply to the application */ +    break; +  case MHD_HTTP_NOT_FOUND: +    /* Nothing really to verify, this should never +       happen, we should pass the JSON reply to the application */ +    break; +  case MHD_HTTP_INTERNAL_SERVER_ERROR: +    /* Server had an internal issue; we should retry, but this API +       leaves this to the application */ +    break; +  default: +    /* unexpected response code */ +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected response code %u\n", +                response_code); +    GNUNET_break (0); +    response_code = 0; +    break; +  } +  if (0 != response_code) +  { +    /* pass on successful reply */ +    wh->cb (wh->cb_cls, +            response_code, +            NULL, +            json); +  } +  /* signal end of iteration */ +  wh->cb (wh->cb_cls, +          0, +          NULL, +          NULL); +  if (NULL != json) +    json_decref (json); +  TALER_EXCHANGE_wire_cancel (wh); +} + + +/** + * Obtain information about a exchange's wire instructions. + * A exchange may provide wire instructions for creating + * a reserve.  The wire instructions also indicate + * which wire formats merchants may use with the exchange. + * This API is typically used by a wallet for wiring + * funds, and possibly by a merchant to determine + * supported wire formats. + * + * Note that while we return the (main) response verbatim to the + * caller for further processing, we do already verify that the + * response is well-formed (i.e. that signatures included in the + * response are all valid).  If the exchange's reply is not well-formed, + * we return an HTTP status code of zero to @a cb. + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param wire_cb the callback to call when a reply for this request is available + * @param wire_cb_cls closure for the above callback + * @return a handle for this request + */ +struct TALER_EXCHANGE_WireHandle * +TALER_EXCHANGE_wire (struct TALER_EXCHANGE_Handle *exchange, +                 TALER_EXCHANGE_WireResultCallback wire_cb, +                 void *wire_cb_cls) +{ +  struct TALER_EXCHANGE_WireHandle *wh; +  struct TALER_EXCHANGE_Context *ctx; +  CURL *eh; + +  if (GNUNET_YES != +      MAH_handle_is_ready (exchange)) +  { +    GNUNET_break (0); +    return NULL; +  } +  wh = GNUNET_new (struct TALER_EXCHANGE_WireHandle); +  wh->exchange = exchange; +  wh->cb = wire_cb; +  wh->cb_cls = wire_cb_cls; +  wh->url = MAH_path_to_url (exchange, "/wire"); + +  eh = curl_easy_init (); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_URL, +                                   wh->url)); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_WRITEFUNCTION, +                                   &MAC_download_cb)); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_WRITEDATA, +                                   &wh->db)); +  ctx = MAH_handle_to_context (exchange); +  wh->job = MAC_job_add (ctx, +                         eh, +                         GNUNET_YES, +                         &handle_wire_finished, +                         wh); +  return wh; +} + + +/** + * Cancel a wire information request.  This function cannot be used + * on a request handle if a response is already served for it. + * + * @param wh the wire information request handle + */ +void +TALER_EXCHANGE_wire_cancel (struct TALER_EXCHANGE_WireHandle *wh) +{ +  if (NULL != wh->job) +  { +    MAC_job_cancel (wh->job); +    wh->job = NULL; +  } +  if (NULL != wh->methods) +  { +    json_decref (wh->methods); +    wh->methods = NULL; +  } +  GNUNET_free_non_null (wh->db.buf); +  GNUNET_free (wh->url); +  GNUNET_free (wh); +} + + +/* end of exchange_api_wire.c */ diff --git a/src/exchange-lib/exchange_api_wire_deposits.c b/src/exchange-lib/exchange_api_wire_deposits.c new file mode 100644 index 00000000..281ae209 --- /dev/null +++ b/src/exchange-lib/exchange_api_wire_deposits.c @@ -0,0 +1,284 @@ +/* +  This file is part of TALER +  Copyright (C) 2014, 2015, 2016 GNUnet e.V. + +  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, If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/exchange_api_wire_deposits.c + * @brief Implementation of the /wire/deposits request of the exchange's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include "taler_exchange_service.h" +#include "exchange_api_common.h" +#include "exchange_api_json.h" +#include "exchange_api_context.h" +#include "exchange_api_handle.h" +#include "taler_signatures.h" + + +/** + * @brief A /wire/deposits Handle + */ +struct TALER_EXCHANGE_WireDepositsHandle +{ + +  /** +   * The connection to exchange this request handle will use +   */ +  struct TALER_EXCHANGE_Handle *exchange; + +  /** +   * The url for this request. +   */ +  char *url; + +  /** +   * Handle for the request. +   */ +  struct MAC_Job *job; + +  /** +   * Function to call with the result. +   */ +  TALER_EXCHANGE_WireDepositsCallback cb; + +  /** +   * Closure for @a cb. +   */ +  void *cb_cls; + +  /** +   * Download buffer +   */ +  struct MAC_DownloadBuffer db; + +}; + + +/** + * Function called when we're done processing the + * HTTP /wire/deposits request. + * + * @param cls the `struct TALER_EXCHANGE_WireDepositsHandle` + * @param eh the curl request handle + */ +static void +handle_wire_deposits_finished (void *cls, +                               CURL *eh) +{ +  struct TALER_EXCHANGE_WireDepositsHandle *wdh = cls; +  long response_code; +  json_t *json; + +  wdh->job = NULL; +  json = MAC_download_get_result (&wdh->db, +                                  eh, +                                  &response_code); +  switch (response_code) +  { +  case 0: +    break; +  case MHD_HTTP_OK: +    { +      json_t *details_j; +      struct GNUNET_HashCode h_wire; +      struct TALER_Amount total_amount; +      struct TALER_MerchantPublicKeyP merchant_pub; +      unsigned int num_details; +      struct MAJ_Specification spec[] = { +        MAJ_spec_fixed_auto ("H_wire", &h_wire), +        MAJ_spec_fixed_auto ("merchant_pub", &merchant_pub), +        MAJ_spec_amount ("total_amount", &total_amount), +        MAJ_spec_json ("details", &details_j), +        MAJ_spec_end +      }; + +      if (GNUNET_OK != +          MAJ_parse_json (json, +                          spec)) +      { +        GNUNET_break_op (0); +        response_code = 0; +        break; +      } +      num_details = json_array_size (details_j); +      { +        struct TALER_WireDepositDetails details[num_details]; +        unsigned int i; + +        for (i=0;i<num_details;i++) +        { +          struct TALER_WireDepositDetails *detail = &details[i]; +          struct json_t *detail_j = json_array_get (details_j, i); +          struct MAJ_Specification spec_detail[] = { +            MAJ_spec_fixed_auto ("H_contract", &detail->h_contract), +            MAJ_spec_amount ("deposit_value", &detail->coin_value), +            MAJ_spec_amount ("deposit_fee", &detail->coin_fee), +            MAJ_spec_uint64 ("transaction_id", &detail->transaction_id), +            MAJ_spec_fixed_auto ("coin_pub", &detail->coin_pub), +            MAJ_spec_end +          }; + +          if (GNUNET_OK != +              MAJ_parse_json (detail_j, +                              spec_detail)) +          { +            GNUNET_break_op (0); +            response_code = 0; +            break; +          } +        } +        if (0 == response_code) +          break; +        wdh->cb (wdh->cb_cls, +                 response_code, +                 json, +                 &h_wire, +                 &total_amount, +                 num_details, +                 details); +        json_decref (json); +        TALER_EXCHANGE_wire_deposits_cancel (wdh); +        return; +      } +    } +    break; +  case MHD_HTTP_BAD_REQUEST: +    /* This should never happen, either us or the exchange is buggy +       (or API version conflict); just pass JSON reply to the application */ +    break; +  case MHD_HTTP_UNAUTHORIZED: +    /* Nothing really to verify, exchange says one of the signatures is +       invalid; as we checked them, this should never happen, we +       should pass the JSON reply to the application */ +    break; +  case MHD_HTTP_NOT_FOUND: +    /* Exchange does not know about transaction; +       we should pass the reply to the application */ +    break; +  case MHD_HTTP_INTERNAL_SERVER_ERROR: +    /* Server had an internal issue; we should retry, but this API +       leaves this to the application */ +    break; +  default: +    /* unexpected response code */ +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected response code %u\n", +                response_code); +    GNUNET_break (0); +    response_code = 0; +    break; +  } +  wdh->cb (wdh->cb_cls, +           response_code, +           json, +           NULL, NULL, 0, NULL); +  json_decref (json); +  TALER_EXCHANGE_wire_deposits_cancel (wdh); +} + + +/** + * Query the exchange about which transactions were combined + * to create a wire transfer. + * + * @param exchange exchange to query + * @param wtid raw wire transfer identifier to get information about + * @param cb callback to call + * @param cb_cls closure for @a cb + * @return handle to cancel operation + */ +struct TALER_EXCHANGE_WireDepositsHandle * +TALER_EXCHANGE_wire_deposits (struct TALER_EXCHANGE_Handle *exchange, +                          const struct TALER_WireTransferIdentifierRawP *wtid, +                          TALER_EXCHANGE_WireDepositsCallback cb, +                          void *cb_cls) +{ +  struct TALER_EXCHANGE_WireDepositsHandle *wdh; +  struct TALER_EXCHANGE_Context *ctx; +  char *buf; +  char *path; +  CURL *eh; + +  if (GNUNET_YES != +      MAH_handle_is_ready (exchange)) +  { +    GNUNET_break (0); +    return NULL; +  } + +  wdh = GNUNET_new (struct TALER_EXCHANGE_WireDepositsHandle); +  wdh->exchange = exchange; +  wdh->cb = cb; +  wdh->cb_cls = cb_cls; + +  buf = GNUNET_STRINGS_data_to_string_alloc (wtid, +                                             sizeof (struct TALER_WireTransferIdentifierRawP)); +  GNUNET_asprintf (&path, +                   "/wire/deposits?wtid=%s", +                   buf); +  wdh->url = MAH_path_to_url (wdh->exchange, +                              path); +  GNUNET_free (buf); +  GNUNET_free (path); + +  eh = curl_easy_init (); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_URL, +                                   wdh->url)); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_WRITEFUNCTION, +                                   &MAC_download_cb)); +  GNUNET_assert (CURLE_OK == +                 curl_easy_setopt (eh, +                                   CURLOPT_WRITEDATA, +                                   &wdh->db)); +  ctx = MAH_handle_to_context (exchange); +  wdh->job = MAC_job_add (ctx, +                          eh, +                          GNUNET_YES, +                          &handle_wire_deposits_finished, +                          wdh); +  return wdh; +} + + +/** + * Cancel wire deposits request.  This function cannot be used on a request + * handle if a response is already served for it. + * + * @param wdh the wire deposits request handle + */ +void +TALER_EXCHANGE_wire_deposits_cancel (struct TALER_EXCHANGE_WireDepositsHandle *wdh) +{ +  if (NULL != wdh->job) +  { +    MAC_job_cancel (wdh->job); +    wdh->job = NULL; +  } +  GNUNET_free_non_null (wdh->db.buf); +  GNUNET_free (wdh->url); +  GNUNET_free (wdh); +} + + +/* end of exchange_api_wire_deposits.c */ diff --git a/src/exchange-lib/test-exchange-home/config/exchange-common.conf b/src/exchange-lib/test-exchange-home/config/exchange-common.conf new file mode 100644 index 00000000..80604658 --- /dev/null +++ b/src/exchange-lib/test-exchange-home/config/exchange-common.conf @@ -0,0 +1,31 @@ +[exchange] +# Currency supported by the exchange (can only be one) +CURRENCY = EUR + +# Wire format supported by the exchange +# We use 'test' for testing of the actual +# coin operations, and 'sepa' to test SEPA-specific routines. +WIREFORMAT = test sepa + +# HTTP port the exchange listens to +PORT = 8081 + +# Master public key used to sign the exchange's various keys +MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG + +# How to access our database +DB = postgres + +# Is this is a testcase, use transient DB actions? +TESTRUN = YES + +[exchangedb-postgres] +DB_CONN_STR = "postgres:///talercheck" + +[wire-sepa] +SEPA_RESPONSE_FILE = "test-exchange-home/sepa.json" + +[wire-test] +REDIRECT_URL = "http://www.taler.net/" +BANK_URI = "http://localhost/" +BANK_ACCOUNT_NO = 2 diff --git a/src/exchange-lib/test-exchange-home/config/exchange-keyup.conf b/src/exchange-lib/test-exchange-home/config/exchange-keyup.conf new file mode 100644 index 00000000..4a80da7e --- /dev/null +++ b/src/exchange-lib/test-exchange-home/config/exchange-keyup.conf @@ -0,0 +1,86 @@ +[exchange_keys] + +# how long is one signkey valid? +signkey_duration = 4 weeks + +# how long are the signatures with the signkey valid? +legal_duration = 2 years + +# how long do we generate denomination and signing keys +# ahead of time? +lookahead_sign = 32 weeks 1 day + +# how long do we provide to clients denomination and signing keys +# ahead of time? +lookahead_provide = 4 weeks 1 day + + +# Coin definitions are detected because the section +# name begins with "coin_".  The rest of the +# name is free, but of course following the convention +# of "coin_$CURRENCY[_$SUBUNIT]_$VALUE" make sense. +[coin_eur_ct_1] +value = EUR:0.01 +duration_overlap = 5 minutes +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = EUR:0.00 +fee_deposit = EUR:0.00 +fee_refresh = EUR:0.01 +rsa_keysize = 1024 + +[coin_eur_ct_10] +value = EUR:0.10 +duration_overlap = 5 minutes +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = EUR:0.01 +fee_deposit = EUR:0.01 +fee_refresh = EUR:0.03 +rsa_keysize = 1024 + +[coin_eur_1] +value = EUR:1 +duration_overlap = 5 minutes +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = EUR:0.01 +fee_deposit = EUR:0.01 +fee_refresh = EUR:0.03 +rsa_keysize = 1024 + +[coin_eur_5] +value = EUR:5 +duration_overlap = 5 minutes +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = EUR:0.01 +fee_deposit = EUR:0.01 +fee_refresh = EUR:0.03 +rsa_keysize = 1024 + +[coin_eur_10] +value = EUR:10 +duration_overlap = 5 minutes +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = EUR:0.01 +fee_deposit = EUR:0.01 +fee_refresh = EUR:0.03 +rsa_keysize = 1024 + +[coin_eur_1000] +value = EUR:1000 +duration_overlap = 5 minutes +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = EUR:0.01 +fee_deposit = EUR:0.01 +fee_refresh = EUR:0.03 +rsa_keysize = 2048 diff --git a/src/exchange-lib/test-exchange-home/master.priv b/src/exchange-lib/test-exchange-home/master.priv new file mode 100644 index 00000000..39492693 --- /dev/null +++ b/src/exchange-lib/test-exchange-home/master.priv @@ -0,0 +1 @@ +pÚ^ó-Ú33ˆ€XXÁ!ˆ\0qúýµmUþ_‰ˆ
\ No newline at end of file diff --git a/src/exchange-lib/test-exchange-home/sepa.json b/src/exchange-lib/test-exchange-home/sepa.json new file mode 100644 index 00000000..36d12f66 --- /dev/null +++ b/src/exchange-lib/test-exchange-home/sepa.json @@ -0,0 +1,6 @@ +{ +  "receiver_name": "Max Mustermann", +  "iban": "DE89370400440532013000", +  "bic": "COBADEFF370", +  "sig": "8M5YJXM68PRAXKH76HYEBCJW657B23JA0RFGNDMZK2379YZMT626H1BN89KC0M1KJBWGYEN5Z763Q0Y7MCTZQ6BPPT7D9KFCTW60C10" +}
\ No newline at end of file diff --git a/src/exchange-lib/test_exchange_api.c b/src/exchange-lib/test_exchange_api.c new file mode 100644 index 00000000..80e2c6ad --- /dev/null +++ b/src/exchange-lib/test_exchange_api.c @@ -0,0 +1,2599 @@ +/* +  This file is part of TALER +  Copyright (C) 2014, 2015, 2016 GNUnet e.V. + +  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, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange/test_exchange_api.c + * @brief testcase to test exchange's HTTP API interface + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_util.h" +#include "taler_signatures.h" +#include "taler_exchange_service.h" +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> + +/** + * Is the configuration file is set to include wire format 'test'? + */ +#define WIRE_TEST 1 + +/** + * Is the configuration file is set to include wire format 'sepa'? + */ +#define WIRE_SEPA 1 + +/** + * Main execution context for the main loop. + */ +static struct TALER_EXCHANGE_Context *ctx; + +/** + * Handle to access the exchange. + */ +static struct TALER_EXCHANGE_Handle *exchange; + +/** + * Task run on shutdown. + */ +static struct GNUNET_SCHEDULER_Task *shutdown_task; + +/** + * Task that runs the main event loop. + */ +static struct GNUNET_SCHEDULER_Task *ctx_task; + +/** + * Result of the testcases, #GNUNET_OK on success + */ +static int result; + + +/** + * Opcodes for the interpreter. + */ +enum OpCode +{ +  /** +   * Termination code, stops the interpreter loop (with success). +   */ +  OC_END = 0, + +  /** +   * Add funds to a reserve by (faking) incoming wire transfer. +   */ +  OC_ADMIN_ADD_INCOMING, + +  /** +   * Check status of a reserve. +   */ +  OC_WITHDRAW_STATUS, + +  /** +   * Withdraw a coin from a reserve. +   */ +  OC_WITHDRAW_SIGN, + +  /** +   * Deposit a coin (pay with it). +   */ +  OC_DEPOSIT, + +  /** +   * Melt a (set of) coins. +   */ +  OC_REFRESH_MELT, + +  /** +   * Complete melting session by withdrawing melted coins. +   */ +  OC_REFRESH_REVEAL, + +  /** +   * Verify exchange's /refresh/link by linking original private key to +   * results from #OC_REFRESH_REVEAL step. +   */ +  OC_REFRESH_LINK, + +  /** +   * Verify the exchange's /wire-method. +   */ +  OC_WIRE, + +  /** +   * Verify exchange's /wire/deposits method. +   */ +  OC_WIRE_DEPOSITS, + +  /** +   * Verify exchange's /deposit/wtid method. +   */ +  OC_DEPOSIT_WTID + +}; + + +/** + * Structure specifying details about a coin to be melted. + * Used in a NULL-terminated array as part of command + * specification. + */ +struct MeltDetails +{ + +  /** +   * Amount to melt (including fee). +   */ +  const char *amount; + +  /** +   * Reference to reserve_withdraw operations for coin to +   * be used for the /refresh/melt operation. +   */ +  const char *coin_ref; + +}; + + +/** + * Information about a fresh coin generated by the refresh operation. + */ +struct FreshCoin +{ + +  /** +   * If @e amount is NULL, this specifies the denomination key to +   * use.  Otherwise, this will be set (by the interpreter) to the +   * denomination PK matching @e amount. +   */ +  const struct TALER_EXCHANGE_DenomPublicKey *pk; + +  /** +   * Set (by the interpreter) to the exchange's signature over the +   * coin's public key. +   */ +  struct TALER_DenominationSignature sig; + +  /** +   * Set (by the interpreter) to the coin's private key. +   */ +  struct TALER_CoinSpendPrivateKeyP coin_priv; + +}; + + +/** + * Details for a exchange operation to execute. + */ +struct Command +{ +  /** +   * Opcode of the command. +   */ +  enum OpCode oc; + +  /** +   * Label for the command, can be NULL. +   */ +  const char *label; + +  /** +   * Which response code do we expect for this command? +   */ +  unsigned int expected_response_code; + +  /** +   * Details about the command. +   */ +  union +  { + +    /** +     * Information for a #OC_ADMIN_ADD_INCOMING command. +     */ +    struct +    { + +      /** +       * Label to another admin_add_incoming command if we +       * should deposit into an existing reserve, NULL if +       * a fresh reserve should be created. +       */ +      const char *reserve_reference; + +      /** +       * String describing the amount to add to the reserve. +       */ +      const char *amount; + +      /** +       * Wire details (JSON). +       */ +      const char *wire; + +      /** +       * Set (by the interpreter) to the reserve's private key +       * we used to fill the reserve. +       */ +      struct TALER_ReservePrivateKeyP reserve_priv; + +      /** +       * Set to the API's handle during the operation. +       */ +      struct TALER_EXCHANGE_AdminAddIncomingHandle *aih; + +    } admin_add_incoming; + +    /** +     * Information for a #OC_WITHDRAW_STATUS command. +     */ +    struct +    { + +      /** +       * Label to the #OC_ADMIN_ADD_INCOMING command which +       * created the reserve. +       */ +      const char *reserve_reference; + +      /** +       * Set to the API's handle during the operation. +       */ +      struct TALER_EXCHANGE_ReserveStatusHandle *wsh; + +      /** +       * Expected reserve balance. +       */ +      const char *expected_balance; + +    } reserve_status; + +    /** +     * Information for a #OC_WITHDRAW_SIGN command. +     */ +    struct +    { + +      /** +       * Which reserve should we withdraw from? +       */ +      const char *reserve_reference; + +      /** +       * String describing the denomination value we should withdraw. +       * A corresponding denomination key must exist in the exchange's +       * offerings.  Can be NULL if @e pk is set instead. +       */ +      const char *amount; + +      /** +       * If @e amount is NULL, this specifies the denomination key to +       * use.  Otherwise, this will be set (by the interpreter) to the +       * denomination PK matching @e amount. +       */ +      const struct TALER_EXCHANGE_DenomPublicKey *pk; + +      /** +       * Set (by the interpreter) to the exchange's signature over the +       * coin's public key. +       */ +      struct TALER_DenominationSignature sig; + +      /** +       * Set (by the interpreter) to the coin's private key. +       */ +      struct TALER_CoinSpendPrivateKeyP coin_priv; + +      /** +       * Blinding key used for the operation. +       */ +      struct TALER_DenominationBlindingKey blinding_key; + +      /** +       * Withdraw handle (while operation is running). +       */ +      struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh; + +    } reserve_withdraw; + +    /** +     * Information for a #OC_DEPOSIT command. +     */ +    struct +    { + +      /** +       * Amount to deposit. +       */ +      const char *amount; + +      /** +       * Reference to a reserve_withdraw operation for a coin to +       * be used for the /deposit operation. +       */ +      const char *coin_ref; + +      /** +       * If this @e coin_ref refers to an operation that generated +       * an array of coins, this value determines which coin to use. +       */ +      unsigned int coin_idx; + +      /** +       * JSON string describing the merchant's "wire details". +       */ +      const char *wire_details; + +      /** +       * JSON string describing the contract between the two parties. +       */ +      const char *contract; + +      /** +       * Transaction ID to use. +       */ +      uint64_t transaction_id; + +      /** +       * Relative time (to add to 'now') to compute the refund deadline. +       * Zero for no refunds. +       */ +      struct GNUNET_TIME_Relative refund_deadline; + +      /** +       * Set (by the interpreter) to a fresh private key of the merchant, +       * if @e refund_deadline is non-zero. +       */ +      struct TALER_MerchantPrivateKeyP merchant_priv; + +      /** +       * Deposit handle while operation is running. +       */ +      struct TALER_EXCHANGE_DepositHandle *dh; + +    } deposit; + +    /** +     * Information for a #OC_REFRESH_MELT command. +     */ +    struct +    { + +      /** +       * Information about coins to be melted. +       */ +      struct MeltDetails *melted_coins; + +      /** +       * Denominations of the fresh coins to withdraw. +       */ +      const char **fresh_amounts; + +      /** +       * Array of the public keys corresponding to +       * the @e fresh_amounts, set by the interpreter. +       */ +      const struct TALER_EXCHANGE_DenomPublicKey **fresh_pks; + +      /** +       * Melt handle while operation is running. +       */ +      struct TALER_EXCHANGE_RefreshMeltHandle *rmh; + +      /** +       * Data used in the refresh operation, set by the interpreter. +       */ +      char *refresh_data; + +      /** +       * Number of bytes in @e refresh_data, set by the interpreter. +       */ +      size_t refresh_data_length; + +      /** +       * Set by the interpreter (upon completion) to the noreveal +       * index selected by the exchange. +       */ +      uint16_t noreveal_index; + +    } refresh_melt; + +    /** +     * Information for a #OC_REFRESH_REVEAL command. +     */ +    struct +    { + +      /** +       * Melt operation this is the matching reveal for. +       */ +      const char *melt_ref; + +      /** +       * Reveal handle while operation is running. +       */ +      struct TALER_EXCHANGE_RefreshRevealHandle *rrh; + +      /** +       * Number of fresh coins withdrawn, set by the interpreter. +       * Length of the @e fresh_coins array. +       */ +      unsigned int num_fresh_coins; + +      /** +       * Information about coins withdrawn, set by the interpreter. +       */ +      struct FreshCoin *fresh_coins; + +    } refresh_reveal; + +    /** +     * Information for a #OC_REFRESH_LINK command. +     */ +    struct +    { + +      /** +       * Reveal operation this is the matching link for. +       */ +      const char *reveal_ref; + +      /** +       * Link handle while operation is running. +       */ +      struct TALER_EXCHANGE_RefreshLinkHandle *rlh; + +      /** +       * Which of the melted coins should be used for the linkage? +       */ +      unsigned int coin_idx; + +    } refresh_link; + +    /** +     * Information for the /wire command. +     */ +    struct { + +      /** +       * Handle to the wire request. +       */ +      struct TALER_EXCHANGE_WireHandle *wh; + +      /** +       * Format we expect to see, others will be *ignored*. +       */ +      const char *format; + +    } wire; + +    /** +     * Information for the /wire/deposits's command. +     */ +    struct { + +      /** +       * Handle to the wire deposits request. +       */ +      struct TALER_EXCHANGE_WireDepositsHandle *wdh; + +      /** +       * Reference to a /deposit/wtid command. If set, we use the +       * WTID from that command. +       */ +      const char *wtid_ref; + +      /** +       * WTID to use (used if @e wtid_ref is NULL). +       */ +      struct TALER_WireTransferIdentifierRawP wtid; + +      /* TODO: may want to add list of deposits we expected +         to see aggregated here in the future. */ + +    } wire_deposits; + +    /** +     * Information for the /deposit/wtid command. +     */ +    struct { + +      /** +       * Handle to the deposit wtid request. +       */ +      struct TALER_EXCHANGE_DepositWtidHandle *dwh; + +      /** +       * Which /deposit operation should we obtain WTID data for? +       */ +      const char *deposit_ref; + +      /** +       * What is the expected total amount? Only used if +       * @e expected_response_code was #MHD_HTTP_OK. +       */ +      struct TALER_Amount total_amount_expected; + +      /** +       * Wire transfer identifier, set if #MHD_HTTP_OK was the response code. +       */ +      struct TALER_WireTransferIdentifierRawP wtid; + +    } deposit_wtid; + +  } details; + +}; + + +/** + * State of the interpreter loop. + */ +struct InterpreterState +{ +  /** +   * Keys from the exchange. +   */ +  const struct TALER_EXCHANGE_Keys *keys; + +  /** +   * Commands the interpreter will run. +   */ +  struct Command *commands; + +  /** +   * Interpreter task (if one is scheduled). +   */ +  struct GNUNET_SCHEDULER_Task *task; + +  /** +   * Instruction pointer.  Tells #interpreter_run() which +   * instruction to run next. +   */ +  unsigned int ip; + +}; + + +/** + * Task that runs the context's event loop with the GNUnet scheduler. + * + * @param cls unused + * @param tc scheduler context (unused) + */ +static void +context_task (void *cls, +              const struct GNUNET_SCHEDULER_TaskContext *tc); + + +/** + * Run the context task, the working set has changed. + */ +static void +trigger_context_task () +{ +  GNUNET_SCHEDULER_cancel (ctx_task); +  ctx_task = GNUNET_SCHEDULER_add_now (&context_task, +                                       NULL); +} + + +/** + * The testcase failed, return with an error code. + * + * @param is interpreter state to clean up + */ +static void +fail (struct InterpreterState *is) +{ +  result = GNUNET_SYSERR; +  GNUNET_SCHEDULER_shutdown (); +} + + +/** + * Find a command by label. + * + * @param is interpreter state to search + * @param label label to look for + * @return NULL if command was not found + */ +static const struct Command * +find_command (const struct InterpreterState *is, +              const char *label) +{ +  unsigned int i; +  const struct Command *cmd; + +  if (NULL == label) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                "Attempt to lookup command for empty label\n"); +    return NULL; +  } +  for (i=0;OC_END != (cmd = &is->commands[i])->oc;i++) +    if ( (NULL != cmd->label) && +         (0 == strcmp (cmd->label, +                       label)) ) +      return cmd; +  GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +              "Command not found: %s\n", +              label); +  return NULL; +} + + +/** + * Run the main interpreter loop that performs exchange operations. + * + * @param cls contains the `struct InterpreterState` + * @param tc scheduler context + */ +static void +interpreter_run (void *cls, +                 const struct GNUNET_SCHEDULER_TaskContext *tc); + + +/** + * Function called upon completion of our /admin/add/incoming request. + * + * @param cls closure with the interpreter state + * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request + *                    0 if the exchange's reply is bogus (fails to follow the protocol) + * @param full_response full response from the exchange (for logging, in case of errors) + */ +static void +add_incoming_cb (void *cls, +                 unsigned int http_status, +                 json_t *full_response) +{ +  struct InterpreterState *is = cls; +  struct Command *cmd = &is->commands[is->ip]; + +  cmd->details.admin_add_incoming.aih = NULL; +  if (MHD_HTTP_OK != http_status) +  { +    GNUNET_break (0); +    fail (is); +    return; +  } +  is->ip++; +  is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, +                                       is); +} + + +/** + * Check if the given historic event @a h corresponds to the given + * command @a cmd. + * + * @param h event in history + * @param cmd an #OC_ADMIN_ADD_INCOMING command + * @return #GNUNET_OK if they match, #GNUNET_SYSERR if not + */ +static int +compare_admin_add_incoming_history (const struct TALER_EXCHANGE_ReserveHistory *h, +                                    const struct Command *cmd) +{ +  struct TALER_Amount amount; + +  if (TALER_EXCHANGE_RTT_DEPOSIT != h->type) +  { +    GNUNET_break_op (0); +    return GNUNET_SYSERR; +  } +  GNUNET_assert (GNUNET_OK == +                 TALER_string_to_amount (cmd->details.admin_add_incoming.amount, +                                         &amount)); +  if (0 != TALER_amount_cmp (&amount, +                             &h->amount)) +  { +    GNUNET_break_op (0); +    return GNUNET_SYSERR; +  } +  return GNUNET_OK; +} + + +/** + * Check if the given historic event @a h corresponds to the given + * command @a cmd. + * + * @param h event in history + * @param cmd an #OC_WITHDRAW_SIGN command + * @return #GNUNET_OK if they match, #GNUNET_SYSERR if not + */ +static int +compare_reserve_withdraw_history (const struct TALER_EXCHANGE_ReserveHistory *h, +                                  const struct Command *cmd) +{ +  struct TALER_Amount amount; +  struct TALER_Amount amount_with_fee; + +  if (TALER_EXCHANGE_RTT_WITHDRAWAL != h->type) +  { +    GNUNET_break_op (0); +    return GNUNET_SYSERR; +  } +  GNUNET_assert (GNUNET_OK == +                 TALER_string_to_amount (cmd->details.reserve_withdraw.amount, +                                         &amount)); +  GNUNET_assert (GNUNET_OK == +                 TALER_amount_add (&amount_with_fee, +                                   &amount, +                                   &cmd->details.reserve_withdraw.pk->fee_withdraw)); +  if (0 != TALER_amount_cmp (&amount_with_fee, +                             &h->amount)) +  { +    GNUNET_break_op (0); +    return GNUNET_SYSERR; +  } +  return GNUNET_OK; +} + + +/** + * Function called with the result of a /reserve/status request. + * + * @param cls closure with the interpreter state + * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request + *                    0 if the exchange's reply is bogus (fails to follow the protocol) + * @param[in] json original response in JSON format (useful only for diagnostics) + * @param balance current balance in the reserve, NULL on error + * @param history_length number of entries in the transaction history, 0 on error + * @param history detailed transaction history, NULL on error + */ +static void +reserve_status_cb (void *cls, +                   unsigned int http_status, +                   json_t *json, +                   const struct TALER_Amount *balance, +                   unsigned int history_length, +                   const struct TALER_EXCHANGE_ReserveHistory *history) +{ +  struct InterpreterState *is = cls; +  struct Command *cmd = &is->commands[is->ip]; +  struct Command *rel; +  unsigned int i; +  unsigned int j; +  struct TALER_Amount amount; + +  cmd->details.reserve_status.wsh = NULL; +  if (cmd->expected_response_code != http_status) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected response code %u to command %s\n", +                http_status, +                cmd->label); +    GNUNET_break (0); +    json_dumpf (json, stderr, 0); +    fail (is); +    return; +  } +  switch (http_status) +  { +  case MHD_HTTP_OK: +    /* FIXME: note that history events may come in a different +       order than the commands. However, for now this works... */ +    j = 0; +    for (i=0;i<is->ip;i++) +    { +      switch ((rel = &is->commands[i])->oc) +      { +      case OC_ADMIN_ADD_INCOMING: +        if ( ( (NULL != rel->label) && +               (0 == strcmp (cmd->details.reserve_status.reserve_reference, +                             rel->label) ) ) || +             ( (NULL != rel->details.admin_add_incoming.reserve_reference) && +               (0 == strcmp (cmd->details.reserve_status.reserve_reference, +                             rel->details.admin_add_incoming.reserve_reference) ) ) ) +        { +          if (GNUNET_OK != +              compare_admin_add_incoming_history (&history[j], +                                                  rel)) +          { +            GNUNET_break (0); +            fail (is); +            return; +          } +          j++; +        } +        break; +      case OC_WITHDRAW_SIGN: +        if (0 == strcmp (cmd->details.reserve_status.reserve_reference, +                         rel->details.reserve_withdraw.reserve_reference)) +        { +          if (GNUNET_OK != +              compare_reserve_withdraw_history (&history[j], +                                             rel)) +          { +            GNUNET_break (0); +            fail (is); +            return; +          } +          j++; +        } +        break; +      default: +        /* unreleated, just skip */ +        break; +      } +    } +    if (j != history_length) +    { +      GNUNET_break (0); +      fail (is); +      return; +    } +    if (NULL != cmd->details.reserve_status.expected_balance) +    { +      GNUNET_assert (GNUNET_OK == +                     TALER_string_to_amount (cmd->details.reserve_status.expected_balance, +                                             &amount)); +      if (0 != TALER_amount_cmp (&amount, +                                 balance)) +      { +        GNUNET_break (0); +        fail (is); +        return; +      } +    } +    break; +  default: +    /* Unsupported status code (by test harness) */ +    GNUNET_break (0); +    break; +  } +  is->ip++; +  is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, +                                       is); +} + + +/** + * Function called upon completion of our /reserve/withdraw request. + * + * @param cls closure with the interpreter state + * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request + *                    0 if the exchange's reply is bogus (fails to follow the protocol) + * @param sig signature over the coin, NULL on error + * @param full_response full response from the exchange (for logging, in case of errors) + */ +static void +reserve_withdraw_cb (void *cls, +                     unsigned int http_status, +                     const struct TALER_DenominationSignature *sig, +                     json_t *full_response) +{ +  struct InterpreterState *is = cls; +  struct Command *cmd = &is->commands[is->ip]; + +  cmd->details.reserve_withdraw.wsh = NULL; +  if (cmd->expected_response_code != http_status) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected response code %u to command %s\n", +                http_status, +                cmd->label); +    json_dumpf (full_response, stderr, 0); +    GNUNET_break (0); +    fail (is); +    return; +  } +  switch (http_status) +  { +  case MHD_HTTP_OK: +    if (NULL == sig) +    { +      GNUNET_break (0); +      fail (is); +      return; +    } +    cmd->details.reserve_withdraw.sig.rsa_signature +      = GNUNET_CRYPTO_rsa_signature_dup (sig->rsa_signature); +    break; +  case MHD_HTTP_PAYMENT_REQUIRED: +    /* nothing to check */ +    break; +  default: +    /* Unsupported status code (by test harness) */ +    GNUNET_break (0); +    break; +  } +  is->ip++; +  is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, +                                       is); +} + + +/** + * Function called with the result of a /deposit operation. + * + * @param cls closure with the interpreter state + * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful deposit; + *                    0 if the exchange's reply is bogus (fails to follow the protocol) + * @param obj the received JSON reply, should be kept as proof (and, in case of errors, + *            be forwarded to the customer) + */ +static void +deposit_cb (void *cls, +            unsigned int http_status, +            json_t *obj) +{ +  struct InterpreterState *is = cls; +  struct Command *cmd = &is->commands[is->ip]; + +  cmd->details.deposit.dh = NULL; +  if (cmd->expected_response_code != http_status) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected response code %u to command %s\n", +                http_status, +                cmd->label); +    json_dumpf (obj, stderr, 0); +    fail (is); +    return; +  } +  is->ip++; +  is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, +                                       is); +} + + +/** + * Function called with the result of the /refresh/melt operation. + * + * @param cls closure with the interpreter state + * @param http_status HTTP response code, never #MHD_HTTP_OK (200) as for successful intermediate response this callback is skipped. + *                    0 if the exchange's reply is bogus (fails to follow the protocol) + * @param noreveal_index choice by the exchange in the cut-and-choose protocol, + *                    UINT16_MAX on error + * @param full_response full response from the exchange (for logging, in case of errors) + */ +static void +melt_cb (void *cls, +         unsigned int http_status, +         uint16_t noreveal_index, +         json_t *full_response) +{ +  struct InterpreterState *is = cls; +  struct Command *cmd = &is->commands[is->ip]; + +  cmd->details.refresh_melt.rmh = NULL; +  if (cmd->expected_response_code != http_status) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected response code %u to command %s\n", +                http_status, +                cmd->label); +    json_dumpf (full_response, stderr, 0); +    fail (is); +    return; +  } +  cmd->details.refresh_melt.noreveal_index = noreveal_index; +  is->ip++; +  is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, +                                       is); +} + + +/** + * Function called with the result of the /refresh/reveal operation. + * + * @param cls closure with the interpreter state + * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request + *                    0 if the exchange's reply is bogus (fails to follow the protocol) + * @param num_coins number of fresh coins created, length of the @a sigs and @a coin_privs arrays, 0 if the operation failed + * @param coin_privs array of @a num_coins private keys for the coins that were created, NULL on error + * @param sigs array of signature over @a num_coins coins, NULL on error + * @param full_response full response from the exchange (for logging, in case of errors) + */ +static void +reveal_cb (void *cls, +           unsigned int http_status, +           unsigned int num_coins, +           const struct TALER_CoinSpendPrivateKeyP *coin_privs, +           const struct TALER_DenominationSignature *sigs, +           json_t *full_response) +{ +  struct InterpreterState *is = cls; +  struct Command *cmd = &is->commands[is->ip]; +  const struct Command *ref; +  unsigned int i; + +  cmd->details.refresh_reveal.rrh = NULL; +  if (cmd->expected_response_code != http_status) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected response code %u to command %s\n", +                http_status, +                cmd->label); +    json_dumpf (full_response, stderr, 0); +    fail (is); +    return; +  } +  ref = find_command (is, +                      cmd->details.refresh_reveal.melt_ref); +  cmd->details.refresh_reveal.num_fresh_coins = num_coins; +  switch (http_status) +  { +  case MHD_HTTP_OK: +    cmd->details.refresh_reveal.fresh_coins +      = GNUNET_new_array (num_coins, +                          struct FreshCoin); +    for (i=0;i<num_coins;i++) +    { +      struct FreshCoin *fc = &cmd->details.refresh_reveal.fresh_coins[i]; + +      fc->pk = ref->details.refresh_melt.fresh_pks[i]; +      fc->coin_priv = coin_privs[i]; +      fc->sig.rsa_signature +        = GNUNET_CRYPTO_rsa_signature_dup (sigs[i].rsa_signature); +    } +    break; +  default: +    break; +  } + +  is->ip++; +  is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, +                                       is); +} + + +/** + * Function called with the result of a /refresh/link operation. + * + * @param cls closure with the interpreter state + * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request + *                    0 if the exchange's reply is bogus (fails to follow the protocol) + * @param num_coins number of fresh coins created, length of the @a sigs and @a coin_privs arrays, 0 if the operation failed + * @param coin_privs array of @a num_coins private keys for the coins that were created, NULL on error + * @param sigs array of signature over @a num_coins coins, NULL on error + * @param pubs array of public keys for the @a sigs, NULL on error + * @param full_response full response from the exchange (for logging, in case of errors) + */ +static void +link_cb (void *cls, +         unsigned int http_status, +         unsigned int num_coins, +         const struct TALER_CoinSpendPrivateKeyP *coin_privs, +         const struct TALER_DenominationSignature *sigs, +         const struct TALER_DenominationPublicKey *pubs, +         json_t *full_response) +{ +  struct InterpreterState *is = cls; +  struct Command *cmd = &is->commands[is->ip]; +  const struct Command *ref; +  unsigned int i; +  unsigned int j; +  unsigned int found; + +  cmd->details.refresh_link.rlh = NULL; +  if (cmd->expected_response_code != http_status) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected response code %u to command %s\n", +                http_status, +                cmd->label); +    json_dumpf (full_response, stderr, 0); +    fail (is); +    return; +  } +  ref = find_command (is, +                      cmd->details.refresh_link.reveal_ref); +  switch (http_status) +  { +  case MHD_HTTP_OK: +    /* check that number of coins returned matches */ +    if (num_coins != ref->details.refresh_reveal.num_fresh_coins) +    { +      GNUNET_break (0); +      fail (is); +      return; +    } +    /* check that the coins match */ +    for (i=0;i<num_coins;i++) +      for (j=i+1;j<num_coins;j++) +	if (0 == memcmp (&coin_privs[i], +			 &coin_privs[j], +			 sizeof (struct TALER_CoinSpendPrivateKeyP))) +	  GNUNET_break (0); +    /* Note: coins might be legitimately permutated in here... */ +    found = 0; +    for (i=0;i<num_coins;i++) +      for (j=0;j<num_coins;j++) +      { +	const struct FreshCoin *fc; + +	fc = &ref->details.refresh_reveal.fresh_coins[j]; +	if ( (0 == memcmp (&coin_privs[i], +			   &fc->coin_priv, +			   sizeof (struct TALER_CoinSpendPrivateKeyP))) && +	     (0 == GNUNET_CRYPTO_rsa_signature_cmp (fc->sig.rsa_signature, +						    sigs[i].rsa_signature)) && +	     (0 == GNUNET_CRYPTO_rsa_public_key_cmp (fc->pk->key.rsa_public_key, +						     pubs[i].rsa_public_key)) ) +	{ +	  found++; +	  break; +	} +      } +    if (found != num_coins) +    { +      fprintf (stderr, +	       "Only %u/%u coins match expectations\n", +	       found, +	       num_coins); +      GNUNET_break (0); +      fail (is); +      return; +    } +    break; +  default: +    break; +  } +  is->ip++; +  is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, +                                       is); +} + + +/** + * Find denomination key matching the given amount. + * + * @param keys array of keys to search + * @param amount coin value to look for + * @return NULL if no matching key was found + */ +static const struct TALER_EXCHANGE_DenomPublicKey * +find_pk (const struct TALER_EXCHANGE_Keys *keys, +         const struct TALER_Amount *amount) +{ +  unsigned int i; +  struct GNUNET_TIME_Absolute now; +  struct TALER_EXCHANGE_DenomPublicKey *pk; +  char *str; + +  now = GNUNET_TIME_absolute_get (); +  for (i=0;i<keys->num_denom_keys;i++) +  { +    pk = &keys->denom_keys[i]; +    if ( (0 == TALER_amount_cmp (amount, +                                 &pk->value)) && +         (now.abs_value_us >= pk->valid_from.abs_value_us) && +         (now.abs_value_us < pk->withdraw_valid_until.abs_value_us) ) +      return pk; +  } +  /* do 2nd pass to check if expiration times are to blame for failure */ +  str = TALER_amount_to_string (amount); +  for (i=0;i<keys->num_denom_keys;i++) +  { +    pk = &keys->denom_keys[i]; +    if ( (0 == TALER_amount_cmp (amount, +                                 &pk->value)) && +         ( (now.abs_value_us < pk->valid_from.abs_value_us) || +           (now.abs_value_us > pk->withdraw_valid_until.abs_value_us) ) ) +    { +      GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                  "Have denomination key for `%s', but with wrong expiration range %llu vs [%llu,%llu)\n", +                  str, +                  now.abs_value_us, +                  pk->valid_from.abs_value_us, +                  pk->withdraw_valid_until.abs_value_us); +      GNUNET_free (str); +      return NULL; +    } +  } +  GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +              "No denomination key for amount %s found\n", +              str); +  GNUNET_free (str); +  return NULL; +} + + +/** + * Callbacks called with the result(s) of a + * wire format inquiry request to the exchange. + * + * The callback is invoked multiple times, once for each supported @a + * method.  Finally, it is invoked one more time with cls/0/NULL/NULL + * to indicate the end of the iteration.  If any request fails to + * generate a valid response from the exchange, @a http_status will also + * be zero and the iteration will also end.  Thus, the iteration + * always ends with a final call with an @a http_status of 0. If the + * @a http_status is already 0 on the first call, then the response to + * the /wire request was invalid.  Later, clients can tell the + * difference between @a http_status of 0 indicating a failed + * /wire/method request and a regular end of the iteration by @a + * method being non-NULL.  If the exchange simply correctly asserts that + * it does not support any methods, @a method will be NULL but the @a + * http_status will be #MHD_HTTP_OK for the first call (followed by a + * cls/0/NULL/NULL call to signal the end of the iteration). + * + * @param cls closure with the interpreter state + * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful request; + *                    0 if the exchange's reply is bogus (fails to follow the protocol) + * @param method wire format method supported, i.e. "test" or "sepa", or NULL + *            if already the /wire request failed. + * @param obj the received JSON reply, if successful this should be the wire + *            format details as provided by /wire/METHOD/, or NULL if the + *            reply was not in JSON format (in this case, the client might + *            want to do an HTTP request to /wire/METHOD/ with a browser to + *            provide more information to the user about the @a method). + */ +static void +wire_cb (void *cls, +         unsigned int http_status, +         const char *method, +         json_t *obj) +{ +  struct InterpreterState *is = cls; +  struct Command *cmd = &is->commands[is->ip]; + +  if (0 == http_status) +  { +    /* 0 always signals the end of the iteration */ +    cmd->details.wire.wh = NULL; +  } +  else if ( (NULL != method) && +            (0 != strcasecmp (method, +                              cmd->details.wire.format)) ) +  { +    /* not the method we care about, skip */ +    return; +  } +  if (cmd->expected_response_code != http_status) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected response code %u to command %s/%s\n", +                http_status, +                cmd->label, +                method); +    json_dumpf (obj, stderr, 0); +    fail (is); +    return; +  } +  if (0 == http_status) +  { +    /* end of iteration, move to next command */ +    is->ip++; +    is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, +                                         is); +    return; +  } +  /* For now, we only support to be called only once +     with a "positive" result; so we switch to an +     expected value of 0 for the 2nd iteration */ +  cmd->expected_response_code = 0; +} + + +/** + * Function called with detailed wire transfer data, including all + * of the coin transactions that were combined into the wire transfer. + * + * @param cls closure + * @param http_status HTTP status code we got, 0 on exchange protocol violation + * @param json original json reply (may include signatures, those have then been + *        validated already) + * @param wtid extracted wire transfer identifier, or NULL if the exchange could + *             not provide any (set only if @a http_status is #MHD_HTTP_OK) + * @param total_amount total amount of the wire transfer, or NULL if the exchange could + *             not provide any @a wtid (set only if @a http_status is #MHD_HTTP_OK) + * @param details_length length of the @a details array + * @param details array with details about the combined transactions + */ +static void +wire_deposits_cb (void *cls, +                  unsigned int http_status, +                  json_t *json, +                  const struct GNUNET_HashCode *h_wire, +                  const struct TALER_Amount *total_amount, +                  unsigned int details_length, +                  const struct TALER_WireDepositDetails *details) +{ +  struct InterpreterState *is = cls; +  struct Command *cmd = &is->commands[is->ip]; +  const struct Command *ref; + +  cmd->details.wire_deposits.wdh = NULL; +  ref = find_command (is, +                      cmd->details.wire_deposits.wtid_ref); +  if (cmd->expected_response_code != http_status) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected response code %u to command %s\n", +                http_status, +                cmd->label); +    json_dumpf (json, stderr, 0); +    fail (is); +    return; +  } +  switch (http_status) +  { +  case MHD_HTTP_OK: +    if (0 != TALER_amount_cmp (total_amount, +                               &ref->details.deposit_wtid.total_amount_expected)) +    { +      GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                  "Total amount missmatch to command %s\n", +                  http_status, +                  cmd->label); +      json_dumpf (json, stderr, 0); +      fail (is); +      return; +    } +    if (NULL != ref->details.deposit_wtid.deposit_ref) +    { +      const struct Command *dep; +      struct GNUNET_HashCode hw; + +      dep = find_command (is, +                          ref->details.deposit_wtid.deposit_ref); +      GNUNET_CRYPTO_hash (dep->details.deposit.wire_details, +                          strlen (dep->details.deposit.wire_details), +                          &hw); +      if (0 != memcmp (&hw, +                       h_wire, +                       sizeof (struct GNUNET_HashCode))) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                    "Wire hash missmatch to command %s\n", +                    cmd->label); +        json_dumpf (json, stderr, 0); +        fail (is); +        return; +      } +    } +    break; +  default: +    break; +  } + +  /* move to next command */ +  is->ip++; +  is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, +                                       is); +} + + +/** + * Function called with detailed wire transfer data. + * + * @param cls closure + * @param http_status HTTP status code we got, 0 on exchange protocol violation + * @param json original json reply (may include signatures, those have then been + *        validated already) + * @param wtid wire transfer identifier used by the exchange, NULL if exchange did not + *                  yet execute the transaction + * @param execution_time actual or planned execution time for the wire transfer + * @param coin_contribution contribution to the @a total_amount of the deposited coin (may be NULL) + * @param total_amount total amount of the wire transfer, or NULL if the exchange could + *             not provide any @a wtid (set only if @a http_status is #MHD_HTTP_OK) + */ +static void +deposit_wtid_cb (void *cls, +                 unsigned int http_status, +                 json_t *json, +                 const struct TALER_WireTransferIdentifierRawP *wtid, +                 struct GNUNET_TIME_Absolute execution_time, +                 const struct TALER_Amount *coin_contribution) +{ +  struct InterpreterState *is = cls; +  struct Command *cmd = &is->commands[is->ip]; + +  cmd->details.deposit_wtid.dwh = NULL; +  if (cmd->expected_response_code != http_status) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected response code %u to command %s\n", +                http_status, +                cmd->label); +    json_dumpf (json, stderr, 0); +    fail (is); +    return; +  } +  switch (http_status) +  { +  case MHD_HTTP_OK: +    cmd->details.deposit_wtid.wtid = *wtid; +    break; +  default: +    break; +  } + +  /* move to next command */ +  is->ip++; +  is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, +                                       is); +} + + +/** + * Run the main interpreter loop that performs exchange operations. + * + * @param cls contains the `struct InterpreterState` + * @param tc scheduler context + */ +static void +interpreter_run (void *cls, +                 const struct GNUNET_SCHEDULER_TaskContext *tc) +{ +  struct InterpreterState *is = cls; +  struct Command *cmd = &is->commands[is->ip]; +  const struct Command *ref; +  struct TALER_ReservePublicKeyP reserve_pub; +  struct TALER_CoinSpendPublicKeyP coin_pub; +  struct TALER_Amount amount; +  struct GNUNET_TIME_Absolute execution_date; +  json_t *wire; + +  is->task = NULL; +  if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN)) +  { +    fprintf (stderr, +             "Test aborted by shutdown request\n"); +    fail (is); +    return; +  } +  switch (cmd->oc) +  { +  case OC_END: +    result = GNUNET_OK; +    GNUNET_SCHEDULER_shutdown (); +    return; +  case OC_ADMIN_ADD_INCOMING: +    if (NULL != +        cmd->details.admin_add_incoming.reserve_reference) +    { +      ref = find_command (is, +                          cmd->details.admin_add_incoming.reserve_reference); +      GNUNET_assert (NULL != ref); +      GNUNET_assert (OC_ADMIN_ADD_INCOMING == ref->oc); +      cmd->details.admin_add_incoming.reserve_priv +        = ref->details.admin_add_incoming.reserve_priv; +    } +    else +    { +      struct GNUNET_CRYPTO_EddsaPrivateKey *priv; + +      priv = GNUNET_CRYPTO_eddsa_key_create (); +      cmd->details.admin_add_incoming.reserve_priv.eddsa_priv = *priv; +      GNUNET_free (priv); +    } +    GNUNET_CRYPTO_eddsa_key_get_public (&cmd->details.admin_add_incoming.reserve_priv.eddsa_priv, +                                        &reserve_pub.eddsa_pub); +    if (GNUNET_OK != +        TALER_string_to_amount (cmd->details.admin_add_incoming.amount, +                                &amount)) +    { +      GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                  "Failed to parse amount `%s' at %u\n", +                  cmd->details.admin_add_incoming.amount, +                  is->ip); +      fail (is); +      return; +    } +    wire = json_loads (cmd->details.admin_add_incoming.wire, +                       JSON_REJECT_DUPLICATES, +                       NULL); +    if (NULL == wire) +    { +      GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                  "Failed to parse wire details `%s' at %u\n", +                  cmd->details.admin_add_incoming.wire, +                  is->ip); +      fail (is); +      return; +    } +    execution_date = GNUNET_TIME_absolute_get (); +    TALER_round_abs_time (&execution_date); +    cmd->details.admin_add_incoming.aih +      = TALER_EXCHANGE_admin_add_incoming (exchange, +                                       &reserve_pub, +                                       &amount, +                                       execution_date, +                                       wire, +                                       &add_incoming_cb, +                                       is); +    if (NULL == cmd->details.admin_add_incoming.aih) +    { +      GNUNET_break (0); +      fail (is); +      return; +    } +    trigger_context_task (); +    return; +  case OC_WITHDRAW_STATUS: +    GNUNET_assert (NULL != +                   cmd->details.reserve_status.reserve_reference); +    ref = find_command (is, +                        cmd->details.reserve_status.reserve_reference); +    GNUNET_assert (NULL != ref); +    GNUNET_assert (OC_ADMIN_ADD_INCOMING == ref->oc); +    GNUNET_CRYPTO_eddsa_key_get_public (&ref->details.admin_add_incoming.reserve_priv.eddsa_priv, +                                        &reserve_pub.eddsa_pub); +    cmd->details.reserve_status.wsh +      = TALER_EXCHANGE_reserve_status (exchange, +                                   &reserve_pub, +                                   &reserve_status_cb, +                                   is); +    trigger_context_task (); +    return; +  case OC_WITHDRAW_SIGN: +    GNUNET_assert (NULL != +                   cmd->details.reserve_withdraw.reserve_reference); +    ref = find_command (is, +                        cmd->details.reserve_withdraw.reserve_reference); +    GNUNET_assert (NULL != ref); +    GNUNET_assert (OC_ADMIN_ADD_INCOMING == ref->oc); +    if (NULL != cmd->details.reserve_withdraw.amount) +    { +      if (GNUNET_OK != +          TALER_string_to_amount (cmd->details.reserve_withdraw.amount, +                                  &amount)) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                    "Failed to parse amount `%s' at %u\n", +                    cmd->details.reserve_withdraw.amount, +                    is->ip); +        fail (is); +        return; +      } +      cmd->details.reserve_withdraw.pk = find_pk (is->keys, +                                                  &amount); +    } +    if (NULL == cmd->details.reserve_withdraw.pk) +    { +      GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                  "Failed to determine denomination key at %u\n", +                  is->ip); +      fail (is); +      return; +    } + +    /* create coin's private key */ +    { +      struct GNUNET_CRYPTO_EddsaPrivateKey *priv; + +      priv = GNUNET_CRYPTO_eddsa_key_create (); +      cmd->details.reserve_withdraw.coin_priv.eddsa_priv = *priv; +      GNUNET_free (priv); +    } +    GNUNET_CRYPTO_eddsa_key_get_public (&cmd->details.reserve_withdraw.coin_priv.eddsa_priv, +                                        &coin_pub.eddsa_pub); +    cmd->details.reserve_withdraw.blinding_key.rsa_blinding_key +      = GNUNET_CRYPTO_rsa_blinding_key_create (GNUNET_CRYPTO_rsa_public_key_len (cmd->details.reserve_withdraw.pk->key.rsa_public_key)); +    cmd->details.reserve_withdraw.wsh +      = TALER_EXCHANGE_reserve_withdraw (exchange, +                                     cmd->details.reserve_withdraw.pk, +                                     &ref->details.admin_add_incoming.reserve_priv, +                                     &cmd->details.reserve_withdraw.coin_priv, +                                     &cmd->details.reserve_withdraw.blinding_key, +                                     &reserve_withdraw_cb, +                                     is); +    if (NULL == cmd->details.reserve_withdraw.wsh) +    { +      GNUNET_break (0); +      fail (is); +      return; +    } +    trigger_context_task (); +    return; +  case OC_DEPOSIT: +    { +      struct GNUNET_HashCode h_contract; +      const struct TALER_CoinSpendPrivateKeyP *coin_priv; +      const struct TALER_EXCHANGE_DenomPublicKey *coin_pk; +      const struct TALER_DenominationSignature *coin_pk_sig; +      struct TALER_CoinSpendPublicKeyP coin_pub; +      struct TALER_CoinSpendSignatureP coin_sig; +      struct GNUNET_TIME_Absolute refund_deadline; +      struct GNUNET_TIME_Absolute wire_deadline; +      struct GNUNET_TIME_Absolute timestamp; +      struct GNUNET_CRYPTO_EddsaPrivateKey *priv; +      struct TALER_MerchantPublicKeyP merchant_pub; +      json_t *contract; +      json_t *wire; + +      GNUNET_assert (NULL != +                     cmd->details.deposit.coin_ref); +      ref = find_command (is, +                          cmd->details.deposit.coin_ref); +      GNUNET_assert (NULL != ref); +      switch (ref->oc) +      { +      case OC_WITHDRAW_SIGN: +        coin_priv = &ref->details.reserve_withdraw.coin_priv; +        coin_pk = ref->details.reserve_withdraw.pk; +        coin_pk_sig = &ref->details.reserve_withdraw.sig; +        break; +      case OC_REFRESH_REVEAL: +        { +          const struct FreshCoin *fc; +          unsigned int idx; + +          idx = cmd->details.deposit.coin_idx; +          GNUNET_assert (idx < ref->details.refresh_reveal.num_fresh_coins); +          fc = &ref->details.refresh_reveal.fresh_coins[idx]; + +          coin_priv = &fc->coin_priv; +          coin_pk = fc->pk; +          coin_pk_sig = &fc->sig; +        } +        break; +      default: +        GNUNET_assert (0); +      } +      if (GNUNET_OK != +          TALER_string_to_amount (cmd->details.deposit.amount, +                                  &amount)) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                    "Failed to parse amount `%s' at %u\n", +                    cmd->details.deposit.amount, +                    is->ip); +        fail (is); +        return; +      } +      contract = json_loads (cmd->details.deposit.contract, +                             JSON_REJECT_DUPLICATES, +                             NULL); +      if (NULL == contract) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                    "Failed to parse contract details `%s' at %u/%s\n", +                    cmd->details.deposit.contract, +                    is->ip, +                    cmd->label); +        fail (is); +        return; +      } +      TALER_hash_json (contract, +                       &h_contract); +      wire = json_loads (cmd->details.deposit.wire_details, +                         JSON_REJECT_DUPLICATES, +                         NULL); +      if (NULL == wire) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                    "Failed to parse wire details `%s' at %u/%s\n", +                    cmd->details.deposit.wire_details, +                    is->ip, +                    cmd->label); +        fail (is); +        return; +      } +      GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, +                                          &coin_pub.eddsa_pub); + +      priv = GNUNET_CRYPTO_eddsa_key_create (); +      cmd->details.deposit.merchant_priv.eddsa_priv = *priv; +      GNUNET_free (priv); +      if (0 != cmd->details.deposit.refund_deadline.rel_value_us) +      { +        refund_deadline = GNUNET_TIME_relative_to_absolute (cmd->details.deposit.refund_deadline); +      } +      else +      { +        refund_deadline = GNUNET_TIME_UNIT_ZERO_ABS; +      } +      GNUNET_CRYPTO_eddsa_key_get_public (&cmd->details.deposit.merchant_priv.eddsa_priv, +                                          &merchant_pub.eddsa_pub); + +      wire_deadline = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_DAYS); +      timestamp = GNUNET_TIME_absolute_get (); +      TALER_round_abs_time (×tamp); +      { +        struct TALER_DepositRequestPS dr; + +        memset (&dr, 0, sizeof (dr)); +        dr.purpose.size = htonl (sizeof (struct TALER_DepositRequestPS)); +        dr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT); +        dr.h_contract = h_contract; +        TALER_hash_json (wire, +                         &dr.h_wire); +        dr.timestamp = GNUNET_TIME_absolute_hton (timestamp); +        dr.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline); +        dr.transaction_id = GNUNET_htonll (cmd->details.deposit.transaction_id); +        TALER_amount_hton (&dr.amount_with_fee, +                           &amount); +        TALER_amount_hton (&dr.deposit_fee, +                           &coin_pk->fee_deposit); +        dr.merchant = merchant_pub; +        dr.coin_pub = coin_pub; +        GNUNET_assert (GNUNET_OK == +                       GNUNET_CRYPTO_eddsa_sign (&coin_priv->eddsa_priv, +                                                 &dr.purpose, +                                                 &coin_sig.eddsa_signature)); +      } +      cmd->details.deposit.dh +        = TALER_EXCHANGE_deposit (exchange, +                              &amount, +                              wire_deadline, +                              wire, +                              &h_contract, +                              &coin_pub, +                              coin_pk_sig, +                              &coin_pk->key, +                              timestamp, +                              cmd->details.deposit.transaction_id, +                              &merchant_pub, +                              refund_deadline, +                              &coin_sig, +                              &deposit_cb, +                              is); +      if (NULL == cmd->details.deposit.dh) +      { +        GNUNET_break (0); +        json_decref (wire); +        fail (is); +        return; +      } +      json_decref (wire); +      trigger_context_task (); +      return; +    } +  case OC_REFRESH_MELT: +    { +      unsigned int num_melted_coins; +      unsigned int num_fresh_coins; + +      cmd->details.refresh_melt.noreveal_index = UINT16_MAX; +      for (num_melted_coins=0; +           NULL != cmd->details.refresh_melt.melted_coins[num_melted_coins].amount; +           num_melted_coins++) ; +      for (num_fresh_coins=0; +           NULL != cmd->details.refresh_melt.fresh_amounts[num_fresh_coins]; +           num_fresh_coins++) ; + +      cmd->details.refresh_melt.fresh_pks +        = GNUNET_new_array (num_fresh_coins, +                            const struct TALER_EXCHANGE_DenomPublicKey *); +      { +        struct TALER_CoinSpendPrivateKeyP melt_privs[num_melted_coins]; +        struct TALER_Amount melt_amounts[num_melted_coins]; +        struct TALER_DenominationSignature melt_sigs[num_melted_coins]; +        struct TALER_EXCHANGE_DenomPublicKey melt_pks[num_melted_coins]; +        struct TALER_EXCHANGE_DenomPublicKey fresh_pks[num_fresh_coins]; +        unsigned int i; + +        for (i=0;i<num_melted_coins;i++) +        { +          const struct MeltDetails *md = &cmd->details.refresh_melt.melted_coins[i]; +          ref = find_command (is, +                              md->coin_ref); +          GNUNET_assert (NULL != ref); +          GNUNET_assert (OC_WITHDRAW_SIGN == ref->oc); + +          melt_privs[i] = ref->details.reserve_withdraw.coin_priv; +          if (GNUNET_OK != +              TALER_string_to_amount (md->amount, +                                      &melt_amounts[i])) +          { +            GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                        "Failed to parse amount `%s' at %u\n", +                        md->amount, +                        is->ip); +            fail (is); +            return; +          } +          melt_sigs[i] = ref->details.reserve_withdraw.sig; +          melt_pks[i] = *ref->details.reserve_withdraw.pk; +        } +        for (i=0;i<num_fresh_coins;i++) +        { +          if (GNUNET_OK != +              TALER_string_to_amount (cmd->details.refresh_melt.fresh_amounts[i], +                                      &amount)) +          { +            GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                        "Failed to parse amount `%s' at %u\n", +                        cmd->details.reserve_withdraw.amount, +                        is->ip); +            fail (is); +            return; +          } +          cmd->details.refresh_melt.fresh_pks[i] +            = find_pk (is->keys, +                       &amount); +          fresh_pks[i] = *cmd->details.refresh_melt.fresh_pks[i]; +        } +        cmd->details.refresh_melt.refresh_data +          = TALER_EXCHANGE_refresh_prepare (num_melted_coins, +                                        melt_privs, +                                        melt_amounts, +                                        melt_sigs, +                                        melt_pks, +                                        GNUNET_YES, +                                        num_fresh_coins, +                                        fresh_pks, +                                        &cmd->details.refresh_melt.refresh_data_length); +        if (NULL == cmd->details.refresh_melt.refresh_data) +        { +          GNUNET_break (0); +          fail (is); +          return; +        } +        cmd->details.refresh_melt.rmh +          = TALER_EXCHANGE_refresh_melt (exchange, +                                     cmd->details.refresh_melt.refresh_data_length, +                                     cmd->details.refresh_melt.refresh_data, +                                     &melt_cb, +                                     is); +        if (NULL == cmd->details.refresh_melt.rmh) +        { +          GNUNET_break (0); +          fail (is); +          return; +        } +      } +    } +    trigger_context_task (); +    return; +  case OC_REFRESH_REVEAL: +    ref = find_command (is, +                        cmd->details.refresh_reveal.melt_ref); +    cmd->details.refresh_reveal.rrh +      = TALER_EXCHANGE_refresh_reveal (exchange, +                                   ref->details.refresh_melt.refresh_data_length, +                                   ref->details.refresh_melt.refresh_data, +                                   ref->details.refresh_melt.noreveal_index, +                                   &reveal_cb, +                                   is); +    if (NULL == cmd->details.refresh_reveal.rrh) +    { +      GNUNET_break (0); +      fail (is); +      return; +    } +    trigger_context_task (); +    return; +  case OC_REFRESH_LINK: +    /* find reveal command */ +    ref = find_command (is, +                        cmd->details.refresh_link.reveal_ref); +    /* find melt command */ +    ref = find_command (is, +                        ref->details.refresh_reveal.melt_ref); +    /* find reserve_withdraw command */ +    { +      unsigned int idx; +      const struct MeltDetails *md; +      unsigned int num_melted_coins; + +      for (num_melted_coins=0; +           NULL != ref->details.refresh_melt.melted_coins[num_melted_coins].amount; +           num_melted_coins++) ; +      idx = cmd->details.refresh_link.coin_idx; +      GNUNET_assert (idx < num_melted_coins); +      md = &ref->details.refresh_melt.melted_coins[idx]; +      ref = find_command (is, +                          md->coin_ref); +    } +    GNUNET_assert (OC_WITHDRAW_SIGN == ref->oc); +    /* finally, use private key from withdraw sign command */ +    cmd->details.refresh_link.rlh +      = TALER_EXCHANGE_refresh_link (exchange, +                                 &ref->details.reserve_withdraw.coin_priv, +                                 &link_cb, +                                 is); +    if (NULL == cmd->details.refresh_link.rlh) +    { +      GNUNET_break (0); +      fail (is); +      return; +    } +    trigger_context_task (); +    return; +  case OC_WIRE: +    cmd->details.wire.wh = TALER_EXCHANGE_wire (exchange, +                                            &wire_cb, +                                            is); +    trigger_context_task (); +    return; +  case OC_WIRE_DEPOSITS: +    if (NULL != cmd->details.wire_deposits.wtid_ref) +    { +      ref = find_command (is, +                          cmd->details.wire_deposits.wtid_ref); +      GNUNET_assert (NULL != ref); +      cmd->details.wire_deposits.wtid = ref->details.deposit_wtid.wtid; +    } +    cmd->details.wire_deposits.wdh +      = TALER_EXCHANGE_wire_deposits (exchange, +                                  &cmd->details.wire_deposits.wtid, +                                  &wire_deposits_cb, +                                  is); +    trigger_context_task (); +    return; +  case OC_DEPOSIT_WTID: +    { +      struct GNUNET_HashCode h_wire; +      struct GNUNET_HashCode h_contract; +      json_t *wire; +      json_t *contract; +      const struct Command *coin; +      struct TALER_CoinSpendPublicKeyP coin_pub; + +      ref = find_command (is, +                          cmd->details.deposit_wtid.deposit_ref); +      GNUNET_assert (NULL != ref); +      coin = find_command (is, +                           ref->details.deposit.coin_ref); +      GNUNET_assert (NULL != coin); +      switch (coin->oc) +      { +      case OC_WITHDRAW_SIGN: +        GNUNET_CRYPTO_eddsa_key_get_public (&coin->details.reserve_withdraw.coin_priv.eddsa_priv, +                                            &coin_pub.eddsa_pub); +        break; +      case OC_REFRESH_REVEAL: +        { +          const struct FreshCoin *fc; +          unsigned int idx; + +          idx = ref->details.deposit.coin_idx; +          GNUNET_assert (idx < coin->details.refresh_reveal.num_fresh_coins); +          fc = &coin->details.refresh_reveal.fresh_coins[idx]; + +          GNUNET_CRYPTO_eddsa_key_get_public (&fc->coin_priv.eddsa_priv, +                                              &coin_pub.eddsa_pub); +        } +        break; +      default: +        GNUNET_assert (0); +      } + +      wire = json_loads (ref->details.deposit.wire_details, +                         JSON_REJECT_DUPLICATES, +                         NULL); +      GNUNET_assert (NULL != wire); +      TALER_hash_json (wire, +                       &h_wire); +      json_decref (wire); +      contract = json_loads (ref->details.deposit.contract, +                             JSON_REJECT_DUPLICATES, +                             NULL); +      GNUNET_assert (NULL != contract); +      TALER_hash_json (contract, +                       &h_contract); +      json_decref (contract); +      cmd->details.deposit_wtid.dwh +        = TALER_EXCHANGE_deposit_wtid (exchange, +                                   &ref->details.deposit.merchant_priv, +                                   &h_wire, +                                   &h_contract, +                                   &coin_pub, +                                   ref->details.deposit.transaction_id, +                                   &deposit_wtid_cb, +                                   is); +      trigger_context_task (); +    } +    return; +  default: +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unknown instruction %d at %u (%s)\n", +                cmd->oc, +                is->ip, +                cmd->label); +    fail (is); +    return; +  } +} + + +/** + * Function run when the test terminates (good or bad). + * Cleans up our state. + * + * @param cls the interpreter state. + * @param tc unused + */ +static void +do_shutdown (void *cls, +             const struct GNUNET_SCHEDULER_TaskContext *tc) +{ +  struct InterpreterState *is = cls; +  struct Command *cmd; +  unsigned int i; + +  shutdown_task = NULL; +  for (i=0;OC_END != (cmd = &is->commands[i])->oc;i++) +  { +    switch (cmd->oc) +    { +    case OC_END: +      GNUNET_assert (0); +      break; +    case OC_ADMIN_ADD_INCOMING: +      if (NULL != cmd->details.admin_add_incoming.aih) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                    "Command %u (%s) did not complete\n", +                    i, +                    cmd->label); +        TALER_EXCHANGE_admin_add_incoming_cancel (cmd->details.admin_add_incoming.aih); +        cmd->details.admin_add_incoming.aih = NULL; +      } +      break; +    case OC_WITHDRAW_STATUS: +      if (NULL != cmd->details.reserve_status.wsh) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                    "Command %u (%s) did not complete\n", +                    i, +                    cmd->label); +        TALER_EXCHANGE_reserve_status_cancel (cmd->details.reserve_status.wsh); +        cmd->details.reserve_status.wsh = NULL; +      } +      break; +    case OC_WITHDRAW_SIGN: +      if (NULL != cmd->details.reserve_withdraw.wsh) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                    "Command %u (%s) did not complete\n", +                    i, +                    cmd->label); +        TALER_EXCHANGE_reserve_withdraw_cancel (cmd->details.reserve_withdraw.wsh); +        cmd->details.reserve_withdraw.wsh = NULL; +      } +      if (NULL != cmd->details.reserve_withdraw.sig.rsa_signature) +      { +        GNUNET_CRYPTO_rsa_signature_free (cmd->details.reserve_withdraw.sig.rsa_signature); +        cmd->details.reserve_withdraw.sig.rsa_signature = NULL; +      } +      if (NULL != cmd->details.reserve_withdraw.blinding_key.rsa_blinding_key) +      { +        GNUNET_CRYPTO_rsa_blinding_key_free (cmd->details.reserve_withdraw.blinding_key.rsa_blinding_key); +        cmd->details.reserve_withdraw.blinding_key.rsa_blinding_key = NULL; +      } +      break; +    case OC_DEPOSIT: +      if (NULL != cmd->details.deposit.dh) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                    "Command %u (%s) did not complete\n", +                    i, +                    cmd->label); +        TALER_EXCHANGE_deposit_cancel (cmd->details.deposit.dh); +        cmd->details.deposit.dh = NULL; +      } +      break; +    case OC_REFRESH_MELT: +      if (NULL != cmd->details.refresh_melt.rmh) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                    "Command %u (%s) did not complete\n", +                    i, +                    cmd->label); +        TALER_EXCHANGE_refresh_melt_cancel (cmd->details.refresh_melt.rmh); +        cmd->details.refresh_melt.rmh = NULL; +      } +      GNUNET_free_non_null (cmd->details.refresh_melt.fresh_pks); +      cmd->details.refresh_melt.fresh_pks = NULL; +      GNUNET_free_non_null (cmd->details.refresh_melt.refresh_data); +      cmd->details.refresh_melt.refresh_data = NULL; +      cmd->details.refresh_melt.refresh_data_length = 0; +      break; +    case OC_REFRESH_REVEAL: +      if (NULL != cmd->details.refresh_reveal.rrh) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                    "Command %u (%s) did not complete\n", +                    i, +                    cmd->label); +        TALER_EXCHANGE_refresh_reveal_cancel (cmd->details.refresh_reveal.rrh); +        cmd->details.refresh_reveal.rrh = NULL; +      } +      { +        unsigned int j; +        struct FreshCoin *fresh_coins; + +        fresh_coins = cmd->details.refresh_reveal.fresh_coins; +        for (j=0;j<cmd->details.refresh_reveal.num_fresh_coins;j++) +          GNUNET_CRYPTO_rsa_signature_free (fresh_coins[j].sig.rsa_signature); +      } +      GNUNET_free_non_null (cmd->details.refresh_reveal.fresh_coins); +      cmd->details.refresh_reveal.fresh_coins = NULL; +      cmd->details.refresh_reveal.num_fresh_coins = 0; +      break; +    case OC_REFRESH_LINK: +      if (NULL != cmd->details.refresh_link.rlh) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                    "Command %u (%s) did not complete\n", +                    i, +                    cmd->label); +        TALER_EXCHANGE_refresh_link_cancel (cmd->details.refresh_link.rlh); +        cmd->details.refresh_link.rlh = NULL; +      } +      break; +    case OC_WIRE: +      if (NULL != cmd->details.wire.wh) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                    "Command %u (%s) did not complete\n", +                    i, +                    cmd->label); +        TALER_EXCHANGE_wire_cancel (cmd->details.wire.wh); +        cmd->details.wire.wh = NULL; +      } +      break; +    case OC_WIRE_DEPOSITS: +      if (NULL != cmd->details.wire_deposits.wdh) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                    "Command %u (%s) did not complete\n", +                    i, +                    cmd->label); +        TALER_EXCHANGE_wire_deposits_cancel (cmd->details.wire_deposits.wdh); +        cmd->details.wire_deposits.wdh = NULL; +      } +      break; +    case OC_DEPOSIT_WTID: +      if (NULL != cmd->details.deposit_wtid.dwh) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                    "Command %u (%s) did not complete\n", +                    i, +                    cmd->label); +        TALER_EXCHANGE_deposit_wtid_cancel (cmd->details.deposit_wtid.dwh); +        cmd->details.deposit_wtid.dwh = NULL; +      } +      break; +    default: +      GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                  "Unknown instruction %d at %u (%s)\n", +                  cmd->oc, +                  i, +                  cmd->label); +      break; +    } +  } +  if (NULL != is->task) +  { +    GNUNET_SCHEDULER_cancel (is->task); +    is->task = NULL; +  } +  GNUNET_free (is); +  if (NULL != ctx_task) +  { +    GNUNET_SCHEDULER_cancel (ctx_task); +    ctx_task = NULL; +  } +  if (NULL != exchange) +  { +    TALER_EXCHANGE_disconnect (exchange); +    exchange = NULL; +  } +  if (NULL != ctx) +  { +    TALER_EXCHANGE_fini (ctx); +    ctx = NULL; +  } +} + + +/** + * Functions of this type are called to provide the retrieved signing and + * denomination keys of the exchange.  No TALER_EXCHANGE_*() functions should be called + * in this callback. + * + * @param cls closure + * @param keys information about keys of the exchange + */ +static void +cert_cb (void *cls, +         const struct TALER_EXCHANGE_Keys *keys) +{ +  struct InterpreterState *is = cls; + +  /* check that keys is OK */ +#define ERR(cond) do { if(!(cond)) break; GNUNET_break (0); GNUNET_SCHEDULER_shutdown(); return; } while (0) +  ERR (NULL == keys); +  ERR (0 == keys->num_sign_keys); +  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, +              "Read %u signing keys\n", +              keys->num_sign_keys); +  ERR (0 == keys->num_denom_keys); +  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, +              "Read %u denomination keys\n", +              keys->num_denom_keys); +#undef ERR + +  /* run actual tests via interpreter-loop */ +  is->keys = keys; +  is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, +                                       is); +} + + +/** + * Task that runs the context's event loop with the GNUnet scheduler. + * + * @param cls unused + * @param tc scheduler context (unused) + */ +static void +context_task (void *cls, +              const struct GNUNET_SCHEDULER_TaskContext *tc) +{ +  long timeout; +  int max_fd; +  fd_set read_fd_set; +  fd_set write_fd_set; +  fd_set except_fd_set; +  struct GNUNET_NETWORK_FDSet *rs; +  struct GNUNET_NETWORK_FDSet *ws; +  struct GNUNET_TIME_Relative delay; + +  ctx_task = NULL; +  TALER_EXCHANGE_perform (ctx); +  max_fd = -1; +  timeout = -1; +  FD_ZERO (&read_fd_set); +  FD_ZERO (&write_fd_set); +  FD_ZERO (&except_fd_set); +  TALER_EXCHANGE_get_select_info (ctx, +                              &read_fd_set, +                              &write_fd_set, +                              &except_fd_set, +                              &max_fd, +                              &timeout); +  if (timeout >= 0) +    delay = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, +                                           timeout); +  else +    delay = GNUNET_TIME_UNIT_FOREVER_REL; +  rs = GNUNET_NETWORK_fdset_create (); +  GNUNET_NETWORK_fdset_copy_native (rs, +                                    &read_fd_set, +                                    max_fd + 1); +  ws = GNUNET_NETWORK_fdset_create (); +  GNUNET_NETWORK_fdset_copy_native (ws, +                                    &write_fd_set, +                                    max_fd + 1); +  ctx_task = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT, +                                          delay, +                                          rs, +                                          ws, +                                          &context_task, +                                          cls); +  GNUNET_NETWORK_fdset_destroy (rs); +  GNUNET_NETWORK_fdset_destroy (ws); +} + + +/** + * Main function that will be run by the scheduler. + * + * @param cls closure + * @param args remaining command-line arguments + * @param cfgfile name of the configuration file used (for saving, can be NULL!) + * @param config configuration + */ +static void +run (void *cls, +     const struct GNUNET_SCHEDULER_TaskContext *tc) +{ +  struct InterpreterState *is; +  static struct MeltDetails melt_coins_1[] = { +    { .amount = "EUR:4", +      .coin_ref = "refresh-withdraw-coin-1" }, +    { NULL, NULL } +  }; +  static const char *melt_fresh_amounts_1[] = { +    "EUR:1", +    "EUR:1", +    "EUR:1", +    "EUR:0.1", +    "EUR:0.1", +    "EUR:0.1", +    "EUR:0.1", +    "EUR:0.1", +    "EUR:0.1", +    "EUR:0.1", +    "EUR:0.1", +    "EUR:0.01", +    "EUR:0.01", +    "EUR:0.01", +    "EUR:0.01", +    "EUR:0.01", +    "EUR:0.01", +    /* with 0.01 withdraw fees (except for 1ct coins), +       this totals up to exactly EUR:3.97, and with +       the 0.03 refresh fee, to EUR:4.0*/ +    NULL +  }; +  static struct Command commands[] = +  { +    /* *************** start of /wire testing ************** */ + +#if WIRE_TEST +    { .oc = OC_WIRE, +      .label = "wire-test", +      /* /wire/test replies with a 302 redirect */ +      .expected_response_code = MHD_HTTP_FOUND, +      .details.wire.format = "test" }, +#endif +#if WIRE_SEPA +    { .oc = OC_WIRE, +      .label = "wire-sepa", +      /* /wire/sepa replies with a 200 redirect */ +      .expected_response_code = MHD_HTTP_OK, +      .details.wire.format = "sepa" }, +#endif +    /* *************** end of /wire testing ************** */ + +#if WIRE_TEST +    /* None of this works if 'test' is not allowed as we do +       /admin/add/incoming with format 'test' */ + +    /* Fill reserve with EUR:5.01, as withdraw fee is 1 ct per config */ +    { .oc = OC_ADMIN_ADD_INCOMING, +      .label = "create-reserve-1", +      .expected_response_code = MHD_HTTP_OK, +      .details.admin_add_incoming.wire = "{ \"type\":\"TEST\", \"bank\":\"source bank\", \"account_number\":42 }", +      .details.admin_add_incoming.amount = "EUR:5.01" }, +    /* Withdraw a 5 EUR coin, at fee of 1 ct */ +    { .oc = OC_WITHDRAW_SIGN, +      .label = "withdraw-coin-1", +      .expected_response_code = MHD_HTTP_OK, +      .details.reserve_withdraw.reserve_reference = "create-reserve-1", +      .details.reserve_withdraw.amount = "EUR:5" }, +    /* Check that deposit and withdraw operation are in history, and +       that the balance is now at zero */ +    { .oc = OC_WITHDRAW_STATUS, +      .label = "withdraw-status-1", +      .expected_response_code = MHD_HTTP_OK, +      .details.reserve_status.reserve_reference = "create-reserve-1", +      .details.reserve_status.expected_balance = "EUR:0" }, +    /* Try to deposit the 5 EUR coin (in full) */ +    { .oc = OC_DEPOSIT, +      .label = "deposit-simple", +      .expected_response_code = MHD_HTTP_OK, +      .details.deposit.amount = "EUR:5", +      .details.deposit.coin_ref = "withdraw-coin-1", +      .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account_number\":42 }", +      .details.deposit.contract = "{ \"items\": [ { \"name\":\"ice cream\", \"value\":1 } ] }", +      .details.deposit.transaction_id = 1 }, + +    /* Try to overdraw funds ... */ +    { .oc = OC_WITHDRAW_SIGN, +      .label = "withdraw-coin-2", +      .expected_response_code = MHD_HTTP_PAYMENT_REQUIRED, +      .details.reserve_withdraw.reserve_reference = "create-reserve-1", +      .details.reserve_withdraw.amount = "EUR:5" }, + +    /* Try to double-spend the 5 EUR coin with different wire details */ +    { .oc = OC_DEPOSIT, +      .label = "deposit-double-1", +      .expected_response_code = MHD_HTTP_FORBIDDEN, +      .details.deposit.amount = "EUR:5", +      .details.deposit.coin_ref = "withdraw-coin-1", +      .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account_number\":43 }", +      .details.deposit.contract = "{ \"items\": [ { \"name\":\"ice cream\", \"value\":1 } ] }", +      .details.deposit.transaction_id = 1 }, +    /* Try to double-spend the 5 EUR coin at the same merchant (but different +       transaction ID) */ +    { .oc = OC_DEPOSIT, +      .label = "deposit-double-2", +      .expected_response_code = MHD_HTTP_FORBIDDEN, +      .details.deposit.amount = "EUR:5", +      .details.deposit.coin_ref = "withdraw-coin-1", +      .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account_number\":42 }", +      .details.deposit.contract = "{ \"items\": [ { \"name\":\"ice cream\", \"value\":1 } ] }", +      .details.deposit.transaction_id = 2 }, +    /* Try to double-spend the 5 EUR coin at the same merchant (but different +       contract) */ +    { .oc = OC_DEPOSIT, +      .label = "deposit-double-3", +      .expected_response_code = MHD_HTTP_FORBIDDEN, +      .details.deposit.amount = "EUR:5", +      .details.deposit.coin_ref = "withdraw-coin-1", +      .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account_number\":42 }", +      .details.deposit.contract = "{ \"items\":[{ \"name\":\"ice cream\", \"value\":2 } ] }", +      .details.deposit.transaction_id = 1 }, + +    /* ***************** /refresh testing ******************** */ + +    /* Fill reserve with EUR:5.01, as withdraw fee is 1 ct */ +    { .oc = OC_ADMIN_ADD_INCOMING, +      .label = "refresh-create-reserve-1", +      .expected_response_code = MHD_HTTP_OK, +      .details.admin_add_incoming.wire = "{ \"type\":\"TEST\", \"bank\":\"source bank\", \"account_number\":424 }", +      .details.admin_add_incoming.amount = "EUR:5.01" }, +    /* Withdraw a 5 EUR coin, at fee of 1 ct */ +    { .oc = OC_WITHDRAW_SIGN, +      .label = "refresh-withdraw-coin-1", +      .expected_response_code = MHD_HTTP_OK, +      .details.reserve_withdraw.reserve_reference = "refresh-create-reserve-1", +      .details.reserve_withdraw.amount = "EUR:5" }, +    /* Try to partially spend (deposit) 1 EUR of the 5 EUR coin (in full) +       (merchant would receive EUR:0.99 due to 1 ct deposit fee) */ +    { .oc = OC_DEPOSIT, +      .label = "refresh-deposit-partial", +      .expected_response_code = MHD_HTTP_OK, +      .details.deposit.amount = "EUR:1", +      .details.deposit.coin_ref = "refresh-withdraw-coin-1", +      .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account_number\":42 }", +      .details.deposit.contract = "{ \"items\" : [ { \"name\":\"ice cream\", \"value\":\"EUR:1\" } ] }", +      .details.deposit.transaction_id = 42421 }, + +    /* Melt the rest of the coin's value (EUR:4.00 = 3x EUR:1.03 + 7x EUR:0.13) */ + +    { .oc = OC_REFRESH_MELT, +      .label = "refresh-melt-1", +      .expected_response_code = MHD_HTTP_OK, +      .details.refresh_melt.melted_coins = melt_coins_1, +      .details.refresh_melt.fresh_amounts = melt_fresh_amounts_1 }, + + +    /* Complete (successful) melt operation, and withdraw the coins */ +    { .oc = OC_REFRESH_REVEAL, +      .label = "refresh-reveal-1", +      .expected_response_code = MHD_HTTP_OK, +      .details.refresh_reveal.melt_ref = "refresh-melt-1" }, + +    /* Test that /refresh/link works */ +    { .oc = OC_REFRESH_LINK, +      .label = "refresh-link-1", +      .expected_response_code = MHD_HTTP_OK, +      .details.refresh_link.reveal_ref = "refresh-reveal-1" }, + + +    /* Test successfully spending coins from the refresh operation: +       first EUR:1 */ +    { .oc = OC_DEPOSIT, +      .label = "refresh-deposit-refreshed-1a", +      .expected_response_code = MHD_HTTP_OK, +      .details.deposit.amount = "EUR:1", +      .details.deposit.coin_ref = "refresh-reveal-1", +      .details.deposit.coin_idx = 0, +      .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account_number\":42 }", +      .details.deposit.contract = "{ \"items\": [ { \"name\":\"ice cream\", \"value\":3 } ] }", +      .details.deposit.transaction_id = 2 }, + +    /* Test successfully spending coins from the refresh operation: +       finally EUR:0.1 */ +    { .oc = OC_DEPOSIT, +      .label = "refresh-deposit-refreshed-1b", +      .expected_response_code = MHD_HTTP_OK, +      .details.deposit.amount = "EUR:0.1", +      .details.deposit.coin_ref = "refresh-reveal-1", +      .details.deposit.coin_idx = 4, +      .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account_number\":42 }", +      .details.deposit.contract = "{ \"items\": [ { \"name\":\"ice cream\", \"value\":3 } ] }", +      .details.deposit.transaction_id = 2 }, + +    /* Test running a failing melt operation (same operation again must fail) */ +    { .oc = OC_REFRESH_MELT, +      .label = "refresh-melt-failing", +      .expected_response_code = MHD_HTTP_FORBIDDEN, +      .details.refresh_melt.melted_coins = melt_coins_1, +      .details.refresh_melt.fresh_amounts = melt_fresh_amounts_1 }, + +    // FIXME: also test with coin that was already melted +    // (signature differs from coin that was deposited...) +    /* *************** end of /refresh testing ************** */ + +    /* ************** Test tracking API ******************** */ +    /* Try resolving a deposit's WTID, as we never triggered +       execution of transactions, the answer should be that +       the exchange knows about the deposit, but has no WTID yet. */ +    { .oc = OC_DEPOSIT_WTID, +      .label = "deposit-wtid-found", +      .expected_response_code = MHD_HTTP_ACCEPTED, +      .details.deposit_wtid.deposit_ref = "deposit-simple" }, +    /* Try resolving a deposit's WTID for a failed deposit. +       As the deposit failed, the answer should be that +       the exchange does NOT know about the deposit. */ +    { .oc = OC_DEPOSIT_WTID, +      .label = "deposit-wtid-failing", +      .expected_response_code = MHD_HTTP_NOT_FOUND, +      .details.deposit_wtid.deposit_ref = "deposit-double-2" }, +    /* Try resolving an undefined (all zeros) WTID; this +       should fail as obviously the exchange didn't use that +       WTID value for any transaction. */ +    { .oc = OC_WIRE_DEPOSITS, +      .label = "wire-deposit-failing", +      .expected_response_code = MHD_HTTP_NOT_FOUND }, + +    /* TODO: trigger aggregation logic and then check the +       cases where tracking succeeds! */ + +    /* ************** End of tracking API testing************* */ + + +#endif + +    { .oc = OC_END } +  }; + +  is = GNUNET_new (struct InterpreterState); +  is->commands = commands; + +  ctx = TALER_EXCHANGE_init (); +  GNUNET_assert (NULL != ctx); +  ctx_task = GNUNET_SCHEDULER_add_now (&context_task, +                                       ctx); +  exchange = TALER_EXCHANGE_connect (ctx, +                             "http://localhost:8081", +                             &cert_cb, is, +                             TALER_EXCHANGE_OPTION_END); +  GNUNET_assert (NULL != exchange); +  shutdown_task +    = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply +                                    (GNUNET_TIME_UNIT_SECONDS, 150), +                                    &do_shutdown, is); +} + + +/** + * Main function for the testcase for the exchange API. + * + * @param argc expected to be 1 + * @param argv expected to only contain the program name + */ +int +main (int argc, +      char * const *argv) +{ +  struct GNUNET_OS_Process *proc; +  struct GNUNET_OS_Process *exchanged; + +  GNUNET_log_setup ("test-exchange-api", +                    "WARNING", +                    NULL); +  proc = GNUNET_OS_start_process (GNUNET_NO, +                                  GNUNET_OS_INHERIT_STD_ALL, +                                  NULL, NULL, NULL, +                                  "taler-exchange-keyup", +                                  "taler-exchange-keyup", +                                  "-d", "test-exchange-home", +                                  "-m", "test-exchange-home/master.priv", +                                  NULL); +  GNUNET_OS_process_wait (proc); +  GNUNET_OS_process_destroy (proc); +  exchanged = GNUNET_OS_start_process (GNUNET_NO, +                                   GNUNET_OS_INHERIT_STD_ALL, +                                   NULL, NULL, NULL, +                                   "taler-exchange-httpd", +                                   "taler-exchange-httpd", +                                   "-d", "test-exchange-home", +                                   NULL); +  /* give child time to start and bind against the socket */ +  fprintf (stderr, "Waiting for taler-exchange-httpd to be ready"); +  do +    { +      fprintf (stderr, "."); +      sleep (1); +    } +  while (0 != system ("wget -q -t 1 -T 1 http://127.0.0.1:8081/keys -o /dev/null -O /dev/null")); +  fprintf (stderr, "\n"); +  result = GNUNET_SYSERR; +  GNUNET_SCHEDULER_run (&run, NULL); +  GNUNET_OS_process_kill (exchanged, +                          SIGTERM); +  GNUNET_OS_process_wait (exchanged); +  GNUNET_OS_process_destroy (exchanged); +  return (GNUNET_OK == result) ? 0 : 1; +} + +/* end of test_exchange_api.c */  | 
