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