exchange_api_get-coins-COIN_PUB-history.c (38768B)
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 TALER_JSON_spec_amount_any ("coin_contribution", 726 &rh->details.reserve_open_deposit. 727 coin_contribution), 728 GNUNET_JSON_spec_end () 729 }; 730 731 if (GNUNET_OK != 732 GNUNET_JSON_parse (transaction, 733 spec, 734 NULL, NULL)) 735 { 736 GNUNET_break_op (0); 737 return GNUNET_SYSERR; 738 } 739 if (GNUNET_OK != 740 TALER_wallet_reserve_open_deposit_verify ( 741 amount, 742 &rh->details.reserve_open_deposit.reserve_sig, 743 pc->coin_pub, 744 &rh->details.reserve_open_deposit.coin_sig)) 745 { 746 GNUNET_break_op (0); 747 return GNUNET_SYSERR; 748 } 749 return GNUNET_YES; 750 } 751 752 753 enum GNUNET_GenericReturnValue 754 TALER_EXCHANGE_parse_coin_history ( 755 const struct TALER_EXCHANGE_Keys *keys, 756 const struct TALER_EXCHANGE_DenomPublicKey *dk, 757 const json_t *history, 758 const struct TALER_CoinSpendPublicKeyP *coin_pub, 759 struct TALER_Amount *total_in, 760 struct TALER_Amount *total_out, 761 unsigned int rlen, 762 struct TALER_EXCHANGE_CoinHistoryEntry rhistory[static rlen]) 763 { 764 const struct 765 { 766 const char *type; 767 CoinCheckHelper helper; 768 enum TALER_EXCHANGE_CoinTransactionType ctt; 769 } map[] = { 770 { "DEPOSIT", 771 &help_deposit, 772 TALER_EXCHANGE_CTT_DEPOSIT }, 773 { "MELT", 774 &help_melt, 775 TALER_EXCHANGE_CTT_MELT }, 776 { "REFUND", 777 &help_refund, 778 TALER_EXCHANGE_CTT_REFUND }, 779 { "RECOUP-WITHDRAW", 780 &help_recoup, 781 TALER_EXCHANGE_CTT_RECOUP }, 782 { "RECOUP-REFRESH", 783 &help_recoup_refresh, 784 TALER_EXCHANGE_CTT_RECOUP_REFRESH }, 785 { "RECOUP-REFRESH-RECEIVER", 786 &help_old_coin_recoup, 787 TALER_EXCHANGE_CTT_OLD_COIN_RECOUP }, 788 { "PURSE-DEPOSIT", 789 &help_purse_deposit, 790 TALER_EXCHANGE_CTT_PURSE_DEPOSIT }, 791 { "PURSE-REFUND", 792 &help_purse_refund, 793 TALER_EXCHANGE_CTT_PURSE_REFUND }, 794 { "RESERVE-OPEN-DEPOSIT", 795 &help_reserve_open_deposit, 796 TALER_EXCHANGE_CTT_RESERVE_OPEN_DEPOSIT }, 797 { NULL, NULL, TALER_EXCHANGE_CTT_NONE } 798 }; 799 struct CoinHistoryParseContext pc = { 800 .dk = dk, 801 .coin_pub = coin_pub, 802 .total_out = total_out, 803 .total_in = total_in 804 }; 805 size_t len; 806 807 if (NULL == history) 808 { 809 GNUNET_break_op (0); 810 return GNUNET_SYSERR; 811 } 812 len = json_array_size (history); 813 if (0 == len) 814 { 815 GNUNET_break_op (0); 816 return GNUNET_SYSERR; 817 } 818 *total_in = dk->value; 819 GNUNET_assert (GNUNET_OK == 820 TALER_amount_set_zero (total_in->currency, 821 total_out)); 822 for (size_t off = 0; off < len; off++) 823 { 824 struct TALER_EXCHANGE_CoinHistoryEntry *rh = &rhistory[off]; 825 json_t *transaction = json_array_get (history, 826 off); 827 enum GNUNET_GenericReturnValue add; 828 const char *type; 829 struct GNUNET_JSON_Specification spec_glob[] = { 830 TALER_JSON_spec_amount_any ("amount", 831 &rh->amount), 832 GNUNET_JSON_spec_string ("type", 833 &type), 834 GNUNET_JSON_spec_uint64 ("history_offset", 835 &rh->history_offset), 836 GNUNET_JSON_spec_end () 837 }; 838 839 if (GNUNET_OK != 840 GNUNET_JSON_parse (transaction, 841 spec_glob, 842 NULL, NULL)) 843 { 844 GNUNET_break_op (0); 845 return GNUNET_SYSERR; 846 } 847 if (GNUNET_YES != 848 TALER_amount_cmp_currency (&rh->amount, 849 total_in)) 850 { 851 GNUNET_break_op (0); 852 return GNUNET_SYSERR; 853 } 854 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 855 "Operation of type %s with amount %s\n", 856 type, 857 TALER_amount2s (&rh->amount)); 858 add = GNUNET_SYSERR; 859 for (unsigned int i = 0; NULL != map[i].type; i++) 860 { 861 if (0 == strcasecmp (type, 862 map[i].type)) 863 { 864 rh->type = map[i].ctt; 865 add = map[i].helper (&pc, 866 rh, 867 &rh->amount, 868 transaction); 869 break; 870 } 871 } 872 switch (add) 873 { 874 case GNUNET_SYSERR: 875 /* entry type not supported, new version on server? */ 876 rh->type = TALER_EXCHANGE_CTT_NONE; 877 GNUNET_break_op (0); 878 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 879 "Unexpected type `%s' in response\n", 880 type); 881 return GNUNET_SYSERR; 882 case GNUNET_YES: 883 /* This amount should be debited from the coin */ 884 if (0 > 885 TALER_amount_add (total_out, 886 total_out, 887 &rh->amount)) 888 { 889 /* overflow in history already!? inconceivable! Bad exchange! */ 890 GNUNET_break_op (0); 891 return GNUNET_SYSERR; 892 } 893 break; 894 case GNUNET_NO: 895 /* This amount should be credited to the coin. */ 896 if (0 > 897 TALER_amount_add (total_in, 898 total_in, 899 &rh->amount)) 900 { 901 /* overflow in refund history? inconceivable! Bad exchange! */ 902 GNUNET_break_op (0); 903 return GNUNET_SYSERR; 904 } 905 break; 906 } /* end of switch(add) */ 907 } 908 return GNUNET_OK; 909 } 910 911 912 /** 913 * We received an #MHD_HTTP_OK response. Handle the JSON response. 914 * 915 * @param gcsh handle of the request 916 * @param j JSON response 917 * @return #GNUNET_OK on success 918 */ 919 static enum GNUNET_GenericReturnValue 920 handle_coins_history_ok (struct TALER_EXCHANGE_GetCoinsHistoryHandle *gcsh, 921 const json_t *j) 922 { 923 struct TALER_EXCHANGE_GetCoinsHistoryResponse rs = { 924 .hr.reply = j, 925 .hr.http_status = MHD_HTTP_OK 926 }; 927 struct GNUNET_JSON_Specification spec[] = { 928 TALER_JSON_spec_amount_any ("balance", 929 &rs.details.ok.balance), 930 GNUNET_JSON_spec_fixed_auto ("h_denom_pub", 931 &rs.details.ok.h_denom_pub), 932 GNUNET_JSON_spec_array_const ("history", 933 &rs.details.ok.history), 934 GNUNET_JSON_spec_end () 935 }; 936 937 if (GNUNET_OK != 938 GNUNET_JSON_parse (j, 939 spec, 940 NULL, 941 NULL)) 942 { 943 GNUNET_break_op (0); 944 return GNUNET_SYSERR; 945 } 946 if (NULL != gcsh->cb) 947 { 948 gcsh->cb (gcsh->cb_cls, 949 &rs); 950 gcsh->cb = NULL; 951 } 952 GNUNET_JSON_parse_free (spec); 953 return GNUNET_OK; 954 } 955 956 957 /** 958 * Function called when we're done processing the 959 * HTTP GET /coins/$COIN_PUB/history request. 960 * 961 * @param cls the `struct TALER_EXCHANGE_GetCoinsHistoryHandle` 962 * @param response_code HTTP response code, 0 on error 963 * @param response parsed JSON result, NULL on error 964 */ 965 static void 966 handle_coins_history_finished (void *cls, 967 long response_code, 968 const void *response) 969 { 970 struct TALER_EXCHANGE_GetCoinsHistoryHandle *gcsh = cls; 971 const json_t *j = response; 972 struct TALER_EXCHANGE_GetCoinsHistoryResponse rs = { 973 .hr.reply = j, 974 .hr.http_status = (unsigned int) response_code 975 }; 976 977 gcsh->job = NULL; 978 switch (response_code) 979 { 980 case 0: 981 rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 982 break; 983 case MHD_HTTP_OK: 984 if (GNUNET_OK != 985 handle_coins_history_ok (gcsh, 986 j)) 987 { 988 rs.hr.http_status = 0; 989 rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 990 } 991 break; 992 case MHD_HTTP_NO_CONTENT: 993 break; 994 case MHD_HTTP_NOT_MODIFIED: 995 break; 996 case MHD_HTTP_BAD_REQUEST: 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_FORBIDDEN: 1004 /* This should never happen, either us or the exchange is buggy 1005 (or API version conflict); just pass JSON reply to the application */ 1006 GNUNET_break (0); 1007 rs.hr.ec = TALER_JSON_get_error_code (j); 1008 rs.hr.hint = TALER_JSON_get_error_hint (j); 1009 break; 1010 case MHD_HTTP_NOT_FOUND: 1011 rs.hr.ec = TALER_JSON_get_error_code (j); 1012 rs.hr.hint = TALER_JSON_get_error_hint (j); 1013 break; 1014 case MHD_HTTP_INTERNAL_SERVER_ERROR: 1015 /* Server had an internal issue; we should retry, but this API 1016 leaves this to the application */ 1017 rs.hr.ec = TALER_JSON_get_error_code (j); 1018 rs.hr.hint = TALER_JSON_get_error_hint (j); 1019 break; 1020 default: 1021 /* unexpected response code */ 1022 GNUNET_break_op (0); 1023 rs.hr.ec = TALER_JSON_get_error_code (j); 1024 rs.hr.hint = TALER_JSON_get_error_hint (j); 1025 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1026 "Unexpected response code %u/%d for coins history\n", 1027 (unsigned int) response_code, 1028 (int) rs.hr.ec); 1029 break; 1030 } 1031 if (NULL != gcsh->cb) 1032 { 1033 gcsh->cb (gcsh->cb_cls, 1034 &rs); 1035 gcsh->cb = NULL; 1036 } 1037 TALER_EXCHANGE_get_coins_history_cancel (gcsh); 1038 } 1039 1040 1041 struct TALER_EXCHANGE_GetCoinsHistoryHandle * 1042 TALER_EXCHANGE_get_coins_history_create ( 1043 struct GNUNET_CURL_Context *ctx, 1044 const char *url, 1045 const struct TALER_CoinSpendPrivateKeyP *coin_priv) 1046 { 1047 struct TALER_EXCHANGE_GetCoinsHistoryHandle *gcsh; 1048 1049 gcsh = GNUNET_new (struct TALER_EXCHANGE_GetCoinsHistoryHandle); 1050 gcsh->ctx = ctx; 1051 gcsh->base_url = GNUNET_strdup (url); 1052 gcsh->coin_priv = *coin_priv; 1053 GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, 1054 &gcsh->coin_pub.eddsa_pub); 1055 gcsh->options.start_off = 0; 1056 return gcsh; 1057 } 1058 1059 1060 enum GNUNET_GenericReturnValue 1061 TALER_EXCHANGE_get_coins_history_set_options_ ( 1062 struct TALER_EXCHANGE_GetCoinsHistoryHandle *gcsh, 1063 unsigned int num_options, 1064 const struct TALER_EXCHANGE_GetCoinsHistoryOptionValue *options) 1065 { 1066 for (unsigned int i = 0; i < num_options; i++) 1067 { 1068 const struct TALER_EXCHANGE_GetCoinsHistoryOptionValue *opt = &options[i]; 1069 1070 switch (opt->option) 1071 { 1072 case TALER_EXCHANGE_GET_COINS_HISTORY_OPTION_END: 1073 return GNUNET_OK; 1074 case TALER_EXCHANGE_GET_COINS_HISTORY_OPTION_START_OFF: 1075 gcsh->options.start_off = opt->details.start_off; 1076 break; 1077 } 1078 } 1079 return GNUNET_OK; 1080 } 1081 1082 1083 enum TALER_ErrorCode 1084 TALER_EXCHANGE_get_coins_history_start ( 1085 struct TALER_EXCHANGE_GetCoinsHistoryHandle *gcsh, 1086 TALER_EXCHANGE_GetCoinsHistoryCallback cb, 1087 TALER_EXCHANGE_GET_COINS_HISTORY_RESULT_CLOSURE *cb_cls) 1088 { 1089 char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 64]; 1090 struct curl_slist *job_headers; 1091 CURL *eh; 1092 1093 if (NULL != gcsh->job) 1094 { 1095 GNUNET_break (0); 1096 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 1097 } 1098 gcsh->cb = cb; 1099 gcsh->cb_cls = cb_cls; 1100 { 1101 char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2]; 1102 char *end; 1103 1104 end = GNUNET_STRINGS_data_to_string ( 1105 &gcsh->coin_pub, 1106 sizeof (gcsh->coin_pub), 1107 pub_str, 1108 sizeof (pub_str)); 1109 *end = '\0'; 1110 if (0 != gcsh->options.start_off) 1111 GNUNET_snprintf (arg_str, 1112 sizeof (arg_str), 1113 "coins/%s/history?start=%llu", 1114 pub_str, 1115 (unsigned long long) gcsh->options.start_off); 1116 else 1117 GNUNET_snprintf (arg_str, 1118 sizeof (arg_str), 1119 "coins/%s/history", 1120 pub_str); 1121 } 1122 gcsh->url = TALER_url_join (gcsh->base_url, 1123 arg_str, 1124 NULL); 1125 if (NULL == gcsh->url) 1126 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 1127 eh = TALER_EXCHANGE_curl_easy_get_ (gcsh->url); 1128 if (NULL == eh) 1129 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 1130 { 1131 struct TALER_CoinSpendSignatureP coin_sig; 1132 char *sig_hdr; 1133 char *hdr; 1134 1135 TALER_wallet_coin_history_sign (gcsh->options.start_off, 1136 &gcsh->coin_priv, 1137 &coin_sig); 1138 sig_hdr = GNUNET_STRINGS_data_to_string_alloc ( 1139 &coin_sig, 1140 sizeof (coin_sig)); 1141 GNUNET_asprintf (&hdr, 1142 "%s: %s", 1143 TALER_COIN_HISTORY_SIGNATURE_HEADER, 1144 sig_hdr); 1145 GNUNET_free (sig_hdr); 1146 job_headers = curl_slist_append (NULL, 1147 hdr); 1148 GNUNET_free (hdr); 1149 if (NULL == job_headers) 1150 { 1151 GNUNET_break (0); 1152 curl_easy_cleanup (eh); 1153 GNUNET_free (gcsh->url); 1154 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 1155 } 1156 } 1157 gcsh->job = GNUNET_CURL_job_add2 (gcsh->ctx, 1158 eh, 1159 job_headers, 1160 &handle_coins_history_finished, 1161 gcsh); 1162 curl_slist_free_all (job_headers); 1163 if (NULL == gcsh->job) 1164 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 1165 return TALER_EC_NONE; 1166 } 1167 1168 1169 void 1170 TALER_EXCHANGE_get_coins_history_cancel ( 1171 struct TALER_EXCHANGE_GetCoinsHistoryHandle *gcsh) 1172 { 1173 if (NULL != gcsh->job) 1174 { 1175 GNUNET_CURL_job_cancel (gcsh->job); 1176 gcsh->job = NULL; 1177 } 1178 GNUNET_free (gcsh->url); 1179 GNUNET_free (gcsh->base_url); 1180 GNUNET_free (gcsh); 1181 } 1182 1183 1184 enum GNUNET_GenericReturnValue 1185 TALER_EXCHANGE_check_coin_signature_conflict ( 1186 const json_t *history, 1187 const struct TALER_CoinSpendSignatureP *coin_sig) 1188 { 1189 size_t off; 1190 json_t *entry; 1191 1192 json_array_foreach (history, off, entry) 1193 { 1194 struct TALER_CoinSpendSignatureP cs; 1195 struct GNUNET_JSON_Specification spec[] = { 1196 GNUNET_JSON_spec_fixed_auto ("coin_sig", 1197 &cs), 1198 GNUNET_JSON_spec_end () 1199 }; 1200 1201 if (GNUNET_OK != 1202 GNUNET_JSON_parse (entry, 1203 spec, 1204 NULL, NULL)) 1205 continue; /* entry without coin signature */ 1206 if (0 == 1207 GNUNET_memcmp (&cs, 1208 coin_sig)) 1209 { 1210 GNUNET_break_op (0); 1211 return GNUNET_SYSERR; 1212 } 1213 } 1214 return GNUNET_OK; 1215 } 1216 1217 1218 #if FIXME_IMPLEMENT /* #9422 */ 1219 /** 1220 * FIXME-Oec-#9422: we need some specific routines that show 1221 * that certain coin operations are indeed in conflict, 1222 * for example that the coin is of a different denomination 1223 * or different age restrictions. 1224 * This relates to unimplemented error handling for 1225 * coins in the exchange! 1226 * 1227 * Check that the provided @a proof indeeds indicates 1228 * a conflict for @a coin_pub. 1229 * 1230 * @param keys exchange keys 1231 * @param proof provided conflict proof 1232 * @param dk denomination of @a coin_pub that the client 1233 * used 1234 * @param coin_pub public key of the coin 1235 * @param required balance required on the coin for the operation 1236 * @return #GNUNET_OK if @a proof holds 1237 */ 1238 // FIXME-#9422: should be properly defined and implemented! 1239 enum GNUNET_GenericReturnValue 1240 TALER_EXCHANGE_check_coin_conflict_ ( 1241 const struct TALER_EXCHANGE_Keys *keys, 1242 const json_t *proof, 1243 const struct TALER_EXCHANGE_DenomPublicKey *dk, 1244 const struct TALER_CoinSpendPublicKeyP *coin_pub, 1245 const struct TALER_Amount *required) 1246 { 1247 enum TALER_ErrorCode ec; 1248 1249 ec = TALER_JSON_get_error_code (proof); 1250 switch (ec) 1251 { 1252 case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS: 1253 /* Nothing to check anymore here, proof needs to be 1254 checked in the GET /coins/$COIN_PUB handler */ 1255 break; 1256 case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY: 1257 // FIXME-#9422: write check! 1258 break; 1259 default: 1260 GNUNET_break_op (0); 1261 return GNUNET_SYSERR; 1262 } 1263 return GNUNET_OK; 1264 } 1265 1266 1267 #endif 1268 1269 1270 /* end of exchange_api_get-coins-COIN_PUB-history.c */