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