Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2022 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it
6 : under the terms of the GNU General Public License as published by
7 : the Free Software Foundation; either version 3, or (at your
8 : option) any later version.
9 :
10 : TALER is distributed in the hope that it will be useful, but
11 : WITHOUT ANY WARRANTY; without even the implied warranty of
12 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 : General Public License for more details.
14 :
15 : You should have received a copy of the GNU General Public
16 : License along with TALER; see the file COPYING. If not, see
17 : <http://www.gnu.org/licenses/>
18 : */
19 : /**
20 : * @file testing/testing_api_cmd_purse_create_deposit.c
21 : * @brief command for testing /purses/$PID/create
22 : * @author Christian Grothoff
23 : */
24 : #include "taler/taler_json_lib.h"
25 : #include <gnunet/gnunet_curl_lib.h>
26 : #include "taler/taler_testing_lib.h"
27 : #include "taler/taler_signatures.h"
28 :
29 : /**
30 : * Information we keep per deposited coin.
31 : */
32 : struct Coin
33 : {
34 : /**
35 : * Reference to the respective command.
36 : */
37 : char *command_ref;
38 :
39 : /**
40 : * index of the specific coin in the traits of @e command_ref.
41 : */
42 : unsigned int coin_index;
43 :
44 : /**
45 : * Public key of the deposited coin.
46 : */
47 : struct TALER_CoinSpendPublicKeyP coin_pub;
48 :
49 : /**
50 : * Amount to deposit (with fee).
51 : */
52 : struct TALER_Amount deposit_with_fee;
53 :
54 : /**
55 : * Entry in the coin's history generated by this operation.
56 : */
57 : struct TALER_EXCHANGE_CoinHistoryEntry che;
58 :
59 : };
60 :
61 :
62 : /**
63 : * State for a "purse create deposit" CMD.
64 : */
65 : struct PurseCreateDepositState
66 : {
67 :
68 : /**
69 : * Total purse target amount without fees.
70 : */
71 : struct TALER_Amount target_amount;
72 :
73 : /**
74 : * Reference to any command that is able to provide a coin.
75 : */
76 : struct Coin *coin_references;
77 :
78 : /**
79 : * JSON string describing what a proposal is about.
80 : */
81 : json_t *contract_terms;
82 :
83 : /**
84 : * Purse expiration time.
85 : */
86 : struct GNUNET_TIME_Timestamp purse_expiration;
87 :
88 : /**
89 : * Relative purse expiration time.
90 : */
91 : struct GNUNET_TIME_Relative rel_expiration;
92 :
93 : /**
94 : * Set (by the interpreter) to a fresh private key. This
95 : * key will be used to create the purse.
96 : */
97 : struct TALER_PurseContractPrivateKeyP purse_priv;
98 :
99 : /**
100 : * Set (by the interpreter) to a fresh private key. This
101 : * key will be used to merge the purse.
102 : */
103 : struct TALER_PurseMergePrivateKeyP merge_priv;
104 :
105 : /**
106 : * Set (by the interpreter) to a fresh private key. This
107 : * key will be used to decrypt the contract.
108 : */
109 : struct TALER_ContractDiffiePrivateP contract_priv;
110 :
111 : /**
112 : * Signing key used by the exchange to sign the
113 : * deposit confirmation.
114 : */
115 : struct TALER_ExchangePublicKeyP exchange_pub;
116 :
117 : /**
118 : * Signature from the exchange on the
119 : * deposit confirmation.
120 : */
121 : struct TALER_ExchangeSignatureP exchange_sig;
122 :
123 : /**
124 : * Set (by the interpreter) to a public key corresponding
125 : * to @e purse_priv.
126 : */
127 : struct TALER_PurseContractPublicKeyP purse_pub;
128 :
129 : /**
130 : * PurseCreateDeposit handle while operation is running.
131 : */
132 : struct TALER_EXCHANGE_PostPursesCreateHandle *dh;
133 :
134 : /**
135 : * Interpreter state.
136 : */
137 : struct TALER_TESTING_Interpreter *is;
138 :
139 : /**
140 : * Expected HTTP response code.
141 : */
142 : unsigned int expected_response_code;
143 :
144 : /**
145 : * Length of the @e coin_references array.
146 : */
147 : unsigned int num_coin_references;
148 :
149 : /**
150 : * Should we upload the contract?
151 : */
152 : bool upload_contract;
153 :
154 : };
155 :
156 :
157 : /**
158 : * Callback to analyze the /purses/$PID/create response, just used to check if
159 : * the response code is acceptable.
160 : *
161 : * @param cls closure.
162 : * @param dr deposit response details
163 : */
164 : static void
165 11 : deposit_cb (void *cls,
166 : const struct TALER_EXCHANGE_PostPursesCreateResponse *dr)
167 : {
168 11 : struct PurseCreateDepositState *ds = cls;
169 :
170 11 : ds->dh = NULL;
171 11 : if (ds->expected_response_code != dr->hr.http_status)
172 : {
173 0 : TALER_TESTING_unexpected_status (ds->is,
174 : dr->hr.http_status,
175 : ds->expected_response_code);
176 0 : return;
177 : }
178 11 : if (MHD_HTTP_OK == dr->hr.http_status)
179 : {
180 9 : ds->exchange_pub = dr->details.ok.exchange_pub;
181 9 : ds->exchange_sig = dr->details.ok.exchange_sig;
182 : }
183 11 : TALER_TESTING_interpreter_next (ds->is);
184 : }
185 :
186 :
187 : /**
188 : * Run the command.
189 : *
190 : * @param cls closure.
191 : * @param cmd the command to execute.
192 : * @param is the interpreter state.
193 : */
194 : static void
195 11 : deposit_run (void *cls,
196 : const struct TALER_TESTING_Command *cmd,
197 : struct TALER_TESTING_Interpreter *is)
198 11 : {
199 11 : struct PurseCreateDepositState *ds = cls;
200 11 : struct TALER_EXCHANGE_PurseDeposit deposits[ds->num_coin_references];
201 :
202 : (void) cmd;
203 11 : ds->is = is;
204 11 : GNUNET_CRYPTO_eddsa_key_create (&ds->purse_priv.eddsa_priv);
205 11 : GNUNET_CRYPTO_eddsa_key_create (&ds->merge_priv.eddsa_priv);
206 11 : GNUNET_CRYPTO_ecdhe_key_create (&ds->contract_priv.ecdhe_priv);
207 11 : GNUNET_CRYPTO_eddsa_key_get_public (&ds->purse_priv.eddsa_priv,
208 : &ds->purse_pub.eddsa_pub);
209 :
210 22 : for (unsigned int i = 0; i<ds->num_coin_references; i++)
211 : {
212 11 : struct Coin *cr = &ds->coin_references[i];
213 11 : struct TALER_EXCHANGE_PurseDeposit *pd = &deposits[i];
214 : const struct TALER_TESTING_Command *coin_cmd;
215 : const struct TALER_CoinSpendPrivateKeyP *coin_priv;
216 11 : const struct TALER_AgeCommitmentProof *age_commitment_proof = NULL;
217 : const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
218 : const struct TALER_DenominationSignature *denom_pub_sig;
219 :
220 11 : coin_cmd = TALER_TESTING_interpreter_lookup_command (is,
221 11 : cr->command_ref);
222 11 : if (NULL == coin_cmd)
223 : {
224 0 : GNUNET_break (0);
225 0 : TALER_TESTING_interpreter_fail (is);
226 0 : return;
227 : }
228 :
229 11 : if ( (GNUNET_OK !=
230 11 : TALER_TESTING_get_trait_coin_priv (coin_cmd,
231 : cr->coin_index,
232 11 : &coin_priv)) ||
233 : (GNUNET_OK !=
234 11 : TALER_TESTING_get_trait_age_commitment_proof (coin_cmd,
235 : cr->coin_index,
236 : &age_commitment_proof))
237 11 : ||
238 : (GNUNET_OK !=
239 11 : TALER_TESTING_get_trait_denom_pub (coin_cmd,
240 : cr->coin_index,
241 11 : &denom_pub)) ||
242 : (GNUNET_OK !=
243 11 : TALER_TESTING_get_trait_denom_sig (coin_cmd,
244 : cr->coin_index,
245 : &denom_pub_sig)) )
246 : {
247 0 : GNUNET_break (0);
248 0 : TALER_TESTING_interpreter_fail (is);
249 0 : return;
250 : }
251 11 : pd->age_commitment_proof = age_commitment_proof;
252 11 : pd->denom_sig = *denom_pub_sig;
253 11 : pd->coin_priv = *coin_priv;
254 11 : pd->amount = cr->deposit_with_fee;
255 11 : pd->h_denom_pub = denom_pub->h_key;
256 11 : GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
257 : &cr->coin_pub.eddsa_pub);
258 11 : cr->che.type = TALER_EXCHANGE_CTT_PURSE_DEPOSIT;
259 11 : cr->che.amount = cr->deposit_with_fee;
260 11 : GNUNET_CRYPTO_eddsa_key_get_public (
261 11 : &ds->purse_priv.eddsa_priv,
262 : &cr->che.details.purse_deposit.purse_pub.eddsa_pub);
263 : cr->che.details.purse_deposit.exchange_base_url
264 11 : = TALER_TESTING_get_exchange_url (is);
265 11 : TALER_age_commitment_hash (
266 11 : &age_commitment_proof->commitment,
267 : &cr->che.details.purse_deposit.phac);
268 : }
269 :
270 : ds->purse_expiration =
271 11 : GNUNET_TIME_absolute_to_timestamp (
272 : GNUNET_TIME_relative_to_absolute (ds->rel_expiration));
273 11 : GNUNET_assert (0 ==
274 : json_object_set_new (
275 : ds->contract_terms,
276 : "pay_deadline",
277 : GNUNET_JSON_from_timestamp (ds->purse_expiration)));
278 11 : ds->dh = TALER_EXCHANGE_post_purses_create_create (
279 : TALER_TESTING_interpreter_get_context (is),
280 : TALER_TESTING_get_exchange_url (is),
281 : TALER_TESTING_get_keys (is),
282 11 : &ds->purse_priv,
283 11 : &ds->merge_priv,
284 11 : &ds->contract_priv,
285 11 : ds->contract_terms,
286 : ds->num_coin_references,
287 : deposits);
288 11 : GNUNET_assert (NULL != ds->dh);
289 11 : if (ds->upload_contract)
290 11 : TALER_EXCHANGE_post_purses_create_set_options (
291 : ds->dh,
292 : TALER_EXCHANGE_post_purses_create_option_upload_contract ());
293 11 : GNUNET_assert (TALER_EC_NONE ==
294 : TALER_EXCHANGE_post_purses_create_start (ds->dh,
295 : &deposit_cb,
296 : ds));
297 : }
298 :
299 :
300 : /**
301 : * Free the state of a "deposit" CMD, and possibly cancel a
302 : * pending operation thereof.
303 : *
304 : * @param cls closure, must be a `struct PurseCreateDepositState`.
305 : * @param cmd the command which is being cleaned up.
306 : */
307 : static void
308 11 : deposit_cleanup (void *cls,
309 : const struct TALER_TESTING_Command *cmd)
310 : {
311 11 : struct PurseCreateDepositState *ds = cls;
312 :
313 11 : if (NULL != ds->dh)
314 : {
315 0 : TALER_TESTING_command_incomplete (ds->is,
316 : cmd->label);
317 0 : TALER_EXCHANGE_post_purses_create_cancel (ds->dh);
318 0 : ds->dh = NULL;
319 : }
320 22 : for (unsigned int i = 0; i<ds->num_coin_references; i++)
321 11 : GNUNET_free (ds->coin_references[i].command_ref);
322 11 : json_decref (ds->contract_terms);
323 11 : GNUNET_free (ds->coin_references);
324 11 : GNUNET_free (ds);
325 11 : }
326 :
327 :
328 : /**
329 : * Offer internal data from a "deposit" CMD, to other commands.
330 : *
331 : * @param cls closure.
332 : * @param[out] ret result.
333 : * @param trait name of the trait.
334 : * @param index index number of the object to offer.
335 : * @return #GNUNET_OK on success.
336 : */
337 : static enum GNUNET_GenericReturnValue
338 32 : deposit_traits (void *cls,
339 : const void **ret,
340 : const char *trait,
341 : unsigned int index)
342 : {
343 32 : struct PurseCreateDepositState *ds = cls;
344 32 : if (index >= ds->num_coin_references)
345 2 : return GNUNET_NO;
346 :
347 : {
348 30 : const struct Coin *co = &ds->coin_references[index];
349 : struct TALER_TESTING_Trait traits[] = {
350 30 : TALER_TESTING_make_trait_merge_priv (&ds->merge_priv),
351 30 : TALER_TESTING_make_trait_contract_priv (&ds->contract_priv),
352 30 : TALER_TESTING_make_trait_coin_history (index,
353 : &co->che),
354 30 : TALER_TESTING_make_trait_coin_pub (index,
355 : &co->coin_pub),
356 30 : TALER_TESTING_make_trait_purse_priv (&ds->purse_priv),
357 30 : TALER_TESTING_make_trait_purse_pub (&ds->purse_pub),
358 30 : TALER_TESTING_make_trait_contract_terms (ds->contract_terms),
359 30 : TALER_TESTING_make_trait_deposit_amount (0,
360 30 : &ds->target_amount),
361 30 : TALER_TESTING_make_trait_timestamp (index,
362 30 : &ds->purse_expiration),
363 30 : TALER_TESTING_trait_end ()
364 : };
365 :
366 30 : return TALER_TESTING_get_trait (traits,
367 : ret,
368 : trait,
369 : index);
370 : }
371 : }
372 :
373 :
374 : struct TALER_TESTING_Command
375 11 : TALER_TESTING_cmd_purse_create_with_deposit (
376 : const char *label,
377 : unsigned int expected_http_status,
378 : const char *contract_terms,
379 : bool upload_contract,
380 : struct GNUNET_TIME_Relative purse_expiration,
381 : ...)
382 : {
383 : struct PurseCreateDepositState *ds;
384 :
385 11 : ds = GNUNET_new (struct PurseCreateDepositState);
386 11 : ds->rel_expiration = purse_expiration;
387 11 : ds->upload_contract = upload_contract;
388 11 : ds->expected_response_code = expected_http_status;
389 11 : ds->contract_terms = json_loads (contract_terms,
390 : JSON_REJECT_DUPLICATES,
391 : NULL);
392 11 : if (NULL == ds->contract_terms)
393 : {
394 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
395 : "Failed to parse contract terms `%s' for CMD `%s'\n",
396 : contract_terms,
397 : label);
398 0 : GNUNET_assert (0);
399 : }
400 : {
401 : va_list ap;
402 : unsigned int i;
403 : const char *ref;
404 : const char *val;
405 :
406 11 : va_start (ap, purse_expiration);
407 33 : while (NULL != (va_arg (ap, const char *)))
408 22 : ds->num_coin_references++;
409 11 : va_end (ap);
410 11 : GNUNET_assert (0 == (ds->num_coin_references % 2));
411 11 : ds->num_coin_references /= 2;
412 11 : ds->coin_references = GNUNET_new_array (ds->num_coin_references,
413 : struct Coin);
414 11 : i = 0;
415 11 : va_start (ap, purse_expiration);
416 22 : while (NULL != (ref = va_arg (ap, const char *)))
417 : {
418 11 : struct Coin *c = &ds->coin_references[i++];
419 :
420 11 : GNUNET_assert (NULL != (val = va_arg (ap, const char *)));
421 11 : GNUNET_assert (GNUNET_OK ==
422 : TALER_TESTING_parse_coin_reference (
423 : ref,
424 : &c->command_ref,
425 : &c->coin_index));
426 11 : GNUNET_assert (GNUNET_OK ==
427 : TALER_string_to_amount (val,
428 : &c->deposit_with_fee));
429 : }
430 11 : va_end (ap);
431 : }
432 : {
433 11 : struct TALER_TESTING_Command cmd = {
434 : .cls = ds,
435 : .label = label,
436 : .run = &deposit_run,
437 : .cleanup = &deposit_cleanup,
438 : .traits = &deposit_traits
439 : };
440 :
441 11 : return cmd;
442 : }
443 : }
444 :
445 :
446 : /* end of testing_api_cmd_purse_create_deposit.c */
|