Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2022-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_post-purses-PURSE_PUB-create.c
19 : * @brief Implementation of the client to create a purse with
20 : * an initial set of deposits (and a contract)
21 : * @author Christian Grothoff
22 : */
23 : #include <jansson.h>
24 : #include <microhttpd.h> /* just for HTTP status codes */
25 : #include <gnunet/gnunet_util_lib.h>
26 : #include <gnunet/gnunet_json_lib.h>
27 : #include <gnunet/gnunet_curl_lib.h>
28 : #include "taler/taler_json_lib.h"
29 : #include "exchange_api_handle.h"
30 : #include "exchange_api_common.h"
31 : #include "taler/taler_signatures.h"
32 : #include "exchange_api_curl_defaults.h"
33 :
34 :
35 : /**
36 : * Information we track per deposited coin.
37 : */
38 : struct Deposit
39 : {
40 : /**
41 : * Coin's public key.
42 : */
43 : struct TALER_CoinSpendPublicKeyP coin_pub;
44 :
45 : /**
46 : * Signature made with the coin.
47 : */
48 : struct TALER_CoinSpendSignatureP coin_sig;
49 :
50 : /**
51 : * Coin's denomination.
52 : */
53 : struct TALER_DenominationHashP h_denom_pub;
54 :
55 : /**
56 : * Signature proving the validity of the coin.
57 : */
58 : struct TALER_DenominationSignature denom_sig;
59 :
60 : /**
61 : * Age restriction hash for the coin.
62 : */
63 : struct TALER_AgeCommitmentHashP ahac;
64 :
65 : /**
66 : * How much did we say the coin contributed.
67 : */
68 : struct TALER_Amount contribution;
69 :
70 : /**
71 : * Age attestation for this coin. Valid if @e have_age.
72 : */
73 : struct TALER_AgeAttestationP attest;
74 :
75 : /**
76 : * True if this coin uses age attestation.
77 : */
78 : bool have_age;
79 :
80 : };
81 :
82 :
83 : /**
84 : * @brief A purse create with deposit handle
85 : */
86 : struct TALER_EXCHANGE_PostPursesCreateHandle
87 : {
88 :
89 : /**
90 : * The curl context for this request.
91 : */
92 : struct GNUNET_CURL_Context *ctx;
93 :
94 : /**
95 : * The base URL of the exchange.
96 : */
97 : char *base_url;
98 :
99 : /**
100 : * The keys of the exchange this request handle will use
101 : */
102 : struct TALER_EXCHANGE_Keys *keys;
103 :
104 : /**
105 : * The url for this request, set during _start.
106 : */
107 : char *url;
108 :
109 : /**
110 : * Context for #TEH_curl_easy_post(). Keeps the data that must
111 : * persist for Curl to make the upload.
112 : */
113 : struct TALER_CURL_PostContext post_ctx;
114 :
115 : /**
116 : * Handle for the request.
117 : */
118 : struct GNUNET_CURL_Job *job;
119 :
120 : /**
121 : * Function to call with the result.
122 : */
123 : TALER_EXCHANGE_PostPursesCreateCallback cb;
124 :
125 : /**
126 : * Closure for @a cb.
127 : */
128 : TALER_EXCHANGE_POST_PURSES_CREATE_RESULT_CLOSURE *cb_cls;
129 :
130 : /**
131 : * Expected value in the purse after fees.
132 : */
133 : struct TALER_Amount purse_value_after_fees;
134 :
135 : /**
136 : * Our encrypted contract (if we had any).
137 : */
138 : struct TALER_EncryptedContract econtract;
139 :
140 : /**
141 : * Public key of the merge capability.
142 : */
143 : struct TALER_PurseMergePublicKeyP merge_pub;
144 :
145 : /**
146 : * Private key of the purse which we are creating.
147 : */
148 : struct TALER_PurseContractPrivateKeyP purse_priv;
149 :
150 : /**
151 : * Private key for the merge operation.
152 : */
153 : struct TALER_PurseMergePrivateKeyP merge_priv;
154 :
155 : /**
156 : * Private key to decrypt the contract.
157 : */
158 : struct TALER_ContractDiffiePrivateP contract_priv;
159 :
160 : /**
161 : * Contract terms for the payment.
162 : */
163 : json_t *contract_terms;
164 :
165 : /**
166 : * Minimum age, as per @e contract_terms.
167 : */
168 : uint32_t min_age;
169 :
170 : /**
171 : * Public key of the purse.
172 : */
173 : struct TALER_PurseContractPublicKeyP purse_pub;
174 :
175 : /**
176 : * Signature for purse creation.
177 : */
178 : struct TALER_PurseContractSignatureP purse_sig;
179 :
180 : /**
181 : * Hash over the purse's contract terms.
182 : */
183 : struct TALER_PrivateContractHashP h_contract_terms;
184 :
185 : /**
186 : * When does the purse expire.
187 : */
188 : struct GNUNET_TIME_Timestamp purse_expiration;
189 :
190 : /**
191 : * Array of @e num_deposit deposits.
192 : */
193 : struct Deposit *deposits;
194 :
195 : /**
196 : * How many deposits did we make?
197 : */
198 : unsigned int num_deposits;
199 :
200 : struct
201 : {
202 :
203 : /**
204 : * Whether we are uploading a contract.
205 : */
206 : bool upload_contract;
207 :
208 : } options;
209 :
210 : };
211 :
212 :
213 : /**
214 : * Find the deposited coin @a coin_pub and how we used it.
215 : *
216 : * @param cls a `struct TALER_EXCHANGE_PostPursesCreateHandle *`
217 : * @param coin_pub public key of the coin named in the conflict reply
218 : * @param[out] h_denom_pub set to the hash of the denomination we used
219 : * @param[out] h_age_commitment set to the age commitment hash we used, or NULL
220 : * @return #GNUNET_OK if found, #GNUNET_NO if the coin is not ours
221 : */
222 : static enum GNUNET_GenericReturnValue
223 0 : purse_create_coin_lookup (void *cls,
224 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
225 : const struct TALER_DenominationHashP **h_denom_pub,
226 : const struct TALER_AgeCommitmentHashP **
227 : h_age_commitment)
228 : {
229 0 : struct TALER_EXCHANGE_PostPursesCreateHandle *pch = cls;
230 :
231 0 : for (unsigned int i = 0; i<pch->num_deposits; i++)
232 : {
233 0 : const struct Deposit *deposit = &pch->deposits[i];
234 :
235 0 : if (0 != GNUNET_memcmp (coin_pub,
236 : &deposit->coin_pub))
237 0 : continue;
238 0 : *h_denom_pub = &deposit->h_denom_pub;
239 0 : *h_age_commitment = deposit->have_age
240 : ? &deposit->ahac
241 0 : : NULL;
242 0 : return GNUNET_OK;
243 : }
244 0 : return GNUNET_NO;
245 : }
246 :
247 :
248 : /**
249 : * Function called when we're done processing the
250 : * HTTP /purses/$PID/create request.
251 : *
252 : * @param cls the `struct TALER_EXCHANGE_PostPursesCreateHandle`
253 : * @param response_code HTTP response code, 0 on error
254 : * @param response parsed JSON result, NULL on error
255 : */
256 : static void
257 13 : handle_purse_create_deposit_finished (void *cls,
258 : long response_code,
259 : const void *response)
260 : {
261 13 : struct TALER_EXCHANGE_PostPursesCreateHandle *pch = cls;
262 13 : const json_t *j = response;
263 13 : struct TALER_EXCHANGE_PostPursesCreateResponse dr = {
264 : .hr.reply = j,
265 13 : .hr.http_status = (unsigned int) response_code
266 : };
267 13 : const struct TALER_EXCHANGE_Keys *keys = pch->keys;
268 :
269 13 : pch->job = NULL;
270 13 : switch (response_code)
271 : {
272 0 : case 0:
273 0 : dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
274 0 : break;
275 9 : case MHD_HTTP_OK:
276 : {
277 : struct GNUNET_JSON_Specification spec[] = {
278 9 : GNUNET_JSON_spec_fixed_auto ("exchange_sig",
279 : &dr.details.ok.exchange_sig),
280 9 : GNUNET_JSON_spec_fixed_auto ("exchange_pub",
281 : &dr.details.ok.exchange_pub),
282 9 : GNUNET_JSON_spec_timestamp ("exchange_timestamp",
283 : &dr.details.ok.exchange_timestamp),
284 9 : TALER_JSON_spec_amount ("total_deposited",
285 9 : pch->purse_value_after_fees.currency,
286 : &dr.details.ok.total_deposited),
287 9 : GNUNET_JSON_spec_end ()
288 : };
289 :
290 9 : if (GNUNET_OK !=
291 9 : GNUNET_JSON_parse (j,
292 : spec,
293 : NULL, NULL))
294 : {
295 0 : GNUNET_break_op (0);
296 0 : dr.hr.http_status = 0;
297 0 : dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
298 0 : break;
299 : }
300 9 : if (GNUNET_OK !=
301 9 : TALER_EXCHANGE_test_signing_key (keys,
302 : &dr.details.ok.exchange_pub))
303 : {
304 0 : GNUNET_break_op (0);
305 0 : dr.hr.http_status = 0;
306 0 : dr.hr.ec = TALER_EC_EXCHANGE_PURSE_CREATE_EXCHANGE_SIGNATURE_INVALID;
307 0 : break;
308 : }
309 9 : if (GNUNET_OK !=
310 9 : TALER_exchange_online_purse_created_verify (
311 : dr.details.ok.exchange_timestamp,
312 : pch->purse_expiration,
313 9 : &pch->purse_value_after_fees,
314 : &dr.details.ok.total_deposited,
315 9 : &pch->purse_pub,
316 9 : &pch->h_contract_terms,
317 : &dr.details.ok.exchange_pub,
318 : &dr.details.ok.exchange_sig))
319 : {
320 0 : GNUNET_break_op (0);
321 0 : dr.hr.http_status = 0;
322 0 : dr.hr.ec = TALER_EC_EXCHANGE_PURSE_CREATE_EXCHANGE_SIGNATURE_INVALID;
323 0 : break;
324 : }
325 : }
326 9 : break;
327 2 : case MHD_HTTP_BAD_REQUEST:
328 : /* This should never happen, either us or the exchange is buggy
329 : (or API version conflict); just pass JSON reply to the application */
330 2 : dr.hr.ec = TALER_JSON_get_error_code (j);
331 2 : dr.hr.hint = TALER_JSON_get_error_hint (j);
332 2 : break;
333 0 : case MHD_HTTP_FORBIDDEN:
334 0 : dr.hr.ec = TALER_JSON_get_error_code (j);
335 0 : dr.hr.hint = TALER_JSON_get_error_hint (j);
336 : /* Nothing really to verify, exchange says one of the signatures is
337 : invalid; as we checked them, this should never happen, we
338 : should pass the JSON reply to the application */
339 0 : break;
340 0 : case MHD_HTTP_NOT_FOUND:
341 0 : dr.hr.ec = TALER_JSON_get_error_code (j);
342 0 : dr.hr.hint = TALER_JSON_get_error_hint (j);
343 : /* Nothing really to verify, this should never
344 : happen, we should pass the JSON reply to the application */
345 0 : break;
346 2 : case MHD_HTTP_CONFLICT:
347 : {
348 2 : dr.hr.ec = TALER_JSON_get_error_code (j);
349 2 : switch (dr.hr.ec)
350 : {
351 0 : case TALER_EC_EXCHANGE_PURSE_CREATE_CONFLICTING_META_DATA:
352 0 : if (GNUNET_OK !=
353 0 : TALER_EXCHANGE_check_purse_create_conflict_ (
354 0 : &pch->purse_sig,
355 0 : &pch->purse_pub,
356 : j))
357 : {
358 0 : GNUNET_break_op (0);
359 0 : dr.hr.http_status = 0;
360 0 : dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
361 0 : break;
362 : }
363 0 : break;
364 2 : case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
365 : /* Nothing to check anymore here, proof needs to be
366 : checked in the GET /coins/$COIN_PUB handler */
367 2 : break;
368 0 : case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY:
369 : case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_AGE_HASH:
370 0 : if (GNUNET_OK !=
371 0 : TALER_EXCHANGE_check_coin_conflict_ (pch->keys,
372 : dr.hr.ec,
373 : j,
374 : &purse_create_coin_lookup,
375 : pch,
376 : &dr.details.conflict))
377 : {
378 0 : GNUNET_break_op (0);
379 0 : dr.hr.http_status = 0;
380 0 : dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
381 : }
382 0 : break;
383 0 : case TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA:
384 : {
385 : struct TALER_CoinSpendPublicKeyP coin_pub;
386 : struct TALER_CoinSpendSignatureP coin_sig;
387 : struct TALER_DenominationHashP h_denom_pub;
388 : struct TALER_AgeCommitmentHashP phac;
389 0 : bool found = false;
390 :
391 0 : if (GNUNET_OK !=
392 0 : TALER_EXCHANGE_check_purse_coin_conflict_ (
393 0 : &pch->purse_pub,
394 0 : pch->base_url,
395 : j,
396 : &h_denom_pub,
397 : &phac,
398 : &coin_pub,
399 : &coin_sig))
400 : {
401 0 : GNUNET_break_op (0);
402 0 : dr.hr.http_status = 0;
403 0 : dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
404 0 : break;
405 : }
406 0 : for (unsigned int i = 0; i<pch->num_deposits; i++)
407 : {
408 0 : struct Deposit *deposit = &pch->deposits[i];
409 :
410 0 : if (0 !=
411 0 : GNUNET_memcmp (&coin_pub,
412 : &deposit->coin_pub))
413 0 : continue;
414 0 : if (0 !=
415 0 : GNUNET_memcmp (&deposit->h_denom_pub,
416 : &h_denom_pub))
417 : {
418 0 : found = true;
419 0 : break;
420 : }
421 0 : if (0 !=
422 0 : GNUNET_memcmp (&deposit->ahac,
423 : &phac))
424 : {
425 0 : found = true;
426 0 : break;
427 : }
428 0 : if (0 ==
429 0 : GNUNET_memcmp (&coin_sig,
430 : &deposit->coin_sig))
431 : {
432 0 : GNUNET_break_op (0);
433 0 : continue;
434 : }
435 0 : found = true;
436 0 : break;
437 : }
438 0 : if (! found)
439 : {
440 : /* conflict is for a different coin! */
441 0 : GNUNET_break_op (0);
442 0 : dr.hr.http_status = 0;
443 0 : dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
444 0 : break;
445 : }
446 : }
447 0 : break;
448 0 : case TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA:
449 0 : if (GNUNET_OK !=
450 0 : TALER_EXCHANGE_check_purse_econtract_conflict_ (
451 0 : &pch->econtract.econtract_sig,
452 0 : &pch->purse_pub,
453 : j))
454 : {
455 0 : GNUNET_break_op (0);
456 0 : dr.hr.http_status = 0;
457 0 : dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
458 0 : break;
459 : }
460 0 : break;
461 0 : default:
462 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
463 : "Unexpected error code %d for conflicting deposit\n",
464 : dr.hr.ec);
465 0 : GNUNET_break_op (0);
466 0 : dr.hr.http_status = 0;
467 0 : dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
468 : } /* switch on (ec) */
469 : }
470 2 : break;
471 0 : case MHD_HTTP_GONE:
472 : /* could happen if denomination was revoked */
473 : /* Note: one might want to check /keys for revocation
474 : signature here, alas tricky in case our /keys
475 : is outdated => left to clients */
476 0 : dr.hr.ec = TALER_JSON_get_error_code (j);
477 0 : dr.hr.hint = TALER_JSON_get_error_hint (j);
478 0 : break;
479 0 : case MHD_HTTP_PRECONDITION_FAILED:
480 : /* could happen if we were too early and the denomination
481 : is not yet valid */
482 0 : dr.hr.ec = TALER_JSON_get_error_code (j);
483 0 : dr.hr.hint = TALER_JSON_get_error_hint (j);
484 0 : break;
485 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
486 0 : dr.hr.ec = TALER_JSON_get_error_code (j);
487 0 : dr.hr.hint = TALER_JSON_get_error_hint (j);
488 : /* Server had an internal issue; we should retry, but this API
489 : leaves this to the application */
490 0 : break;
491 0 : default:
492 : /* unexpected response code */
493 0 : dr.hr.ec = TALER_JSON_get_error_code (j);
494 0 : dr.hr.hint = TALER_JSON_get_error_hint (j);
495 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
496 : "Unexpected response code %u/%d for exchange deposit\n",
497 : (unsigned int) response_code,
498 : dr.hr.ec);
499 0 : GNUNET_break_op (0);
500 0 : break;
501 : }
502 13 : if (NULL != pch->cb)
503 : {
504 13 : pch->cb (pch->cb_cls,
505 : &dr);
506 13 : pch->cb = NULL;
507 : }
508 13 : TALER_EXCHANGE_free_coin_conflict_ (&dr.hr,
509 : &dr.details.conflict);
510 13 : TALER_EXCHANGE_post_purses_create_cancel (pch);
511 13 : }
512 :
513 :
514 : struct TALER_EXCHANGE_PostPursesCreateHandle *
515 13 : TALER_EXCHANGE_post_purses_create_create (
516 : struct GNUNET_CURL_Context *ctx,
517 : const char *url,
518 : struct TALER_EXCHANGE_Keys *keys,
519 : const struct TALER_PurseContractPrivateKeyP *purse_priv,
520 : const struct TALER_PurseMergePrivateKeyP *merge_priv,
521 : const struct TALER_ContractDiffiePrivateP *contract_priv,
522 : const json_t *contract_terms,
523 : unsigned int num_deposits,
524 : const struct TALER_EXCHANGE_PurseDeposit deposits[static num_deposits])
525 13 : {
526 : struct TALER_EXCHANGE_PostPursesCreateHandle *pch;
527 :
528 13 : pch = GNUNET_new (struct TALER_EXCHANGE_PostPursesCreateHandle);
529 13 : pch->ctx = ctx;
530 13 : pch->base_url = GNUNET_strdup (url);
531 13 : pch->purse_priv = *purse_priv;
532 13 : pch->merge_priv = *merge_priv;
533 13 : pch->contract_priv = *contract_priv;
534 13 : pch->contract_terms = json_incref ((json_t *) contract_terms);
535 : {
536 : struct GNUNET_JSON_Specification spec[] = {
537 13 : GNUNET_JSON_spec_timestamp ("pay_deadline",
538 : &pch->purse_expiration),
539 13 : TALER_JSON_spec_amount_any ("amount",
540 : &pch->purse_value_after_fees),
541 13 : GNUNET_JSON_spec_mark_optional (
542 : GNUNET_JSON_spec_uint32 ("minimum_age",
543 : &pch->min_age),
544 : NULL),
545 13 : GNUNET_JSON_spec_end ()
546 : };
547 :
548 13 : if (GNUNET_OK !=
549 13 : GNUNET_JSON_parse (contract_terms,
550 : spec,
551 : NULL, NULL))
552 : {
553 0 : GNUNET_break (0);
554 0 : json_decref (pch->contract_terms);
555 0 : GNUNET_free (pch->base_url);
556 0 : GNUNET_free (pch);
557 0 : return NULL;
558 : }
559 : }
560 13 : if (GNUNET_OK !=
561 13 : TALER_JSON_contract_hash (contract_terms,
562 : &pch->h_contract_terms))
563 : {
564 0 : GNUNET_break (0);
565 0 : json_decref (pch->contract_terms);
566 0 : GNUNET_free (pch->base_url);
567 0 : GNUNET_free (pch);
568 0 : return NULL;
569 : }
570 13 : GNUNET_CRYPTO_eddsa_key_get_public (&purse_priv->eddsa_priv,
571 : &pch->purse_pub.eddsa_pub);
572 13 : GNUNET_CRYPTO_eddsa_key_get_public (&merge_priv->eddsa_priv,
573 : &pch->merge_pub.eddsa_pub);
574 13 : pch->num_deposits = num_deposits;
575 13 : pch->deposits = GNUNET_new_array (num_deposits,
576 : struct Deposit);
577 13 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
578 : "Signing with URL `%s'\n",
579 : url);
580 26 : for (unsigned int i = 0; i<num_deposits; i++)
581 : {
582 13 : const struct TALER_EXCHANGE_PurseDeposit *deposit = &deposits[i];
583 13 : const struct TALER_AgeCommitmentProof *acp = deposit->age_commitment_proof;
584 13 : struct Deposit *d = &pch->deposits[i];
585 :
586 13 : if (NULL != acp)
587 : {
588 0 : TALER_age_commitment_hash (&acp->commitment,
589 : &d->ahac);
590 0 : d->have_age = true;
591 0 : if (GNUNET_OK !=
592 0 : TALER_age_commitment_attest (acp,
593 0 : pch->min_age,
594 : &d->attest))
595 : {
596 0 : GNUNET_break (0);
597 0 : for (unsigned int j = 0; j<i; j++)
598 0 : TALER_denom_sig_free (&pch->deposits[j].denom_sig);
599 0 : GNUNET_array_grow (pch->deposits,
600 : pch->num_deposits,
601 : 0);
602 0 : json_decref (pch->contract_terms);
603 0 : GNUNET_free (pch->base_url);
604 0 : GNUNET_free (pch);
605 0 : return NULL;
606 : }
607 : }
608 13 : d->contribution = deposit->amount;
609 13 : d->h_denom_pub = deposit->h_denom_pub;
610 13 : TALER_denom_sig_copy (&d->denom_sig,
611 : &deposit->denom_sig);
612 13 : GNUNET_CRYPTO_eddsa_key_get_public (&deposit->coin_priv.eddsa_priv,
613 : &d->coin_pub.eddsa_pub);
614 13 : TALER_wallet_purse_deposit_sign (
615 : url,
616 13 : &pch->purse_pub,
617 : &deposit->amount,
618 13 : &d->h_denom_pub,
619 13 : &d->ahac,
620 : &deposit->coin_priv,
621 : &d->coin_sig);
622 : }
623 13 : pch->keys = TALER_EXCHANGE_keys_incref (keys);
624 13 : return pch;
625 : }
626 :
627 :
628 : enum GNUNET_GenericReturnValue
629 13 : TALER_EXCHANGE_post_purses_create_set_options_ (
630 : struct TALER_EXCHANGE_PostPursesCreateHandle *ppch,
631 : unsigned int num_options,
632 : const struct TALER_EXCHANGE_PostPursesCreateOptionValue options[])
633 : {
634 26 : for (unsigned int i = 0; i < num_options; i++)
635 : {
636 26 : const struct TALER_EXCHANGE_PostPursesCreateOptionValue *opt = &options[i];
637 :
638 26 : switch (opt->option)
639 : {
640 13 : case TALER_EXCHANGE_POST_PURSES_CREATE_OPTION_END:
641 13 : return GNUNET_OK;
642 13 : case TALER_EXCHANGE_POST_PURSES_CREATE_OPTION_UPLOAD_CONTRACT:
643 13 : ppch->options.upload_contract = true;
644 13 : break;
645 : }
646 : }
647 0 : return GNUNET_OK;
648 : }
649 :
650 :
651 : enum TALER_ErrorCode
652 13 : TALER_EXCHANGE_post_purses_create_start (
653 : struct TALER_EXCHANGE_PostPursesCreateHandle *pch,
654 : TALER_EXCHANGE_PostPursesCreateCallback cb,
655 : TALER_EXCHANGE_POST_PURSES_CREATE_RESULT_CLOSURE *cb_cls)
656 : {
657 : char arg_str[sizeof (pch->purse_pub) * 2 + 32];
658 : CURL *eh;
659 : json_t *deposit_arr;
660 : json_t *body;
661 :
662 13 : pch->cb = cb;
663 13 : pch->cb_cls = cb_cls;
664 13 : deposit_arr = json_array ();
665 13 : GNUNET_assert (NULL != deposit_arr);
666 :
667 26 : for (unsigned int i = 0; i<pch->num_deposits; i++)
668 : {
669 13 : struct Deposit *d = &pch->deposits[i];
670 13 : struct TALER_AgeCommitmentHashP *aghp = NULL;
671 13 : struct TALER_AgeAttestationP *attestp = NULL;
672 : json_t *jdeposit;
673 :
674 13 : if (d->have_age)
675 : {
676 0 : aghp = &d->ahac;
677 0 : attestp = &d->attest;
678 : }
679 13 : jdeposit = GNUNET_JSON_PACK (
680 : GNUNET_JSON_pack_allow_null (
681 : GNUNET_JSON_pack_data_auto ("h_age_commitment",
682 : aghp)),
683 : GNUNET_JSON_pack_allow_null (
684 : GNUNET_JSON_pack_data_auto ("age_attestation",
685 : attestp)),
686 : TALER_JSON_pack_amount ("amount",
687 : &d->contribution),
688 : GNUNET_JSON_pack_data_auto ("denom_pub_hash",
689 : &d->h_denom_pub),
690 : TALER_JSON_pack_denom_sig ("ub_sig",
691 : &d->denom_sig),
692 : GNUNET_JSON_pack_data_auto ("coin_sig",
693 : &d->coin_sig),
694 : GNUNET_JSON_pack_data_auto ("coin_pub",
695 : &d->coin_pub));
696 13 : GNUNET_assert (0 ==
697 : json_array_append_new (deposit_arr,
698 : jdeposit));
699 :
700 : }
701 :
702 13 : if (pch->options.upload_contract)
703 : {
704 13 : TALER_CRYPTO_contract_encrypt_for_merge (
705 13 : &pch->purse_pub,
706 13 : &pch->contract_priv,
707 13 : &pch->merge_priv,
708 13 : pch->contract_terms,
709 : &pch->econtract.econtract,
710 : &pch->econtract.econtract_size);
711 13 : GNUNET_CRYPTO_ecdhe_key_get_public (
712 13 : &pch->contract_priv.ecdhe_priv,
713 : &pch->econtract.contract_pub.ecdhe_pub);
714 13 : TALER_wallet_econtract_upload_sign (
715 13 : pch->econtract.econtract,
716 : pch->econtract.econtract_size,
717 13 : &pch->econtract.contract_pub,
718 13 : &pch->purse_priv,
719 : &pch->econtract.econtract_sig);
720 : }
721 13 : TALER_wallet_purse_create_sign (pch->purse_expiration,
722 13 : &pch->h_contract_terms,
723 13 : &pch->merge_pub,
724 : pch->min_age,
725 13 : &pch->purse_value_after_fees,
726 13 : &pch->purse_priv,
727 : &pch->purse_sig);
728 :
729 13 : body = GNUNET_JSON_PACK (
730 : TALER_JSON_pack_amount ("amount",
731 : &pch->purse_value_after_fees),
732 : GNUNET_JSON_pack_uint64 ("min_age",
733 : pch->min_age),
734 : GNUNET_JSON_pack_allow_null (
735 : TALER_JSON_pack_econtract ("econtract",
736 : pch->options.upload_contract
737 : ? &pch->econtract
738 : : NULL)),
739 : GNUNET_JSON_pack_data_auto ("purse_sig",
740 : &pch->purse_sig),
741 : GNUNET_JSON_pack_data_auto ("merge_pub",
742 : &pch->merge_pub),
743 : GNUNET_JSON_pack_data_auto ("h_contract_terms",
744 : &pch->h_contract_terms),
745 : GNUNET_JSON_pack_timestamp ("purse_expiration",
746 : pch->purse_expiration),
747 : GNUNET_JSON_pack_array_steal ("deposits",
748 : deposit_arr));
749 13 : if (NULL == body)
750 : {
751 0 : GNUNET_break (0);
752 0 : return TALER_EC_GENERIC_CURL_ALLOCATION_FAILURE;
753 : }
754 :
755 : {
756 : char pub_str[sizeof (pch->purse_pub) * 2];
757 : char *end;
758 :
759 13 : end = GNUNET_STRINGS_data_to_string (
760 13 : &pch->purse_pub,
761 : sizeof (pch->purse_pub),
762 : pub_str,
763 : sizeof (pub_str));
764 13 : *end = '\0';
765 13 : GNUNET_snprintf (arg_str,
766 : sizeof (arg_str),
767 : "purses/%s/create",
768 : pub_str);
769 : }
770 13 : pch->url = TALER_url_join (pch->base_url,
771 : arg_str,
772 : NULL);
773 13 : if (NULL == pch->url)
774 : {
775 0 : GNUNET_break (0);
776 0 : json_decref (body);
777 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
778 : }
779 13 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
780 : "URL for purse create with deposit: `%s'\n",
781 : pch->url);
782 13 : eh = TALER_EXCHANGE_curl_easy_get_ (pch->url);
783 26 : if ( (NULL == eh) ||
784 : (GNUNET_OK !=
785 13 : TALER_curl_easy_post (&pch->post_ctx,
786 : eh,
787 : body)) )
788 : {
789 0 : GNUNET_break (0);
790 0 : if (NULL != eh)
791 0 : curl_easy_cleanup (eh);
792 0 : json_decref (body);
793 0 : return TALER_EC_GENERIC_CURL_ALLOCATION_FAILURE;
794 : }
795 13 : json_decref (body);
796 26 : pch->job = GNUNET_CURL_job_add2 (pch->ctx,
797 : eh,
798 13 : pch->post_ctx.headers,
799 : &handle_purse_create_deposit_finished,
800 : pch);
801 13 : if (NULL == pch->job)
802 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
803 13 : return TALER_EC_NONE;
804 : }
805 :
806 :
807 : void
808 13 : TALER_EXCHANGE_post_purses_create_cancel (
809 : struct TALER_EXCHANGE_PostPursesCreateHandle *pch)
810 : {
811 13 : if (NULL != pch->job)
812 : {
813 0 : GNUNET_CURL_job_cancel (pch->job);
814 0 : pch->job = NULL;
815 : }
816 13 : json_decref (pch->contract_terms);
817 13 : GNUNET_free (pch->econtract.econtract);
818 13 : GNUNET_free (pch->base_url);
819 13 : GNUNET_free (pch->url);
820 :
821 26 : for (unsigned int i = 0; i<pch->num_deposits; i++)
822 : {
823 13 : struct Deposit *d = &pch->deposits[i];
824 :
825 13 : TALER_denom_sig_free (&d->denom_sig);
826 : }
827 13 : GNUNET_array_grow (pch->deposits,
828 : pch->num_deposits,
829 : 0);
830 13 : TALER_EXCHANGE_keys_decref (pch->keys);
831 13 : TALER_curl_easy_post_finished (&pch->post_ctx);
832 13 : GNUNET_free (pch);
833 13 : }
834 :
835 :
836 : /* end of exchange_api_post-purses-PURSE_PUB-create.c */
|