exchange_api_coins_history.c (37873B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014-2023 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU General Public License for more details. 12 13 You should have received a copy of the GNU General Public License along with 14 TALER; see the file COPYING. If not, see 15 <http://www.gnu.org/licenses/> 16 */ 17 /** 18 * @file lib/exchange_api_coins_history.c 19 * @brief Implementation of the POST /coins/$COIN_PUB/history requests 20 * @author Christian Grothoff 21 * 22 * NOTE: this is an incomplete draft, never finished! 23 */ 24 #include "taler/platform.h" 25 #include <jansson.h> 26 #include <microhttpd.h> /* just for HTTP history codes */ 27 #include <gnunet/gnunet_util_lib.h> 28 #include <gnunet/gnunet_json_lib.h> 29 #include <gnunet/gnunet_curl_lib.h> 30 #include "taler/taler_exchange_service.h" 31 #include "taler/taler_json_lib.h" 32 #include "exchange_api_handle.h" 33 #include "taler/taler_signatures.h" 34 #include "exchange_api_curl_defaults.h" 35 36 37 /** 38 * @brief A /coins/$RID/history Handle 39 */ 40 struct TALER_EXCHANGE_CoinsHistoryHandle 41 { 42 43 /** 44 * The url for this request. 45 */ 46 char *url; 47 48 /** 49 * Handle for the request. 50 */ 51 struct GNUNET_CURL_Job *job; 52 53 /** 54 * Context for #TEH_curl_easy_post(). Keeps the data that must 55 * persist for Curl to make the upload. 56 */ 57 struct TALER_CURL_PostContext post_ctx; 58 59 /** 60 * Function to call with the result. 61 */ 62 TALER_EXCHANGE_CoinsHistoryCallback cb; 63 64 /** 65 * Public key of the coin we are querying. 66 */ 67 struct TALER_CoinSpendPublicKeyP coin_pub; 68 69 /** 70 * Closure for @a cb. 71 */ 72 void *cb_cls; 73 74 }; 75 76 77 /** 78 * Context for coin helpers. 79 */ 80 struct CoinHistoryParseContext 81 { 82 83 /** 84 * Keys of the exchange. 85 */ 86 struct TALER_EXCHANGE_Keys *keys; 87 88 /** 89 * Denomination of the coin. 90 */ 91 const struct TALER_EXCHANGE_DenomPublicKey *dk; 92 93 /** 94 * Our coin public key. 95 */ 96 const struct TALER_CoinSpendPublicKeyP *coin_pub; 97 98 /** 99 * Where to sum up total refunds. 100 */ 101 struct TALER_Amount *total_in; 102 103 /** 104 * Total amount encountered. 105 */ 106 struct TALER_Amount *total_out; 107 108 }; 109 110 111 /** 112 * Signature of functions that operate on one of 113 * the coin's history entries. 114 * 115 * @param[in,out] pc overall context 116 * @param[out] rh where to write the history entry 117 * @param amount main amount of this operation 118 * @param transaction JSON details for the operation 119 * @return #GNUNET_SYSERR on error, 120 * #GNUNET_OK to add, #GNUNET_NO to subtract 121 */ 122 typedef enum GNUNET_GenericReturnValue 123 (*CoinCheckHelper)(struct CoinHistoryParseContext *pc, 124 struct TALER_EXCHANGE_CoinHistoryEntry *rh, 125 const struct TALER_Amount *amount, 126 json_t *transaction); 127 128 129 /** 130 * Handle deposit entry in the coin's history. 131 * 132 * @param[in,out] pc overall context 133 * @param[out] rh history entry to initialize 134 * @param amount main amount of this operation 135 * @param transaction JSON details for the operation 136 * @return #GNUNET_SYSERR on error, 137 * #GNUNET_OK to add, #GNUNET_NO to subtract 138 */ 139 static enum GNUNET_GenericReturnValue 140 help_deposit (struct CoinHistoryParseContext *pc, 141 struct TALER_EXCHANGE_CoinHistoryEntry *rh, 142 const struct TALER_Amount *amount, 143 json_t *transaction) 144 { 145 struct GNUNET_JSON_Specification spec[] = { 146 TALER_JSON_spec_amount_any ("deposit_fee", 147 &rh->details.deposit.deposit_fee), 148 GNUNET_JSON_spec_fixed_auto ("merchant_pub", 149 &rh->details.deposit.merchant_pub), 150 GNUNET_JSON_spec_timestamp ("timestamp", 151 &rh->details.deposit.wallet_timestamp), 152 GNUNET_JSON_spec_mark_optional ( 153 GNUNET_JSON_spec_timestamp ("refund_deadline", 154 &rh->details.deposit.refund_deadline), 155 NULL), 156 GNUNET_JSON_spec_fixed_auto ("h_contract_terms", 157 &rh->details.deposit.h_contract_terms), 158 GNUNET_JSON_spec_fixed_auto ("h_wire", 159 &rh->details.deposit.h_wire), 160 GNUNET_JSON_spec_mark_optional ( 161 GNUNET_JSON_spec_fixed_auto ("h_policy", 162 &rh->details.deposit.h_policy), 163 &rh->details.deposit.no_h_policy), 164 GNUNET_JSON_spec_mark_optional ( 165 GNUNET_JSON_spec_fixed_auto ("wallet_data_hash", 166 &rh->details.deposit.wallet_data_hash), 167 &rh->details.deposit.no_wallet_data_hash), 168 GNUNET_JSON_spec_mark_optional ( 169 GNUNET_JSON_spec_fixed_auto ("h_age_commitment", 170 &rh->details.deposit.hac), 171 &rh->details.deposit.no_hac), 172 GNUNET_JSON_spec_fixed_auto ("coin_sig", 173 &rh->details.deposit.sig), 174 GNUNET_JSON_spec_end () 175 }; 176 177 rh->details.deposit.refund_deadline = GNUNET_TIME_UNIT_ZERO_TS; 178 if (GNUNET_OK != 179 GNUNET_JSON_parse (transaction, 180 spec, 181 NULL, NULL)) 182 { 183 GNUNET_break_op (0); 184 return GNUNET_SYSERR; 185 } 186 if (GNUNET_OK != 187 TALER_wallet_deposit_verify ( 188 amount, 189 &rh->details.deposit.deposit_fee, 190 &rh->details.deposit.h_wire, 191 &rh->details.deposit.h_contract_terms, 192 rh->details.deposit.no_wallet_data_hash 193 ? NULL 194 : &rh->details.deposit.wallet_data_hash, 195 rh->details.deposit.no_hac 196 ? NULL 197 : &rh->details.deposit.hac, 198 rh->details.deposit.no_h_policy 199 ? NULL 200 : &rh->details.deposit.h_policy, 201 &pc->dk->h_key, 202 rh->details.deposit.wallet_timestamp, 203 &rh->details.deposit.merchant_pub, 204 rh->details.deposit.refund_deadline, 205 pc->coin_pub, 206 &rh->details.deposit.sig)) 207 { 208 GNUNET_break_op (0); 209 return GNUNET_SYSERR; 210 } 211 /* check that deposit fee matches our expectations from /keys! */ 212 if ( (GNUNET_YES != 213 TALER_amount_cmp_currency (&rh->details.deposit.deposit_fee, 214 &pc->dk->fees.deposit)) || 215 (0 != 216 TALER_amount_cmp (&rh->details.deposit.deposit_fee, 217 &pc->dk->fees.deposit)) ) 218 { 219 GNUNET_break_op (0); 220 return GNUNET_SYSERR; 221 } 222 return GNUNET_YES; 223 } 224 225 226 /** 227 * Handle melt entry in the coin's history. 228 * 229 * @param[in,out] pc overall context 230 * @param[out] rh history entry to initialize 231 * @param amount main amount of this operation 232 * @param transaction JSON details for the operation 233 * @return #GNUNET_SYSERR on error, 234 * #GNUNET_OK to add, #GNUNET_NO to subtract 235 */ 236 static enum GNUNET_GenericReturnValue 237 help_melt (struct CoinHistoryParseContext *pc, 238 struct TALER_EXCHANGE_CoinHistoryEntry *rh, 239 const struct TALER_Amount *amount, 240 json_t *transaction) 241 { 242 struct GNUNET_JSON_Specification spec[] = { 243 TALER_JSON_spec_amount_any ("melt_fee", 244 &rh->details.melt.melt_fee), 245 GNUNET_JSON_spec_fixed_auto ("rc", 246 &rh->details.melt.rc), 247 // FIXME: also return refresh_seed? 248 // FIXME: also return blinding_seed? 249 GNUNET_JSON_spec_mark_optional ( 250 GNUNET_JSON_spec_fixed_auto ("h_age_commitment", 251 &rh->details.melt.h_age_commitment), 252 &rh->details.melt.no_hac), 253 GNUNET_JSON_spec_fixed_auto ("coin_sig", 254 &rh->details.melt.sig), 255 GNUNET_JSON_spec_end () 256 }; 257 258 if (GNUNET_OK != 259 GNUNET_JSON_parse (transaction, 260 spec, 261 NULL, NULL)) 262 { 263 GNUNET_break_op (0); 264 return GNUNET_SYSERR; 265 } 266 267 /* check that melt fee matches our expectations from /keys! */ 268 if ( (GNUNET_YES != 269 TALER_amount_cmp_currency (&rh->details.melt.melt_fee, 270 &pc->dk->fees.refresh)) || 271 (0 != 272 TALER_amount_cmp (&rh->details.melt.melt_fee, 273 &pc->dk->fees.refresh)) ) 274 { 275 GNUNET_break_op (0); 276 return GNUNET_SYSERR; 277 } 278 if (GNUNET_OK != 279 TALER_wallet_melt_verify ( 280 amount, 281 &rh->details.melt.melt_fee, 282 &rh->details.melt.rc, 283 &pc->dk->h_key, 284 rh->details.melt.no_hac 285 ? NULL 286 : &rh->details.melt.h_age_commitment, 287 pc->coin_pub, 288 &rh->details.melt.sig)) 289 { 290 GNUNET_break_op (0); 291 return GNUNET_SYSERR; 292 } 293 return GNUNET_YES; 294 } 295 296 297 /** 298 * Handle refund entry in the coin's history. 299 * 300 * @param[in,out] pc overall context 301 * @param[out] rh history entry to initialize 302 * @param amount main amount of this operation 303 * @param transaction JSON details for the operation 304 * @return #GNUNET_SYSERR on error, 305 * #GNUNET_OK to add, #GNUNET_NO to subtract 306 */ 307 static enum GNUNET_GenericReturnValue 308 help_refund (struct CoinHistoryParseContext *pc, 309 struct TALER_EXCHANGE_CoinHistoryEntry *rh, 310 const struct TALER_Amount *amount, 311 json_t *transaction) 312 { 313 struct GNUNET_JSON_Specification spec[] = { 314 TALER_JSON_spec_amount_any ("refund_fee", 315 &rh->details.refund.refund_fee), 316 GNUNET_JSON_spec_fixed_auto ("h_contract_terms", 317 &rh->details.refund.h_contract_terms), 318 GNUNET_JSON_spec_fixed_auto ("merchant_pub", 319 &rh->details.refund.merchant_pub), 320 GNUNET_JSON_spec_uint64 ("rtransaction_id", 321 &rh->details.refund.rtransaction_id), 322 GNUNET_JSON_spec_fixed_auto ("merchant_sig", 323 &rh->details.refund.sig), 324 GNUNET_JSON_spec_end () 325 }; 326 327 if (GNUNET_OK != 328 GNUNET_JSON_parse (transaction, 329 spec, 330 NULL, NULL)) 331 { 332 GNUNET_break_op (0); 333 return GNUNET_SYSERR; 334 } 335 if (0 > 336 TALER_amount_add (&rh->details.refund.sig_amount, 337 &rh->details.refund.refund_fee, 338 amount)) 339 { 340 GNUNET_break_op (0); 341 return GNUNET_SYSERR; 342 } 343 if (GNUNET_OK != 344 TALER_merchant_refund_verify (pc->coin_pub, 345 &rh->details.refund.h_contract_terms, 346 rh->details.refund.rtransaction_id, 347 &rh->details.refund.sig_amount, 348 &rh->details.refund.merchant_pub, 349 &rh->details.refund.sig)) 350 { 351 GNUNET_break_op (0); 352 return GNUNET_SYSERR; 353 } 354 /* NOTE: theoretically, we could also check that the given 355 merchant_pub and h_contract_terms appear in the 356 history under deposits. However, there is really no benefit 357 for the exchange to lie here, so not checking is probably OK 358 (an auditor ought to check, though). Then again, we similarly 359 had no reason to check the merchant's signature (other than a 360 well-formendess check). */ 361 362 /* check that refund fee matches our expectations from /keys! */ 363 if ( (GNUNET_YES != 364 TALER_amount_cmp_currency (&rh->details.refund.refund_fee, 365 &pc->dk->fees.refund)) || 366 (0 != 367 TALER_amount_cmp (&rh->details.refund.refund_fee, 368 &pc->dk->fees.refund)) ) 369 { 370 GNUNET_break_op (0); 371 return GNUNET_SYSERR; 372 } 373 return GNUNET_NO; 374 } 375 376 377 /** 378 * Handle recoup entry in the coin's history. 379 * 380 * @param[in,out] pc overall context 381 * @param[out] rh history entry to initialize 382 * @param amount main amount of this operation 383 * @param transaction JSON details for the operation 384 * @return #GNUNET_SYSERR on error, 385 * #GNUNET_OK to add, #GNUNET_NO to subtract 386 */ 387 static enum GNUNET_GenericReturnValue 388 help_recoup (struct CoinHistoryParseContext *pc, 389 struct TALER_EXCHANGE_CoinHistoryEntry *rh, 390 const struct TALER_Amount *amount, 391 json_t *transaction) 392 { 393 struct GNUNET_JSON_Specification spec[] = { 394 GNUNET_JSON_spec_fixed_auto ("exchange_sig", 395 &rh->details.recoup.exchange_sig), 396 GNUNET_JSON_spec_fixed_auto ("exchange_pub", 397 &rh->details.recoup.exchange_pub), 398 GNUNET_JSON_spec_fixed_auto ("reserve_pub", 399 &rh->details.recoup.reserve_pub), 400 GNUNET_JSON_spec_fixed_auto ("coin_sig", 401 &rh->details.recoup.coin_sig), 402 GNUNET_JSON_spec_fixed_auto ("coin_blind", 403 &rh->details.recoup.coin_bks), 404 GNUNET_JSON_spec_timestamp ("timestamp", 405 &rh->details.recoup.timestamp), 406 GNUNET_JSON_spec_end () 407 }; 408 409 if (GNUNET_OK != 410 GNUNET_JSON_parse (transaction, 411 spec, 412 NULL, NULL)) 413 { 414 GNUNET_break_op (0); 415 return GNUNET_SYSERR; 416 } 417 if (GNUNET_OK != 418 TALER_exchange_online_confirm_recoup_verify ( 419 rh->details.recoup.timestamp, 420 amount, 421 pc->coin_pub, 422 &rh->details.recoup.reserve_pub, 423 &rh->details.recoup.exchange_pub, 424 &rh->details.recoup.exchange_sig)) 425 { 426 GNUNET_break_op (0); 427 return GNUNET_SYSERR; 428 } 429 if (GNUNET_OK != 430 TALER_wallet_recoup_verify (&pc->dk->h_key, 431 &rh->details.recoup.coin_bks, 432 pc->coin_pub, 433 &rh->details.recoup.coin_sig)) 434 { 435 GNUNET_break_op (0); 436 return GNUNET_SYSERR; 437 } 438 return GNUNET_YES; 439 } 440 441 442 /** 443 * Handle recoup-refresh entry in the coin's history. 444 * This is the coin that was subjected to a recoup, 445 * the value being credited to the old coin. 446 * 447 * @param[in,out] pc overall context 448 * @param[out] rh history entry to initialize 449 * @param amount main amount of this operation 450 * @param transaction JSON details for the operation 451 * @return #GNUNET_SYSERR on error, 452 * #GNUNET_OK to add, #GNUNET_NO to subtract 453 */ 454 static enum GNUNET_GenericReturnValue 455 help_recoup_refresh (struct CoinHistoryParseContext *pc, 456 struct TALER_EXCHANGE_CoinHistoryEntry *rh, 457 const struct TALER_Amount *amount, 458 json_t *transaction) 459 { 460 struct GNUNET_JSON_Specification spec[] = { 461 GNUNET_JSON_spec_fixed_auto ("exchange_sig", 462 &rh->details.recoup_refresh.exchange_sig), 463 GNUNET_JSON_spec_fixed_auto ("exchange_pub", 464 &rh->details.recoup_refresh.exchange_pub), 465 GNUNET_JSON_spec_fixed_auto ("coin_sig", 466 &rh->details.recoup_refresh.coin_sig), 467 GNUNET_JSON_spec_fixed_auto ("old_coin_pub", 468 &rh->details.recoup_refresh.old_coin_pub), 469 GNUNET_JSON_spec_fixed_auto ("coin_blind", 470 &rh->details.recoup_refresh.coin_bks), 471 GNUNET_JSON_spec_timestamp ("timestamp", 472 &rh->details.recoup_refresh.timestamp), 473 GNUNET_JSON_spec_end () 474 }; 475 476 if (GNUNET_OK != 477 GNUNET_JSON_parse (transaction, 478 spec, 479 NULL, NULL)) 480 { 481 GNUNET_break_op (0); 482 return GNUNET_SYSERR; 483 } 484 if (GNUNET_OK != 485 TALER_exchange_online_confirm_recoup_refresh_verify ( 486 rh->details.recoup_refresh.timestamp, 487 amount, 488 pc->coin_pub, 489 &rh->details.recoup_refresh.old_coin_pub, 490 &rh->details.recoup_refresh.exchange_pub, 491 &rh->details.recoup_refresh.exchange_sig)) 492 { 493 GNUNET_break_op (0); 494 return GNUNET_SYSERR; 495 } 496 if (GNUNET_OK != 497 TALER_wallet_recoup_verify (&pc->dk->h_key, 498 &rh->details.recoup_refresh.coin_bks, 499 pc->coin_pub, 500 &rh->details.recoup_refresh.coin_sig)) 501 { 502 GNUNET_break_op (0); 503 return GNUNET_SYSERR; 504 } 505 return GNUNET_YES; 506 } 507 508 509 /** 510 * Handle old coin recoup entry in the coin's history. 511 * This is the coin that was credited in a recoup, 512 * the value being credited to the this coin. 513 * 514 * @param[in,out] pc overall context 515 * @param[out] rh history entry to initialize 516 * @param amount main amount of this operation 517 * @param transaction JSON details for the operation 518 * @return #GNUNET_SYSERR on error, 519 * #GNUNET_OK to add, #GNUNET_NO to subtract 520 */ 521 static enum GNUNET_GenericReturnValue 522 help_old_coin_recoup (struct CoinHistoryParseContext *pc, 523 struct TALER_EXCHANGE_CoinHistoryEntry *rh, 524 const struct TALER_Amount *amount, 525 json_t *transaction) 526 { 527 struct GNUNET_JSON_Specification spec[] = { 528 GNUNET_JSON_spec_fixed_auto ("exchange_sig", 529 &rh->details.old_coin_recoup.exchange_sig), 530 GNUNET_JSON_spec_fixed_auto ("exchange_pub", 531 &rh->details.old_coin_recoup.exchange_pub), 532 GNUNET_JSON_spec_fixed_auto ("coin_pub", 533 &rh->details.old_coin_recoup.new_coin_pub), 534 GNUNET_JSON_spec_timestamp ("timestamp", 535 &rh->details.old_coin_recoup.timestamp), 536 GNUNET_JSON_spec_end () 537 }; 538 539 if (GNUNET_OK != 540 GNUNET_JSON_parse (transaction, 541 spec, 542 NULL, NULL)) 543 { 544 GNUNET_break_op (0); 545 return GNUNET_SYSERR; 546 } 547 if (GNUNET_OK != 548 TALER_exchange_online_confirm_recoup_refresh_verify ( 549 rh->details.old_coin_recoup.timestamp, 550 amount, 551 &rh->details.old_coin_recoup.new_coin_pub, 552 pc->coin_pub, 553 &rh->details.old_coin_recoup.exchange_pub, 554 &rh->details.old_coin_recoup.exchange_sig)) 555 { 556 GNUNET_break_op (0); 557 return GNUNET_SYSERR; 558 } 559 return GNUNET_NO; 560 } 561 562 563 /** 564 * Handle purse deposit entry in the coin's history. 565 * 566 * @param[in,out] pc overall context 567 * @param[out] rh history entry to initialize 568 * @param amount main amount of this operation 569 * @param transaction JSON details for the operation 570 * @return #GNUNET_SYSERR on error, 571 * #GNUNET_OK to add, #GNUNET_NO to subtract 572 */ 573 static enum GNUNET_GenericReturnValue 574 help_purse_deposit (struct CoinHistoryParseContext *pc, 575 struct TALER_EXCHANGE_CoinHistoryEntry *rh, 576 const struct TALER_Amount *amount, 577 json_t *transaction) 578 { 579 struct GNUNET_JSON_Specification spec[] = { 580 TALER_JSON_spec_web_url ("exchange_base_url", 581 &rh->details.purse_deposit.exchange_base_url), 582 GNUNET_JSON_spec_mark_optional ( 583 GNUNET_JSON_spec_fixed_auto ("h_age_commitment", 584 &rh->details.purse_deposit.phac), 585 NULL), 586 // FIXME: return deposit_fee? 587 GNUNET_JSON_spec_fixed_auto ("purse_pub", 588 &rh->details.purse_deposit.purse_pub), 589 GNUNET_JSON_spec_bool ("refunded", 590 &rh->details.purse_deposit.refunded), 591 GNUNET_JSON_spec_fixed_auto ("coin_sig", 592 &rh->details.purse_deposit.coin_sig), 593 GNUNET_JSON_spec_end () 594 }; 595 596 if (GNUNET_OK != 597 GNUNET_JSON_parse (transaction, 598 spec, 599 NULL, NULL)) 600 { 601 GNUNET_break_op (0); 602 return GNUNET_SYSERR; 603 } 604 if (GNUNET_OK != 605 TALER_wallet_purse_deposit_verify ( 606 rh->details.purse_deposit.exchange_base_url, 607 &rh->details.purse_deposit.purse_pub, 608 amount, 609 &pc->dk->h_key, 610 &rh->details.purse_deposit.phac, 611 pc->coin_pub, 612 &rh->details.purse_deposit.coin_sig)) 613 { 614 GNUNET_break_op (0); 615 return GNUNET_SYSERR; 616 } 617 if (rh->details.purse_deposit.refunded) 618 { 619 /* We wave the deposit fee. */ 620 if (0 > 621 TALER_amount_add (pc->total_in, 622 pc->total_in, 623 &pc->dk->fees.deposit)) 624 { 625 /* overflow in refund history? inconceivable! Bad exchange! */ 626 GNUNET_break_op (0); 627 return GNUNET_SYSERR; 628 } 629 } 630 return GNUNET_YES; 631 } 632 633 634 /** 635 * Handle purse refund entry in the coin's history. 636 * 637 * @param[in,out] pc overall context 638 * @param[out] rh history entry to initialize 639 * @param amount main amount of this operation 640 * @param transaction JSON details for the operation 641 * @return #GNUNET_SYSERR on error, 642 * #GNUNET_OK to add, #GNUNET_NO to subtract 643 */ 644 static enum GNUNET_GenericReturnValue 645 help_purse_refund (struct CoinHistoryParseContext *pc, 646 struct TALER_EXCHANGE_CoinHistoryEntry *rh, 647 const struct TALER_Amount *amount, 648 json_t *transaction) 649 { 650 struct GNUNET_JSON_Specification spec[] = { 651 TALER_JSON_spec_amount_any ("refund_fee", 652 &rh->details.purse_refund.refund_fee), 653 GNUNET_JSON_spec_fixed_auto ("exchange_sig", 654 &rh->details.purse_refund.exchange_sig), 655 GNUNET_JSON_spec_fixed_auto ("exchange_pub", 656 &rh->details.purse_refund.exchange_pub), 657 GNUNET_JSON_spec_fixed_auto ("purse_pub", 658 &rh->details.purse_refund.purse_pub), 659 GNUNET_JSON_spec_end () 660 }; 661 662 if (GNUNET_OK != 663 GNUNET_JSON_parse (transaction, 664 spec, 665 NULL, NULL)) 666 { 667 GNUNET_break_op (0); 668 return GNUNET_SYSERR; 669 } 670 if (GNUNET_OK != 671 TALER_exchange_online_purse_refund_verify ( 672 amount, 673 &rh->details.purse_refund.refund_fee, 674 pc->coin_pub, 675 &rh->details.purse_refund.purse_pub, 676 &rh->details.purse_refund.exchange_pub, 677 &rh->details.purse_refund.exchange_sig)) 678 { 679 GNUNET_break_op (0); 680 return GNUNET_SYSERR; 681 } 682 if ( (GNUNET_YES != 683 TALER_amount_cmp_currency (&rh->details.purse_refund.refund_fee, 684 &pc->dk->fees.refund)) || 685 (0 != 686 TALER_amount_cmp (&rh->details.purse_refund.refund_fee, 687 &pc->dk->fees.refund)) ) 688 { 689 GNUNET_break_op (0); 690 return GNUNET_SYSERR; 691 } 692 return GNUNET_NO; 693 } 694 695 696 /** 697 * Handle reserve deposit entry in the coin's history. 698 * 699 * @param[in,out] pc overall context 700 * @param[out] rh history entry to initialize 701 * @param amount main amount of this operation 702 * @param transaction JSON details for the operation 703 * @return #GNUNET_SYSERR on error, 704 * #GNUNET_OK to add, #GNUNET_NO to subtract 705 */ 706 static enum GNUNET_GenericReturnValue 707 help_reserve_open_deposit (struct CoinHistoryParseContext *pc, 708 struct TALER_EXCHANGE_CoinHistoryEntry *rh, 709 const struct TALER_Amount *amount, 710 json_t *transaction) 711 { 712 struct GNUNET_JSON_Specification spec[] = { 713 GNUNET_JSON_spec_fixed_auto ("reserve_sig", 714 &rh->details.reserve_open_deposit.reserve_sig), 715 GNUNET_JSON_spec_fixed_auto ("coin_sig", 716 &rh->details.reserve_open_deposit.coin_sig), 717 GNUNET_JSON_spec_end () 718 }; 719 720 if (GNUNET_OK != 721 GNUNET_JSON_parse (transaction, 722 spec, 723 NULL, NULL)) 724 { 725 GNUNET_break_op (0); 726 return GNUNET_SYSERR; 727 } 728 if (GNUNET_OK != 729 TALER_wallet_reserve_open_deposit_verify ( 730 amount, 731 &rh->details.reserve_open_deposit.reserve_sig, 732 pc->coin_pub, 733 &rh->details.reserve_open_deposit.coin_sig)) 734 { 735 GNUNET_break_op (0); 736 return GNUNET_SYSERR; 737 } 738 return GNUNET_YES; 739 } 740 741 742 enum GNUNET_GenericReturnValue 743 TALER_EXCHANGE_parse_coin_history ( 744 const struct TALER_EXCHANGE_Keys *keys, 745 const struct TALER_EXCHANGE_DenomPublicKey *dk, 746 const json_t *history, 747 const struct TALER_CoinSpendPublicKeyP *coin_pub, 748 struct TALER_Amount *total_in, 749 struct TALER_Amount *total_out, 750 unsigned int rlen, 751 struct TALER_EXCHANGE_CoinHistoryEntry rhistory[static rlen]) 752 { 753 const struct 754 { 755 const char *type; 756 CoinCheckHelper helper; 757 enum TALER_EXCHANGE_CoinTransactionType ctt; 758 } map[] = { 759 { "DEPOSIT", 760 &help_deposit, 761 TALER_EXCHANGE_CTT_DEPOSIT }, 762 { "MELT", 763 &help_melt, 764 TALER_EXCHANGE_CTT_MELT }, 765 { "REFUND", 766 &help_refund, 767 TALER_EXCHANGE_CTT_REFUND }, 768 { "RECOUP", 769 &help_recoup, 770 TALER_EXCHANGE_CTT_RECOUP }, 771 { "RECOUP-REFRESH", 772 &help_recoup_refresh, 773 TALER_EXCHANGE_CTT_RECOUP_REFRESH }, 774 { "OLD-COIN-RECOUP", 775 &help_old_coin_recoup, 776 TALER_EXCHANGE_CTT_OLD_COIN_RECOUP }, 777 { "PURSE-DEPOSIT", 778 &help_purse_deposit, 779 TALER_EXCHANGE_CTT_PURSE_DEPOSIT }, 780 { "PURSE-REFUND", 781 &help_purse_refund, 782 TALER_EXCHANGE_CTT_PURSE_REFUND }, 783 { "RESERVE-OPEN-DEPOSIT", 784 &help_reserve_open_deposit, 785 TALER_EXCHANGE_CTT_RESERVE_OPEN_DEPOSIT }, 786 { NULL, NULL, TALER_EXCHANGE_CTT_NONE } 787 }; 788 struct CoinHistoryParseContext pc = { 789 .dk = dk, 790 .coin_pub = coin_pub, 791 .total_out = total_out, 792 .total_in = total_in 793 }; 794 size_t len; 795 796 if (NULL == history) 797 { 798 GNUNET_break_op (0); 799 return GNUNET_SYSERR; 800 } 801 len = json_array_size (history); 802 if (0 == len) 803 { 804 GNUNET_break_op (0); 805 return GNUNET_SYSERR; 806 } 807 *total_in = dk->value; 808 GNUNET_assert (GNUNET_OK == 809 TALER_amount_set_zero (total_in->currency, 810 total_out)); 811 for (size_t off = 0; off<len; off++) 812 { 813 struct TALER_EXCHANGE_CoinHistoryEntry *rh = &rhistory[off]; 814 json_t *transaction = json_array_get (history, 815 off); 816 enum GNUNET_GenericReturnValue add; 817 const char *type; 818 struct GNUNET_JSON_Specification spec_glob[] = { 819 TALER_JSON_spec_amount_any ("amount", 820 &rh->amount), 821 GNUNET_JSON_spec_string ("type", 822 &type), 823 GNUNET_JSON_spec_uint64 ("history_offset", 824 &rh->history_offset), 825 GNUNET_JSON_spec_end () 826 }; 827 828 if (GNUNET_OK != 829 GNUNET_JSON_parse (transaction, 830 spec_glob, 831 NULL, NULL)) 832 { 833 GNUNET_break_op (0); 834 return GNUNET_SYSERR; 835 } 836 if (GNUNET_YES != 837 TALER_amount_cmp_currency (&rh->amount, 838 total_in)) 839 { 840 GNUNET_break_op (0); 841 return GNUNET_SYSERR; 842 } 843 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 844 "Operation of type %s with amount %s\n", 845 type, 846 TALER_amount2s (&rh->amount)); 847 add = GNUNET_SYSERR; 848 for (unsigned int i = 0; NULL != map[i].type; i++) 849 { 850 if (0 == strcasecmp (type, 851 map[i].type)) 852 { 853 rh->type = map[i].ctt; 854 add = map[i].helper (&pc, 855 rh, 856 &rh->amount, 857 transaction); 858 break; 859 } 860 } 861 switch (add) 862 { 863 case GNUNET_SYSERR: 864 /* entry type not supported, new version on server? */ 865 rh->type = TALER_EXCHANGE_CTT_NONE; 866 GNUNET_break_op (0); 867 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 868 "Unexpected type `%s' in response\n", 869 type); 870 return GNUNET_SYSERR; 871 case GNUNET_YES: 872 /* This amount should be debited from the coin */ 873 if (0 > 874 TALER_amount_add (total_out, 875 total_out, 876 &rh->amount)) 877 { 878 /* overflow in history already!? inconceivable! Bad exchange! */ 879 GNUNET_break_op (0); 880 return GNUNET_SYSERR; 881 } 882 break; 883 case GNUNET_NO: 884 /* This amount should be credited to the coin. */ 885 if (0 > 886 TALER_amount_add (total_in, 887 total_in, 888 &rh->amount)) 889 { 890 /* overflow in refund history? inconceivable! Bad exchange! */ 891 GNUNET_break_op (0); 892 return GNUNET_SYSERR; 893 } 894 break; 895 } /* end of switch(add) */ 896 } 897 return GNUNET_OK; 898 } 899 900 901 /** 902 * We received an #MHD_HTTP_OK history code. Handle the JSON 903 * response. 904 * 905 * @param rsh handle of the request 906 * @param j JSON response 907 * @return #GNUNET_OK on success 908 */ 909 static enum GNUNET_GenericReturnValue 910 handle_coins_history_ok (struct TALER_EXCHANGE_CoinsHistoryHandle *rsh, 911 const json_t *j) 912 { 913 struct TALER_EXCHANGE_CoinHistory rs = { 914 .hr.reply = j, 915 .hr.http_status = MHD_HTTP_OK 916 }; 917 struct GNUNET_JSON_Specification spec[] = { 918 TALER_JSON_spec_amount_any ("balance", 919 &rs.details.ok.balance), 920 GNUNET_JSON_spec_fixed_auto ("h_denom_pub", 921 &rs.details.ok.h_denom_pub), 922 GNUNET_JSON_spec_array_const ("history", 923 &rs.details.ok.history), 924 GNUNET_JSON_spec_end () 925 }; 926 927 if (GNUNET_OK != 928 GNUNET_JSON_parse (j, 929 spec, 930 NULL, 931 NULL)) 932 { 933 GNUNET_break_op (0); 934 return GNUNET_SYSERR; 935 } 936 if (NULL != rsh->cb) 937 { 938 rsh->cb (rsh->cb_cls, 939 &rs); 940 rsh->cb = NULL; 941 } 942 GNUNET_JSON_parse_free (spec); 943 return GNUNET_OK; 944 } 945 946 947 /** 948 * Function called when we're done processing the 949 * HTTP /coins/$RID/history request. 950 * 951 * @param cls the `struct TALER_EXCHANGE_CoinsHistoryHandle` 952 * @param response_code HTTP response code, 0 on error 953 * @param response parsed JSON result, NULL on error 954 */ 955 static void 956 handle_coins_history_finished (void *cls, 957 long response_code, 958 const void *response) 959 { 960 struct TALER_EXCHANGE_CoinsHistoryHandle *rsh = cls; 961 const json_t *j = response; 962 struct TALER_EXCHANGE_CoinHistory rs = { 963 .hr.reply = j, 964 .hr.http_status = (unsigned int) response_code 965 }; 966 967 rsh->job = NULL; 968 switch (response_code) 969 { 970 case 0: 971 rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 972 break; 973 case MHD_HTTP_OK: 974 if (GNUNET_OK != 975 handle_coins_history_ok (rsh, 976 j)) 977 { 978 rs.hr.http_status = 0; 979 rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 980 } 981 break; 982 case MHD_HTTP_BAD_REQUEST: 983 /* This should never happen, either us or the exchange is buggy 984 (or API version conflict); just pass JSON reply to the application */ 985 GNUNET_break (0); 986 rs.hr.ec = TALER_JSON_get_error_code (j); 987 rs.hr.hint = TALER_JSON_get_error_hint (j); 988 break; 989 case MHD_HTTP_FORBIDDEN: 990 /* This should never happen, either us or the exchange is buggy 991 (or API version conflict); just pass JSON reply to the application */ 992 GNUNET_break (0); 993 rs.hr.ec = TALER_JSON_get_error_code (j); 994 rs.hr.hint = TALER_JSON_get_error_hint (j); 995 break; 996 case MHD_HTTP_NOT_FOUND: 997 /* Nothing really to verify, this should never 998 happen, we should pass the JSON reply to the application */ 999 rs.hr.ec = TALER_JSON_get_error_code (j); 1000 rs.hr.hint = TALER_JSON_get_error_hint (j); 1001 break; 1002 case MHD_HTTP_INTERNAL_SERVER_ERROR: 1003 /* Server had an internal issue; we should retry, but this API 1004 leaves this to the application */ 1005 rs.hr.ec = TALER_JSON_get_error_code (j); 1006 rs.hr.hint = TALER_JSON_get_error_hint (j); 1007 break; 1008 default: 1009 /* unexpected response code */ 1010 GNUNET_break_op (0); 1011 rs.hr.ec = TALER_JSON_get_error_code (j); 1012 rs.hr.hint = TALER_JSON_get_error_hint (j); 1013 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1014 "Unexpected response code %u/%d for coins history\n", 1015 (unsigned int) response_code, 1016 (int) rs.hr.ec); 1017 break; 1018 } 1019 if (NULL != rsh->cb) 1020 { 1021 rsh->cb (rsh->cb_cls, 1022 &rs); 1023 rsh->cb = NULL; 1024 } 1025 TALER_EXCHANGE_coins_history_cancel (rsh); 1026 } 1027 1028 1029 struct TALER_EXCHANGE_CoinsHistoryHandle * 1030 TALER_EXCHANGE_coins_history ( 1031 struct GNUNET_CURL_Context *ctx, 1032 const char *url, 1033 const struct TALER_CoinSpendPrivateKeyP *coin_priv, 1034 uint64_t start_off, 1035 TALER_EXCHANGE_CoinsHistoryCallback cb, 1036 void *cb_cls) 1037 { 1038 struct TALER_EXCHANGE_CoinsHistoryHandle *rsh; 1039 CURL *eh; 1040 char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 64]; 1041 struct curl_slist *job_headers; 1042 1043 rsh = GNUNET_new (struct TALER_EXCHANGE_CoinsHistoryHandle); 1044 rsh->cb = cb; 1045 rsh->cb_cls = cb_cls; 1046 GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, 1047 &rsh->coin_pub.eddsa_pub); 1048 { 1049 char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2]; 1050 char *end; 1051 1052 end = GNUNET_STRINGS_data_to_string ( 1053 &rsh->coin_pub, 1054 sizeof (rsh->coin_pub), 1055 pub_str, 1056 sizeof (pub_str)); 1057 *end = '\0'; 1058 if (0 != start_off) 1059 GNUNET_snprintf (arg_str, 1060 sizeof (arg_str), 1061 "coins/%s/history?start=%llu", 1062 pub_str, 1063 (unsigned long long) start_off); 1064 else 1065 GNUNET_snprintf (arg_str, 1066 sizeof (arg_str), 1067 "coins/%s/history", 1068 pub_str); 1069 } 1070 rsh->url = TALER_url_join (url, 1071 arg_str, 1072 NULL); 1073 if (NULL == rsh->url) 1074 { 1075 GNUNET_free (rsh); 1076 return NULL; 1077 } 1078 eh = TALER_EXCHANGE_curl_easy_get_ (rsh->url); 1079 if (NULL == eh) 1080 { 1081 GNUNET_break (0); 1082 GNUNET_free (rsh->url); 1083 GNUNET_free (rsh); 1084 return NULL; 1085 } 1086 1087 { 1088 struct TALER_CoinSpendSignatureP coin_sig; 1089 char *sig_hdr; 1090 char *hdr; 1091 1092 TALER_wallet_coin_history_sign (start_off, 1093 coin_priv, 1094 &coin_sig); 1095 1096 sig_hdr = GNUNET_STRINGS_data_to_string_alloc ( 1097 &coin_sig, 1098 sizeof (coin_sig)); 1099 GNUNET_asprintf (&hdr, 1100 "%s: %s", 1101 TALER_COIN_HISTORY_SIGNATURE_HEADER, 1102 sig_hdr); 1103 GNUNET_free (sig_hdr); 1104 job_headers = curl_slist_append (NULL, 1105 hdr); 1106 GNUNET_free (hdr); 1107 if (NULL == job_headers) 1108 { 1109 GNUNET_break (0); 1110 curl_easy_cleanup (eh); 1111 return NULL; 1112 } 1113 } 1114 1115 rsh->job = GNUNET_CURL_job_add2 (ctx, 1116 eh, 1117 job_headers, 1118 &handle_coins_history_finished, 1119 rsh); 1120 curl_slist_free_all (job_headers); 1121 return rsh; 1122 } 1123 1124 1125 void 1126 TALER_EXCHANGE_coins_history_cancel ( 1127 struct TALER_EXCHANGE_CoinsHistoryHandle *rsh) 1128 { 1129 if (NULL != rsh->job) 1130 { 1131 GNUNET_CURL_job_cancel (rsh->job); 1132 rsh->job = NULL; 1133 } 1134 TALER_curl_easy_post_finished (&rsh->post_ctx); 1135 GNUNET_free (rsh->url); 1136 GNUNET_free (rsh); 1137 } 1138 1139 1140 /** 1141 * Verify that @a coin_sig does NOT appear in the @a history of a coin's 1142 * transactions and thus whatever transaction is authorized by @a coin_sig is 1143 * a conflict with @a proof. 1144 * 1145 * @param history coin history to check 1146 * @param coin_sig signature that must not be in @a history 1147 * @return #GNUNET_OK if @a coin_sig is not in @a history 1148 */ 1149 enum GNUNET_GenericReturnValue 1150 TALER_EXCHANGE_check_coin_signature_conflict ( 1151 const json_t *history, 1152 const struct TALER_CoinSpendSignatureP *coin_sig) 1153 { 1154 size_t off; 1155 json_t *entry; 1156 1157 json_array_foreach (history, off, entry) 1158 { 1159 struct TALER_CoinSpendSignatureP cs; 1160 struct GNUNET_JSON_Specification spec[] = { 1161 GNUNET_JSON_spec_fixed_auto ("coin_sig", 1162 &cs), 1163 GNUNET_JSON_spec_end () 1164 }; 1165 1166 if (GNUNET_OK != 1167 GNUNET_JSON_parse (entry, 1168 spec, 1169 NULL, NULL)) 1170 continue; /* entry without coin signature */ 1171 if (0 == 1172 GNUNET_memcmp (&cs, 1173 coin_sig)) 1174 { 1175 GNUNET_break_op (0); 1176 return GNUNET_SYSERR; 1177 } 1178 } 1179 return GNUNET_OK; 1180 } 1181 1182 1183 #if FIXME_IMPLEMENT /* #9422 */ 1184 /** 1185 * FIXME-Oec-#9422: we need some specific routines that show 1186 * that certain coin operations are indeed in conflict, 1187 * for example that the coin is of a different denomination 1188 * or different age restrictions. 1189 * This relates to unimplemented error handling for 1190 * coins in the exchange! 1191 * 1192 * Check that the provided @a proof indeeds indicates 1193 * a conflict for @a coin_pub. 1194 * 1195 * @param keys exchange keys 1196 * @param proof provided conflict proof 1197 * @param dk denomination of @a coin_pub that the client 1198 * used 1199 * @param coin_pub public key of the coin 1200 * @param required balance required on the coin for the operation 1201 * @return #GNUNET_OK if @a proof holds 1202 */ 1203 // FIXME-#9422: should be properly defined and implemented! 1204 enum GNUNET_GenericReturnValue 1205 TALER_EXCHANGE_check_coin_conflict_ ( 1206 const struct TALER_EXCHANGE_Keys *keys, 1207 const json_t *proof, 1208 const struct TALER_EXCHANGE_DenomPublicKey *dk, 1209 const struct TALER_CoinSpendPublicKeyP *coin_pub, 1210 const struct TALER_Amount *required) 1211 { 1212 enum TALER_ErrorCode ec; 1213 1214 ec = TALER_JSON_get_error_code (proof); 1215 switch (ec) 1216 { 1217 case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS: 1218 /* Nothing to check anymore here, proof needs to be 1219 checked in the GET /coins/$COIN_PUB handler */ 1220 break; 1221 case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY: 1222 // FIXME-#9422: write check! 1223 break; 1224 default: 1225 GNUNET_break_op (0); 1226 return GNUNET_SYSERR; 1227 } 1228 return GNUNET_OK; 1229 } 1230 1231 1232 #endif 1233 1234 1235 /* end of exchange_api_coins_history.c */