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