exchange_api_get-coins-COIN_PUB-history.c (39751B)
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 const 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_test_signing_key (pc->keys, 426 &rh->details.recoup.exchange_pub)) 427 { 428 GNUNET_break_op (0); 429 return GNUNET_SYSERR; 430 } 431 if (GNUNET_OK != 432 TALER_exchange_online_confirm_recoup_verify ( 433 rh->details.recoup.timestamp, 434 amount, 435 pc->coin_pub, 436 &rh->details.recoup.reserve_pub, 437 &rh->details.recoup.exchange_pub, 438 &rh->details.recoup.exchange_sig)) 439 { 440 GNUNET_break_op (0); 441 return GNUNET_SYSERR; 442 } 443 if (GNUNET_OK != 444 TALER_wallet_recoup_verify (&pc->dk->h_key, 445 &rh->details.recoup.coin_bks, 446 pc->coin_pub, 447 &rh->details.recoup.coin_sig)) 448 { 449 GNUNET_break_op (0); 450 return GNUNET_SYSERR; 451 } 452 return GNUNET_YES; 453 } 454 455 456 /** 457 * Handle recoup-refresh entry in the coin's history. 458 * This is the coin that was subjected to a recoup, 459 * the value being credited to the old coin. 460 * 461 * @param[in,out] pc overall context 462 * @param[out] rh history entry to initialize 463 * @param amount main amount of this operation 464 * @param transaction JSON details for the operation 465 * @return #GNUNET_SYSERR on error, 466 * #GNUNET_OK to add, #GNUNET_NO to subtract 467 */ 468 static enum GNUNET_GenericReturnValue 469 help_recoup_refresh (struct CoinHistoryParseContext *pc, 470 struct TALER_EXCHANGE_CoinHistoryEntry *rh, 471 const struct TALER_Amount *amount, 472 json_t *transaction) 473 { 474 struct GNUNET_JSON_Specification spec[] = { 475 GNUNET_JSON_spec_fixed_auto ("exchange_sig", 476 &rh->details.recoup_refresh.exchange_sig), 477 GNUNET_JSON_spec_fixed_auto ("exchange_pub", 478 &rh->details.recoup_refresh.exchange_pub), 479 GNUNET_JSON_spec_fixed_auto ("coin_sig", 480 &rh->details.recoup_refresh.coin_sig), 481 GNUNET_JSON_spec_fixed_auto ("old_coin_pub", 482 &rh->details.recoup_refresh.old_coin_pub), 483 GNUNET_JSON_spec_fixed_auto ("coin_blind", 484 &rh->details.recoup_refresh.coin_bks), 485 GNUNET_JSON_spec_timestamp ("timestamp", 486 &rh->details.recoup_refresh.timestamp), 487 GNUNET_JSON_spec_end () 488 }; 489 490 if (GNUNET_OK != 491 GNUNET_JSON_parse (transaction, 492 spec, 493 NULL, NULL)) 494 { 495 GNUNET_break_op (0); 496 return GNUNET_SYSERR; 497 } 498 if (GNUNET_OK != 499 TALER_EXCHANGE_test_signing_key ( 500 pc->keys, 501 &rh->details.recoup_refresh.exchange_pub)) 502 { 503 GNUNET_break_op (0); 504 return GNUNET_SYSERR; 505 } 506 if (GNUNET_OK != 507 TALER_exchange_online_confirm_recoup_refresh_verify ( 508 rh->details.recoup_refresh.timestamp, 509 amount, 510 pc->coin_pub, 511 &rh->details.recoup_refresh.old_coin_pub, 512 &rh->details.recoup_refresh.exchange_pub, 513 &rh->details.recoup_refresh.exchange_sig)) 514 { 515 GNUNET_break_op (0); 516 return GNUNET_SYSERR; 517 } 518 if (GNUNET_OK != 519 TALER_wallet_recoup_verify (&pc->dk->h_key, 520 &rh->details.recoup_refresh.coin_bks, 521 pc->coin_pub, 522 &rh->details.recoup_refresh.coin_sig)) 523 { 524 GNUNET_break_op (0); 525 return GNUNET_SYSERR; 526 } 527 return GNUNET_YES; 528 } 529 530 531 /** 532 * Handle old coin recoup entry in the coin's history. 533 * This is the coin that was credited in a recoup, 534 * the value being credited to this coin. 535 * 536 * @param[in,out] pc overall context 537 * @param[out] rh history entry to initialize 538 * @param amount main amount of this operation 539 * @param transaction JSON details for the operation 540 * @return #GNUNET_SYSERR on error, 541 * #GNUNET_OK to add, #GNUNET_NO to subtract 542 */ 543 static enum GNUNET_GenericReturnValue 544 help_old_coin_recoup (struct CoinHistoryParseContext *pc, 545 struct TALER_EXCHANGE_CoinHistoryEntry *rh, 546 const struct TALER_Amount *amount, 547 json_t *transaction) 548 { 549 struct GNUNET_JSON_Specification spec[] = { 550 GNUNET_JSON_spec_fixed_auto ("exchange_sig", 551 &rh->details.old_coin_recoup.exchange_sig), 552 GNUNET_JSON_spec_fixed_auto ("exchange_pub", 553 &rh->details.old_coin_recoup.exchange_pub), 554 GNUNET_JSON_spec_fixed_auto ("coin_pub", 555 &rh->details.old_coin_recoup.new_coin_pub), 556 GNUNET_JSON_spec_timestamp ("timestamp", 557 &rh->details.old_coin_recoup.timestamp), 558 GNUNET_JSON_spec_end () 559 }; 560 561 if (GNUNET_OK != 562 GNUNET_JSON_parse (transaction, 563 spec, 564 NULL, NULL)) 565 { 566 GNUNET_break_op (0); 567 return GNUNET_SYSERR; 568 } 569 if (GNUNET_OK != 570 TALER_EXCHANGE_test_signing_key ( 571 pc->keys, 572 &rh->details.old_coin_recoup.exchange_pub)) 573 { 574 GNUNET_break_op (0); 575 return GNUNET_SYSERR; 576 } 577 if (GNUNET_OK != 578 TALER_exchange_online_confirm_recoup_refresh_verify ( 579 rh->details.old_coin_recoup.timestamp, 580 amount, 581 &rh->details.old_coin_recoup.new_coin_pub, 582 pc->coin_pub, 583 &rh->details.old_coin_recoup.exchange_pub, 584 &rh->details.old_coin_recoup.exchange_sig)) 585 { 586 GNUNET_break_op (0); 587 return GNUNET_SYSERR; 588 } 589 return GNUNET_NO; 590 } 591 592 593 /** 594 * Handle purse deposit entry in the coin's history. 595 * 596 * @param[in,out] pc overall context 597 * @param[out] rh history entry to initialize 598 * @param amount main amount of this operation 599 * @param transaction JSON details for the operation 600 * @return #GNUNET_SYSERR on error, 601 * #GNUNET_OK to add, #GNUNET_NO to subtract 602 */ 603 static enum GNUNET_GenericReturnValue 604 help_purse_deposit (struct CoinHistoryParseContext *pc, 605 struct TALER_EXCHANGE_CoinHistoryEntry *rh, 606 const struct TALER_Amount *amount, 607 json_t *transaction) 608 { 609 struct GNUNET_JSON_Specification spec[] = { 610 TALER_JSON_spec_web_url ("exchange_base_url", 611 &rh->details.purse_deposit.exchange_base_url), 612 GNUNET_JSON_spec_mark_optional ( 613 GNUNET_JSON_spec_fixed_auto ("h_age_commitment", 614 &rh->details.purse_deposit.phac), 615 NULL), 616 GNUNET_JSON_spec_fixed_auto ("purse_pub", 617 &rh->details.purse_deposit.purse_pub), 618 GNUNET_JSON_spec_bool ("refunded", 619 &rh->details.purse_deposit.refunded), 620 GNUNET_JSON_spec_fixed_auto ("coin_sig", 621 &rh->details.purse_deposit.coin_sig), 622 GNUNET_JSON_spec_end () 623 }; 624 625 if (GNUNET_OK != 626 GNUNET_JSON_parse (transaction, 627 spec, 628 NULL, NULL)) 629 { 630 GNUNET_break_op (0); 631 return GNUNET_SYSERR; 632 } 633 if (GNUNET_OK != 634 TALER_wallet_purse_deposit_verify ( 635 rh->details.purse_deposit.exchange_base_url, 636 &rh->details.purse_deposit.purse_pub, 637 amount, 638 &pc->dk->h_key, 639 &rh->details.purse_deposit.phac, 640 pc->coin_pub, 641 &rh->details.purse_deposit.coin_sig)) 642 { 643 GNUNET_break_op (0); 644 return GNUNET_SYSERR; 645 } 646 if (rh->details.purse_deposit.refunded) 647 { 648 /* We wave the deposit fee. */ 649 if (0 > 650 TALER_amount_add (pc->total_in, 651 pc->total_in, 652 &pc->dk->fees.deposit)) 653 { 654 /* overflow in refund history? inconceivable! Bad exchange! */ 655 GNUNET_break_op (0); 656 return GNUNET_SYSERR; 657 } 658 } 659 return GNUNET_YES; 660 } 661 662 663 /** 664 * Handle purse refund entry in the coin's history. 665 * 666 * @param[in,out] pc overall context 667 * @param[out] rh history entry to initialize 668 * @param amount main amount of this operation 669 * @param transaction JSON details for the operation 670 * @return #GNUNET_SYSERR on error, 671 * #GNUNET_OK to add, #GNUNET_NO to subtract 672 */ 673 static enum GNUNET_GenericReturnValue 674 help_purse_refund (struct CoinHistoryParseContext *pc, 675 struct TALER_EXCHANGE_CoinHistoryEntry *rh, 676 const struct TALER_Amount *amount, 677 json_t *transaction) 678 { 679 struct GNUNET_JSON_Specification spec[] = { 680 TALER_JSON_spec_amount_any ("refund_fee", 681 &rh->details.purse_refund.refund_fee), 682 GNUNET_JSON_spec_fixed_auto ("exchange_sig", 683 &rh->details.purse_refund.exchange_sig), 684 GNUNET_JSON_spec_fixed_auto ("exchange_pub", 685 &rh->details.purse_refund.exchange_pub), 686 GNUNET_JSON_spec_fixed_auto ("purse_pub", 687 &rh->details.purse_refund.purse_pub), 688 GNUNET_JSON_spec_end () 689 }; 690 691 if (GNUNET_OK != 692 GNUNET_JSON_parse (transaction, 693 spec, 694 NULL, NULL)) 695 { 696 GNUNET_break_op (0); 697 return GNUNET_SYSERR; 698 } 699 if (GNUNET_OK != 700 TALER_EXCHANGE_test_signing_key ( 701 pc->keys, 702 &rh->details.purse_refund.exchange_pub)) 703 { 704 GNUNET_break_op (0); 705 return GNUNET_SYSERR; 706 } 707 if (GNUNET_OK != 708 TALER_exchange_online_purse_refund_verify ( 709 amount, 710 &rh->details.purse_refund.refund_fee, 711 pc->coin_pub, 712 &rh->details.purse_refund.purse_pub, 713 &rh->details.purse_refund.exchange_pub, 714 &rh->details.purse_refund.exchange_sig)) 715 { 716 GNUNET_break_op (0); 717 return GNUNET_SYSERR; 718 } 719 if ( (GNUNET_YES != 720 TALER_amount_cmp_currency (&rh->details.purse_refund.refund_fee, 721 &pc->dk->fees.refund)) || 722 (0 != 723 TALER_amount_cmp (&rh->details.purse_refund.refund_fee, 724 &pc->dk->fees.refund)) ) 725 { 726 GNUNET_break_op (0); 727 return GNUNET_SYSERR; 728 } 729 return GNUNET_NO; 730 } 731 732 733 /** 734 * Handle reserve deposit entry in the coin's history. 735 * 736 * @param[in,out] pc overall context 737 * @param[out] rh history entry to initialize 738 * @param amount main amount of this operation 739 * @param transaction JSON details for the operation 740 * @return #GNUNET_SYSERR on error, 741 * #GNUNET_OK to add, #GNUNET_NO to subtract 742 */ 743 static enum GNUNET_GenericReturnValue 744 help_reserve_open_deposit (struct CoinHistoryParseContext *pc, 745 struct TALER_EXCHANGE_CoinHistoryEntry *rh, 746 const struct TALER_Amount *amount, 747 json_t *transaction) 748 { 749 struct GNUNET_JSON_Specification spec[] = { 750 GNUNET_JSON_spec_fixed_auto ( 751 "reserve_sig", 752 &rh->details.reserve_open_deposit.reserve_sig), 753 GNUNET_JSON_spec_fixed_auto ( 754 "coin_sig", 755 &rh->details.reserve_open_deposit.coin_sig), 756 TALER_JSON_spec_amount_any ( 757 "coin_contribution", 758 &rh->details.reserve_open_deposit.coin_contribution), 759 GNUNET_JSON_spec_mark_optional ( 760 GNUNET_JSON_spec_fixed_auto ( 761 "h_age_commitment", 762 &rh->details.reserve_open_deposit.h_age_commitment), 763 &rh->details.reserve_open_deposit.no_hac), 764 GNUNET_JSON_spec_end () 765 }; 766 767 if (GNUNET_OK != 768 GNUNET_JSON_parse (transaction, 769 spec, 770 NULL, NULL)) 771 { 772 GNUNET_break_op (0); 773 return GNUNET_SYSERR; 774 } 775 if (GNUNET_OK != 776 TALER_wallet_reserve_open_deposit_verify ( 777 amount, 778 &pc->dk->h_key, 779 rh->details.reserve_open_deposit.no_hac 780 ? NULL 781 : &rh->details.reserve_open_deposit.h_age_commitment, 782 &rh->details.reserve_open_deposit.reserve_sig, 783 pc->coin_pub, 784 &rh->details.reserve_open_deposit.coin_sig)) 785 { 786 GNUNET_break_op (0); 787 return GNUNET_SYSERR; 788 } 789 return GNUNET_YES; 790 } 791 792 793 enum GNUNET_GenericReturnValue 794 TALER_EXCHANGE_parse_coin_history ( 795 const struct TALER_EXCHANGE_Keys *keys, 796 const struct TALER_EXCHANGE_DenomPublicKey *dk, 797 const json_t *history, 798 const struct TALER_CoinSpendPublicKeyP *coin_pub, 799 struct TALER_Amount *total_in, 800 struct TALER_Amount *total_out, 801 unsigned int rlen, 802 struct TALER_EXCHANGE_CoinHistoryEntry rhistory[static rlen]) 803 { 804 const struct 805 { 806 const char *type; 807 CoinCheckHelper helper; 808 enum TALER_EXCHANGE_CoinTransactionType ctt; 809 } map[] = { 810 { "DEPOSIT", 811 &help_deposit, 812 TALER_EXCHANGE_CTT_DEPOSIT }, 813 { "MELT", 814 &help_melt, 815 TALER_EXCHANGE_CTT_MELT }, 816 { "REFUND", 817 &help_refund, 818 TALER_EXCHANGE_CTT_REFUND }, 819 { "RECOUP-WITHDRAW", 820 &help_recoup, 821 TALER_EXCHANGE_CTT_RECOUP }, 822 { "RECOUP-REFRESH", 823 &help_recoup_refresh, 824 TALER_EXCHANGE_CTT_RECOUP_REFRESH }, 825 { "RECOUP-REFRESH-RECEIVER", 826 &help_old_coin_recoup, 827 TALER_EXCHANGE_CTT_OLD_COIN_RECOUP }, 828 { "PURSE-DEPOSIT", 829 &help_purse_deposit, 830 TALER_EXCHANGE_CTT_PURSE_DEPOSIT }, 831 { "PURSE-REFUND", 832 &help_purse_refund, 833 TALER_EXCHANGE_CTT_PURSE_REFUND }, 834 { "RESERVE-OPEN-DEPOSIT", 835 &help_reserve_open_deposit, 836 TALER_EXCHANGE_CTT_RESERVE_OPEN_DEPOSIT }, 837 { NULL, NULL, TALER_EXCHANGE_CTT_NONE } 838 }; 839 struct CoinHistoryParseContext pc = { 840 .keys = keys, 841 .dk = dk, 842 .coin_pub = coin_pub, 843 .total_out = total_out, 844 .total_in = total_in 845 }; 846 size_t len; 847 848 if (NULL == history) 849 { 850 GNUNET_break_op (0); 851 return GNUNET_SYSERR; 852 } 853 len = json_array_size (history); 854 if (0 == len) 855 { 856 GNUNET_break_op (0); 857 return GNUNET_SYSERR; 858 } 859 *total_in = dk->value; 860 GNUNET_assert (GNUNET_OK == 861 TALER_amount_set_zero (total_in->currency, 862 total_out)); 863 for (size_t off = 0; off < len; off++) 864 { 865 struct TALER_EXCHANGE_CoinHistoryEntry *rh = &rhistory[off]; 866 json_t *transaction = json_array_get (history, 867 off); 868 enum GNUNET_GenericReturnValue add; 869 const char *type; 870 struct GNUNET_JSON_Specification spec_glob[] = { 871 TALER_JSON_spec_amount_any ("amount", 872 &rh->amount), 873 GNUNET_JSON_spec_string ("type", 874 &type), 875 GNUNET_JSON_spec_uint64 ("history_offset", 876 &rh->history_offset), 877 GNUNET_JSON_spec_end () 878 }; 879 880 if (GNUNET_OK != 881 GNUNET_JSON_parse (transaction, 882 spec_glob, 883 NULL, NULL)) 884 { 885 GNUNET_break_op (0); 886 return GNUNET_SYSERR; 887 } 888 if (GNUNET_YES != 889 TALER_amount_cmp_currency (&rh->amount, 890 total_in)) 891 { 892 GNUNET_break_op (0); 893 return GNUNET_SYSERR; 894 } 895 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 896 "Operation of type %s with amount %s\n", 897 type, 898 TALER_amount2s (&rh->amount)); 899 add = GNUNET_SYSERR; 900 for (unsigned int i = 0; NULL != map[i].type; i++) 901 { 902 if (0 == strcasecmp (type, 903 map[i].type)) 904 { 905 rh->type = map[i].ctt; 906 add = map[i].helper (&pc, 907 rh, 908 &rh->amount, 909 transaction); 910 break; 911 } 912 } 913 switch (add) 914 { 915 case GNUNET_SYSERR: 916 /* entry type not supported, new version on server? */ 917 rh->type = TALER_EXCHANGE_CTT_NONE; 918 GNUNET_break_op (0); 919 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 920 "Unexpected type `%s' in response\n", 921 type); 922 return GNUNET_SYSERR; 923 case GNUNET_YES: 924 /* This amount should be debited from the coin */ 925 if (0 > 926 TALER_amount_add (total_out, 927 total_out, 928 &rh->amount)) 929 { 930 /* overflow in history already!? inconceivable! Bad exchange! */ 931 GNUNET_break_op (0); 932 return GNUNET_SYSERR; 933 } 934 break; 935 case GNUNET_NO: 936 /* This amount should be credited to the coin. */ 937 if (0 > 938 TALER_amount_add (total_in, 939 total_in, 940 &rh->amount)) 941 { 942 /* overflow in refund history? inconceivable! Bad exchange! */ 943 GNUNET_break_op (0); 944 return GNUNET_SYSERR; 945 } 946 break; 947 } /* end of switch(add) */ 948 } 949 return GNUNET_OK; 950 } 951 952 953 /** 954 * We received an #MHD_HTTP_OK response. Handle the JSON response. 955 * 956 * @param gcsh handle of the request 957 * @param j JSON response 958 * @return #GNUNET_OK on success 959 */ 960 static enum GNUNET_GenericReturnValue 961 handle_coins_history_ok (struct TALER_EXCHANGE_GetCoinsHistoryHandle *gcsh, 962 const json_t *j) 963 { 964 struct TALER_EXCHANGE_GetCoinsHistoryResponse rs = { 965 .hr.reply = j, 966 .hr.http_status = MHD_HTTP_OK 967 }; 968 struct GNUNET_JSON_Specification spec[] = { 969 TALER_JSON_spec_amount_any ("balance", 970 &rs.details.ok.balance), 971 GNUNET_JSON_spec_fixed_auto ("h_denom_pub", 972 &rs.details.ok.h_denom_pub), 973 GNUNET_JSON_spec_array_const ("history", 974 &rs.details.ok.history), 975 GNUNET_JSON_spec_end () 976 }; 977 978 if (GNUNET_OK != 979 GNUNET_JSON_parse (j, 980 spec, 981 NULL, 982 NULL)) 983 { 984 GNUNET_break_op (0); 985 return GNUNET_SYSERR; 986 } 987 if (NULL != gcsh->cb) 988 { 989 gcsh->cb (gcsh->cb_cls, 990 &rs); 991 gcsh->cb = NULL; 992 } 993 GNUNET_JSON_parse_free (spec); 994 return GNUNET_OK; 995 } 996 997 998 /** 999 * Function called when we're done processing the 1000 * HTTP GET /coins/$COIN_PUB/history request. 1001 * 1002 * @param cls the `struct TALER_EXCHANGE_GetCoinsHistoryHandle` 1003 * @param response_code HTTP response code, 0 on error 1004 * @param response parsed JSON result, NULL on error 1005 */ 1006 static void 1007 handle_coins_history_finished (void *cls, 1008 long response_code, 1009 const void *response) 1010 { 1011 struct TALER_EXCHANGE_GetCoinsHistoryHandle *gcsh = cls; 1012 const json_t *j = response; 1013 struct TALER_EXCHANGE_GetCoinsHistoryResponse rs = { 1014 .hr.reply = j, 1015 .hr.http_status = (unsigned int) response_code 1016 }; 1017 1018 gcsh->job = NULL; 1019 switch (response_code) 1020 { 1021 case 0: 1022 rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 1023 break; 1024 case MHD_HTTP_OK: 1025 if (GNUNET_OK != 1026 handle_coins_history_ok (gcsh, 1027 j)) 1028 { 1029 rs.hr.http_status = 0; 1030 rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 1031 } 1032 break; 1033 case MHD_HTTP_NO_CONTENT: 1034 break; 1035 case MHD_HTTP_NOT_MODIFIED: 1036 break; 1037 case MHD_HTTP_BAD_REQUEST: 1038 /* This should never happen, either us or the exchange is buggy 1039 (or API version conflict); just pass JSON reply to the application */ 1040 GNUNET_break (0); 1041 rs.hr.ec = TALER_JSON_get_error_code (j); 1042 rs.hr.hint = TALER_JSON_get_error_hint (j); 1043 break; 1044 case MHD_HTTP_FORBIDDEN: 1045 /* This should never happen, either us or the exchange is buggy 1046 (or API version conflict); just pass JSON reply to the application */ 1047 GNUNET_break (0); 1048 rs.hr.ec = TALER_JSON_get_error_code (j); 1049 rs.hr.hint = TALER_JSON_get_error_hint (j); 1050 break; 1051 case MHD_HTTP_NOT_FOUND: 1052 rs.hr.ec = TALER_JSON_get_error_code (j); 1053 rs.hr.hint = TALER_JSON_get_error_hint (j); 1054 break; 1055 case MHD_HTTP_INTERNAL_SERVER_ERROR: 1056 /* Server had an internal issue; we should retry, but this API 1057 leaves this to the application */ 1058 rs.hr.ec = TALER_JSON_get_error_code (j); 1059 rs.hr.hint = TALER_JSON_get_error_hint (j); 1060 break; 1061 default: 1062 /* unexpected response code */ 1063 GNUNET_break_op (0); 1064 rs.hr.ec = TALER_JSON_get_error_code (j); 1065 rs.hr.hint = TALER_JSON_get_error_hint (j); 1066 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1067 "Unexpected response code %u/%d for coins history\n", 1068 (unsigned int) response_code, 1069 (int) rs.hr.ec); 1070 break; 1071 } 1072 if (NULL != gcsh->cb) 1073 { 1074 gcsh->cb (gcsh->cb_cls, 1075 &rs); 1076 gcsh->cb = NULL; 1077 } 1078 TALER_EXCHANGE_get_coins_history_cancel (gcsh); 1079 } 1080 1081 1082 struct TALER_EXCHANGE_GetCoinsHistoryHandle * 1083 TALER_EXCHANGE_get_coins_history_create ( 1084 struct GNUNET_CURL_Context *ctx, 1085 const char *url, 1086 const struct TALER_CoinSpendPrivateKeyP *coin_priv) 1087 { 1088 struct TALER_EXCHANGE_GetCoinsHistoryHandle *gcsh; 1089 1090 gcsh = GNUNET_new (struct TALER_EXCHANGE_GetCoinsHistoryHandle); 1091 gcsh->ctx = ctx; 1092 gcsh->base_url = GNUNET_strdup (url); 1093 gcsh->coin_priv = *coin_priv; 1094 GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, 1095 &gcsh->coin_pub.eddsa_pub); 1096 gcsh->options.start_off = 0; 1097 return gcsh; 1098 } 1099 1100 1101 enum GNUNET_GenericReturnValue 1102 TALER_EXCHANGE_get_coins_history_set_options_ ( 1103 struct TALER_EXCHANGE_GetCoinsHistoryHandle *gcsh, 1104 unsigned int num_options, 1105 const struct TALER_EXCHANGE_GetCoinsHistoryOptionValue *options) 1106 { 1107 for (unsigned int i = 0; i < num_options; i++) 1108 { 1109 const struct TALER_EXCHANGE_GetCoinsHistoryOptionValue *opt = &options[i]; 1110 1111 switch (opt->option) 1112 { 1113 case TALER_EXCHANGE_GET_COINS_HISTORY_OPTION_END: 1114 return GNUNET_OK; 1115 case TALER_EXCHANGE_GET_COINS_HISTORY_OPTION_START_OFF: 1116 gcsh->options.start_off = opt->details.start_off; 1117 break; 1118 } 1119 } 1120 return GNUNET_OK; 1121 } 1122 1123 1124 enum TALER_ErrorCode 1125 TALER_EXCHANGE_get_coins_history_start ( 1126 struct TALER_EXCHANGE_GetCoinsHistoryHandle *gcsh, 1127 TALER_EXCHANGE_GetCoinsHistoryCallback cb, 1128 TALER_EXCHANGE_GET_COINS_HISTORY_RESULT_CLOSURE *cb_cls) 1129 { 1130 char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 64]; 1131 struct curl_slist *job_headers; 1132 CURL *eh; 1133 1134 if (NULL != gcsh->job) 1135 { 1136 GNUNET_break (0); 1137 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 1138 } 1139 gcsh->cb = cb; 1140 gcsh->cb_cls = cb_cls; 1141 { 1142 char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2]; 1143 char *end; 1144 1145 end = GNUNET_STRINGS_data_to_string ( 1146 &gcsh->coin_pub, 1147 sizeof (gcsh->coin_pub), 1148 pub_str, 1149 sizeof (pub_str)); 1150 *end = '\0'; 1151 if (0 != gcsh->options.start_off) 1152 GNUNET_snprintf (arg_str, 1153 sizeof (arg_str), 1154 "coins/%s/history?start=%llu", 1155 pub_str, 1156 (unsigned long long) gcsh->options.start_off); 1157 else 1158 GNUNET_snprintf (arg_str, 1159 sizeof (arg_str), 1160 "coins/%s/history", 1161 pub_str); 1162 } 1163 gcsh->url = TALER_url_join (gcsh->base_url, 1164 arg_str, 1165 NULL); 1166 if (NULL == gcsh->url) 1167 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 1168 eh = TALER_EXCHANGE_curl_easy_get_ (gcsh->url); 1169 if (NULL == eh) 1170 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 1171 { 1172 struct TALER_CoinSpendSignatureP coin_sig; 1173 char *sig_hdr; 1174 char *hdr; 1175 1176 TALER_wallet_coin_history_sign (gcsh->options.start_off, 1177 &gcsh->coin_priv, 1178 &coin_sig); 1179 sig_hdr = GNUNET_STRINGS_data_to_string_alloc ( 1180 &coin_sig, 1181 sizeof (coin_sig)); 1182 GNUNET_asprintf (&hdr, 1183 "%s: %s", 1184 TALER_COIN_HISTORY_SIGNATURE_HEADER, 1185 sig_hdr); 1186 GNUNET_free (sig_hdr); 1187 job_headers = curl_slist_append (NULL, 1188 hdr); 1189 GNUNET_free (hdr); 1190 if (NULL == job_headers) 1191 { 1192 GNUNET_break (0); 1193 curl_easy_cleanup (eh); 1194 GNUNET_free (gcsh->url); 1195 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 1196 } 1197 } 1198 gcsh->job = GNUNET_CURL_job_add2 (gcsh->ctx, 1199 eh, 1200 job_headers, 1201 &handle_coins_history_finished, 1202 gcsh); 1203 curl_slist_free_all (job_headers); 1204 if (NULL == gcsh->job) 1205 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 1206 return TALER_EC_NONE; 1207 } 1208 1209 1210 void 1211 TALER_EXCHANGE_get_coins_history_cancel ( 1212 struct TALER_EXCHANGE_GetCoinsHistoryHandle *gcsh) 1213 { 1214 if (NULL != gcsh->job) 1215 { 1216 GNUNET_CURL_job_cancel (gcsh->job); 1217 gcsh->job = NULL; 1218 } 1219 GNUNET_free (gcsh->url); 1220 GNUNET_free (gcsh->base_url); 1221 GNUNET_free (gcsh); 1222 } 1223 1224 1225 enum GNUNET_GenericReturnValue 1226 TALER_EXCHANGE_check_coin_signature_conflict ( 1227 const json_t *history, 1228 const struct TALER_CoinSpendSignatureP *coin_sig) 1229 { 1230 size_t off; 1231 json_t *entry; 1232 1233 json_array_foreach (history, off, entry) 1234 { 1235 struct TALER_CoinSpendSignatureP cs; 1236 struct GNUNET_JSON_Specification spec[] = { 1237 GNUNET_JSON_spec_fixed_auto ("coin_sig", 1238 &cs), 1239 GNUNET_JSON_spec_end () 1240 }; 1241 1242 if (GNUNET_OK != 1243 GNUNET_JSON_parse (entry, 1244 spec, 1245 NULL, NULL)) 1246 continue; /* entry without coin signature */ 1247 if (0 == 1248 GNUNET_memcmp (&cs, 1249 coin_sig)) 1250 { 1251 GNUNET_break_op (0); 1252 return GNUNET_SYSERR; 1253 } 1254 } 1255 return GNUNET_OK; 1256 } 1257 1258 1259 #if FIXME_IMPLEMENT /* #9422 */ 1260 /** 1261 * FIXME-Oec-#9422: we need some specific routines that show 1262 * that certain coin operations are indeed in conflict, 1263 * for example that the coin is of a different denomination 1264 * or different age restrictions. 1265 * This relates to unimplemented error handling for 1266 * coins in the exchange! 1267 * 1268 * Check that the provided @a proof indeeds indicates 1269 * a conflict for @a coin_pub. 1270 * 1271 * @param keys exchange keys 1272 * @param proof provided conflict proof 1273 * @param dk denomination of @a coin_pub that the client 1274 * used 1275 * @param coin_pub public key of the coin 1276 * @param required balance required on the coin for the operation 1277 * @return #GNUNET_OK if @a proof holds 1278 */ 1279 // FIXME-#9422: should be properly defined and implemented! 1280 enum GNUNET_GenericReturnValue 1281 TALER_EXCHANGE_check_coin_conflict_ ( 1282 const struct TALER_EXCHANGE_Keys *keys, 1283 const json_t *proof, 1284 const struct TALER_EXCHANGE_DenomPublicKey *dk, 1285 const struct TALER_CoinSpendPublicKeyP *coin_pub, 1286 const struct TALER_Amount *required) 1287 { 1288 enum TALER_ErrorCode ec; 1289 1290 ec = TALER_JSON_get_error_code (proof); 1291 switch (ec) 1292 { 1293 case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS: 1294 /* Nothing to check anymore here, proof needs to be 1295 checked in the GET /coins/$COIN_PUB handler */ 1296 break; 1297 case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY: 1298 // FIXME-#9422: write check! 1299 break; 1300 default: 1301 GNUNET_break_op (0); 1302 return GNUNET_SYSERR; 1303 } 1304 return GNUNET_OK; 1305 } 1306 1307 1308 #endif 1309 1310 1311 /* end of exchange_api_get-coins-COIN_PUB-history.c */