frosix-httpd_dkg-key.c (24129B)
1 /* 2 This file is part of Frosix 3 Copyright (C) 2022, 2023 Joel Urech 4 5 Frosix is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Affero General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 Frosix 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 Affero General Public License for more details. 12 13 You should have received a copy of the GNU Affero General Public License along with 14 Frosix; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file backend/frosix-httpd_dkg-key.c 18 * @brief functions to handle incoming requests on /dkg-key 19 * @author Joel Urech 20 */ 21 #include "frosix-httpd_dkg.h" 22 #include "frosix-httpd.h" 23 #include "frosix_database_plugin.h" 24 #include "frosix_service.h" 25 #include "keygen.h" 26 #include <taler/taler_util.h> 27 #include <gnunet/gnunet_util_lib.h> 28 #include <gnunet/gnunet_db_lib.h> 29 30 31 struct DkgKeyContext 32 { 33 /** 34 * 35 */ 36 uint8_t provider_index; 37 38 /** 39 * 40 */ 41 uint8_t threshold; 42 43 /** 44 * 45 */ 46 uint8_t num_of_participants; 47 48 /** 49 * 50 */ 51 struct FROSIX_DkgContextStringP context_string; 52 53 /** 54 * 55 */ 56 struct FROSIX_ChallengeHashP auth_hash; 57 58 /** 59 * 60 */ 61 struct FROSIX_EncryptionKey pre_enc_key; 62 63 /** 64 * 65 */ 66 uint8_t expiration; 67 68 69 /** 70 * 71 */ 72 struct FROSIX_DkgRequestIdP request_id; 73 74 /** 75 * 76 */ 77 struct FROSIX_ProviderSaltP provider_salt; 78 79 /** 80 * 81 */ 82 struct FROSIX_SecretProviderSaltP secret_provider_salt; 83 84 /** 85 * 86 */ 87 struct GNUNET_CRYPTO_EddsaPrivateKey priv_sig_key; 88 89 /** 90 * 91 */ 92 struct FROST_DkgShare *dkg_shares; 93 94 /** 95 * Our handler context 96 */ 97 struct TM_HandlerContext *hc; 98 99 /** 100 * Uploaded JSON data, NULL if upload is not yet complete. 101 */ 102 json_t *json; 103 104 /** 105 * Post parser context. 106 */ 107 void *post_ctx; 108 109 /** 110 * Connection handle for closing or resuming 111 */ 112 struct MHD_Connection *connection; 113 114 /** 115 * When should this request time out? 116 */ 117 struct GNUNET_TIME_Absolute timeout; 118 }; 119 120 121 static enum GNUNET_GenericReturnValue 122 derive_encryption_key ( 123 struct FROSIX_EncryptionKey *enc_key, 124 const struct FROSIX_EncryptionKey *pre_enc_key, 125 const struct FROST_PublicKey *pk, 126 const struct FROSIX_ProviderSaltP *provider_salt, 127 uint8_t provider_index) 128 { 129 return GNUNET_CRYPTO_kdf (enc_key, 130 sizeof (*enc_key), 131 "FROSIX-KEY", 132 strlen ("FROSIX-KEY"), 133 pre_enc_key, 134 sizeof (*pre_enc_key), 135 pk, 136 sizeof (*pk), 137 provider_salt, 138 sizeof (*provider_salt), 139 &provider_index, 140 sizeof (provider_index), 141 NULL, 142 0); 143 } 144 145 static void 146 generate_key_pair (struct FROST_KeyPair *key_pair, 147 struct FROST_DkgShare other_shares[], 148 const struct FROST_DkgShare my_shares[], 149 const struct FROST_DkgCommitment dkg_commitments[], 150 uint8_t my_index, 151 uint8_t threshold, 152 uint8_t num_of_participants) 153 { 154 memcpy (&other_shares[my_index - 1].share, 155 &my_shares[my_index - 1].share, 156 sizeof (my_shares[my_index - 1].share)); 157 158 FROST_keygen_finalize (key_pair, 159 my_index, 160 other_shares, 161 dkg_commitments, 162 num_of_participants); 163 } 164 165 static MHD_RESULT 166 store_key_data (struct DkgKeyContext *dc, 167 struct FROST_HashCode *enc_key_hash, 168 struct FROSIX_EncryptionKey *enc_key, 169 struct FROST_KeyPair *key_pair) 170 { 171 /* Copy key data together */ 172 struct FROSIX_KeyDataRaw raw_data; 173 memcpy (&raw_data.bytes[0], 174 &key_pair->my_sk, 175 sizeof (key_pair->my_sk)); 176 177 memcpy (&raw_data.bytes[sizeof key_pair->my_sk], 178 &key_pair->group_pk, 179 sizeof (key_pair->group_pk)); 180 181 /* Generate nonce */ 182 struct FROSIX_EncryptionNonceP nonce; 183 FROSIX_secretbox_nonce_randombytes (&nonce); 184 185 /* Encrypt key data */ 186 struct FROSIX_KeyDataEncrypted ciphertext; 187 FROSIX_secretbox_keydata (&ciphertext, 188 &raw_data, 189 &nonce, 190 enc_key); 191 192 /* store key data */ 193 enum GNUNET_DB_QueryStatus qs; 194 qs = db->store_key (db->cls, 195 enc_key_hash, 196 &nonce, 197 &ciphertext, 198 &dc->auth_hash, 199 dc->expiration, 200 dc->provider_index); 201 switch (qs) 202 { 203 case GNUNET_DB_STATUS_HARD_ERROR: 204 case GNUNET_DB_STATUS_SOFT_ERROR: 205 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 206 GNUNET_break (0); 207 return TALER_MHD_reply_with_error (dc->connection, 208 MHD_HTTP_INTERNAL_SERVER_ERROR, 209 TALER_EC_GENERIC_DB_INVARIANT_FAILURE, 210 "store key"); 211 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 212 break; 213 } 214 return MHD_HTTP_OK; 215 } 216 217 218 MHD_RESULT 219 FH_handler_dkg_key_post ( 220 struct MHD_Connection *connection, 221 struct TM_HandlerContext *hc, 222 const struct FROSIX_DkgRequestIdP *dkg_id, 223 const struct FROSIX_ProviderSaltP *provider_salt, 224 const struct FROSIX_SecretProviderSaltP *secret_provider_salt, 225 const struct GNUNET_CRYPTO_EddsaPrivateKey *priv_sig_key, 226 const char *dkg_key_data, 227 size_t *dkg_key_data_size) 228 { 229 enum GNUNET_GenericReturnValue res; 230 struct DkgKeyContext *dc = hc->ctx; 231 json_t *json_ppk = NULL; 232 json_t *shares = NULL; 233 size_t share_n_comm_length; 234 235 if (NULL == dc) 236 { 237 dc = GNUNET_new (struct DkgKeyContext); 238 dc->connection = connection; 239 dc->request_id = *dkg_id; 240 dc->provider_salt = *provider_salt; 241 dc->secret_provider_salt = *secret_provider_salt; 242 dc->priv_sig_key = *priv_sig_key; 243 hc->ctx = dc; 244 } 245 246 /* parse request body */ 247 if (NULL == dc->json) 248 { 249 res = TALER_MHD_parse_post_json (dc->connection, 250 &dc->post_ctx, 251 dkg_key_data, 252 dkg_key_data_size, 253 &dc->json); 254 if (GNUNET_SYSERR == res) 255 { 256 GNUNET_break (0); 257 return MHD_NO; 258 } 259 if ((GNUNET_NO == res || 260 (NULL == dc->json))) 261 { 262 return MHD_YES; 263 } 264 } 265 266 struct GNUNET_JSON_Specification spec[] = { 267 GNUNET_JSON_spec_uint8 ("provider_index", 268 &dc->provider_index), 269 GNUNET_JSON_spec_uint8 ("threshold", 270 &dc->threshold), 271 GNUNET_JSON_spec_fixed_auto ("context_string", 272 &dc->context_string), 273 GNUNET_JSON_spec_fixed_auto ("auth_hash", 274 &dc->auth_hash), 275 GNUNET_JSON_spec_json ("providers_public_keys", 276 &json_ppk), 277 GNUNET_JSON_spec_fixed_auto ("pre_encryption_key", 278 &dc->pre_enc_key), 279 GNUNET_JSON_spec_json ("secret_shares", 280 &shares), 281 GNUNET_JSON_spec_uint8 ("expiration", 282 &dc->expiration), 283 GNUNET_JSON_spec_end () 284 }; 285 286 res = TALER_MHD_parse_json_data (connection, 287 dc->json, 288 spec); 289 290 if (GNUNET_SYSERR == res) 291 { 292 GNUNET_JSON_parse_free (spec); 293 GNUNET_break (0); 294 return MHD_NO; 295 } 296 if (GNUNET_NO == res) 297 { 298 GNUNET_JSON_parse_free (spec); 299 GNUNET_break_op (0); 300 return TALER_MHD_reply_with_error (connection, 301 MHD_HTTP_BAD_REQUEST, 302 TALER_EC_GENERIC_PARAMETER_MALFORMED, 303 "Unable to parse request body"); 304 } 305 306 /* check number of submitted provider public keys*/ 307 if (254 < json_array_size (json_ppk)) 308 { 309 GNUNET_JSON_parse_free (spec); 310 GNUNET_break_op (0); 311 return TALER_MHD_reply_with_error (connection, 312 MHD_HTTP_BAD_REQUEST, 313 TALER_EC_GENERIC_PARAMETER_MALFORMED, 314 "Too many provider public keys"); 315 } 316 317 /* set number of participants */ 318 dc->num_of_participants = json_array_size (json_ppk); 319 320 /* validate provider_index, threshold and num_of_participants */ 321 if (GNUNET_OK != FROST_validate_dkg_params (dc->provider_index, 322 dc->threshold, 323 dc->num_of_participants)) 324 { 325 GNUNET_JSON_parse_free (spec); 326 GNUNET_break_op (0); 327 return TALER_MHD_reply_with_error (connection, 328 MHD_HTTP_BAD_REQUEST, 329 TALER_EC_GENERIC_PARAMETER_MALFORMED, 330 "Protocol parameters out of bound"); 331 } 332 333 /* Check if called id is matching the send data */ 334 if (GNUNET_OK != FROSIX_dkg_validate_request_id_ (&dc->request_id, 335 &dc->context_string, 336 &dc->auth_hash, 337 &dc->provider_salt, 338 dc->provider_index, 339 dc->num_of_participants, 340 dc->threshold)) 341 { 342 GNUNET_JSON_parse_free (spec); 343 GNUNET_break_op (0); 344 return TALER_MHD_reply_with_error (connection, 345 MHD_HTTP_BAD_REQUEST, 346 TALER_EC_GENERIC_PARAMETER_MALFORMED, 347 "Unable to validate request ID"); 348 } 349 350 /* parse provider public keys */ 351 struct GNUNET_CRYPTO_EddsaPublicKey 352 providers_public_keys[dc->num_of_participants]; 353 for (unsigned int i = 0; i < dc->num_of_participants; i++) 354 { 355 struct GNUNET_JSON_Specification ppk_spec[] = { 356 GNUNET_JSON_spec_fixed_auto (NULL, 357 &providers_public_keys[i]), 358 GNUNET_JSON_spec_end () 359 }; 360 361 res = TALER_MHD_parse_json_array (connection, 362 json_ppk, 363 ppk_spec, 364 i, 365 -1); 366 367 if (GNUNET_SYSERR == res) 368 { 369 GNUNET_JSON_parse_free (spec); 370 GNUNET_JSON_parse_free (ppk_spec); 371 GNUNET_break (0); 372 return MHD_NO; 373 } 374 if (GNUNET_NO == res) 375 { 376 GNUNET_JSON_parse_free (spec); 377 GNUNET_JSON_parse_free (ppk_spec); 378 GNUNET_break_op (0); 379 return TALER_MHD_reply_with_error (connection, 380 MHD_HTTP_BAD_REQUEST, 381 TALER_EC_GENERIC_PARAMETER_MALFORMED, 382 "Unable to parse request body"); 383 } 384 385 GNUNET_JSON_parse_free (ppk_spec); 386 } 387 388 /* check if there are 'num_of_participants - 1' shares */ 389 if (dc->num_of_participants - 1 != json_array_size (shares)) 390 { 391 GNUNET_break_op (0); 392 GNUNET_JSON_parse_free (spec); 393 394 return TALER_MHD_reply_with_error (connection, 395 MHD_HTTP_BAD_REQUEST, 396 TALER_EC_GENERIC_PARAMETER_MALFORMED, 397 "Number of shares not matching"); 398 } 399 400 share_n_comm_length = dc->num_of_participants - 1; 401 402 /* initialize shares and commitments */ 403 struct FROST_DkgShare dkg_shares[dc->num_of_participants]; 404 struct FROST_DkgCommitment dkg_commitments[dc->num_of_participants]; 405 406 { 407 /* parse all shares */ 408 struct FROSIX_EncryptedShareP enc_shares[share_n_comm_length]; 409 struct GNUNET_CRYPTO_EcdhePublicKey pub_enc_keys[share_n_comm_length]; 410 411 unsigned int pi_counter = 0; 412 for (int i = 0; i < share_n_comm_length; i++) 413 { 414 if (dc->provider_index - 1 == pi_counter) 415 pi_counter++; 416 417 struct GNUNET_JSON_Specification share_spec[] = { 418 GNUNET_JSON_spec_uint8 ("provider_index", 419 &dkg_shares[pi_counter].identifier), 420 GNUNET_JSON_spec_fixed_auto ("secret_share", 421 &enc_shares[i]), 422 GNUNET_JSON_spec_fixed_auto ("ephemeral_key", 423 &pub_enc_keys[i]), 424 GNUNET_JSON_spec_end () 425 }; 426 427 res = TALER_MHD_parse_json_array (connection, 428 shares, 429 share_spec, 430 i, 431 -1); 432 433 if (GNUNET_SYSERR == res) 434 { 435 GNUNET_JSON_parse_free (spec); 436 GNUNET_JSON_parse_free (share_spec); 437 GNUNET_break (0); 438 return MHD_NO; 439 } 440 if (GNUNET_NO == res) 441 { 442 GNUNET_JSON_parse_free (spec); 443 GNUNET_JSON_parse_free (share_spec); 444 GNUNET_break_op (0); 445 return TALER_MHD_reply_with_error (connection, 446 MHD_HTTP_BAD_REQUEST, 447 TALER_EC_GENERIC_PARAMETER_MALFORMED, 448 "Unable to parse request body"); 449 } 450 451 /* free json spec */ 452 GNUNET_JSON_parse_free (share_spec); 453 454 pi_counter++; 455 } 456 457 /* free json spec */ 458 GNUNET_JSON_parse_free (spec); 459 460 /* get commitments from db */ 461 { 462 struct FROSIX_DkgCommitmentsRaw commits; 463 commits.length = (sizeof (dc->provider_index) 464 + (sizeof (struct FROST_Point) 465 * dc->threshold)) 466 * (share_n_comm_length); 467 468 enum GNUNET_DB_QueryStatus qs; 469 qs = db->get_dkg_commitment (db->cls, 470 &dc->request_id, 471 &commits); 472 473 switch (qs) 474 { 475 case GNUNET_DB_STATUS_HARD_ERROR: 476 case GNUNET_DB_STATUS_SOFT_ERROR: 477 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 478 GNUNET_break (0); 479 return TALER_MHD_reply_with_error (connection, 480 MHD_HTTP_INTERNAL_SERVER_ERROR, 481 TALER_EC_GENERIC_DB_FETCH_FAILED, 482 "get_dkg_commitments"); 483 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 484 break; 485 } 486 487 // FIXME: check how many bytes we got from the db 488 489 /* parse result to struct */ 490 for (int i = 0; i < dc->num_of_participants; i++) 491 { 492 if (GNUNET_OK != FROST_initialize_dkg_commitment (&dkg_commitments[i], 493 dc->provider_index, 494 dc->threshold)) 495 { 496 FROST_free_dkg_commitment (dkg_commitments, 497 dc->num_of_participants); 498 GNUNET_break (0); 499 return TALER_MHD_reply_with_error (connection, 500 MHD_HTTP_INTERNAL_SERVER_ERROR, 501 TALER_EC_GENERIC_DB_FETCH_FAILED, 502 "Initialize DKG commitment"); 503 } 504 } 505 506 unsigned int offset = 0; 507 unsigned int comm_counter = 0; 508 for (uint8_t i = 0; i < share_n_comm_length; i++) 509 { 510 if (dc->provider_index - 1 == comm_counter) 511 comm_counter++; 512 513 memcpy (&dkg_commitments[comm_counter].identifier, 514 (char *) commits.ptr_commits + offset, 515 1); 516 offset += 1; 517 518 for (int j = 0; j < dc->threshold; j++) 519 { 520 memcpy (&dkg_commitments[comm_counter].share_comm[j].sc.xcoord, 521 (char *) commits.ptr_commits + offset, 522 sizeof (struct FROST_Point)); 523 offset += sizeof (struct FROST_Point); 524 } 525 526 dkg_commitments[comm_counter].shares_commitments_length = dc->threshold; 527 528 comm_counter++; 529 } 530 } 531 532 533 /* check if shares are valid */ 534 unsigned int share_counter = 0; 535 for (int i = 0; i < share_n_comm_length; i++) 536 { 537 if (dc->provider_index - 1 == i) 538 share_counter++; 539 540 /* get key material */ 541 struct GNUNET_HashCode key_material; 542 if (GNUNET_OK != GNUNET_CRYPTO_eddsa_ecdh (&dc->priv_sig_key, 543 &pub_enc_keys[i], 544 &key_material)) 545 { 546 GNUNET_break_op (0); 547 FROST_free_dkg_commitment (dkg_commitments, 548 dc->num_of_participants); 549 return TALER_MHD_reply_with_error (connection, 550 MHD_HTTP_BAD_REQUEST, 551 TALER_EC_GENERIC_PARAMETER_MALFORMED, 552 "Unable to compute encryption key"); 553 } 554 555 /* derive key */ 556 struct GNUNET_CRYPTO_SymmetricSessionKey sym_key; 557 struct GNUNET_CRYPTO_SymmetricInitializationVector iv; 558 GNUNET_CRYPTO_hash_to_aes_key (&key_material, 559 &sym_key, 560 &iv); 561 562 /* decrypt share */ 563 ssize_t len = GNUNET_CRYPTO_symmetric_decrypt (&enc_shares[i], 564 sizeof (enc_shares[i]), 565 &sym_key, 566 &iv, 567 &dkg_shares[share_counter]. 568 share); 569 570 if ( -1 == len || sizeof (dkg_shares[share_counter].share) != len) 571 { 572 GNUNET_break_op (0); 573 FROST_free_dkg_commitment (dkg_commitments, 574 dc->num_of_participants); 575 return TALER_MHD_reply_with_error (connection, 576 MHD_HTTP_BAD_REQUEST, 577 TALER_EC_GENERIC_PARAMETER_MALFORMED, 578 "Unable to decrypt encrypted share"); 579 } 580 581 if (GNUNET_OK != FROST_keygen_validate_share ( 582 &dkg_commitments[share_counter], 583 &dkg_shares[share_counter], 584 dc->provider_index)) 585 { 586 GNUNET_break_op (0); 587 FROST_free_dkg_commitment (dkg_commitments, 588 dc->num_of_participants); 589 return TALER_MHD_reply_with_error (connection, 590 MHD_HTTP_BAD_REQUEST, 591 TALER_EC_GENERIC_PARAMETER_MALFORMED, 592 "Unable to validate share"); 593 } 594 share_counter++; 595 } 596 } 597 598 /* derive the final context string */ 599 struct FROST_DkgContextString cs_h; 600 if (GNUNET_YES != FROSIX_dkg_derive_context_string_ ( 601 &cs_h, 602 dc->provider_index, 603 dc->threshold, 604 &dc->context_string, 605 &dc->auth_hash, 606 providers_public_keys, 607 dc->num_of_participants, 608 &dc->secret_provider_salt)) 609 { 610 GNUNET_break (0); 611 return TALER_MHD_reply_with_error (connection, 612 MHD_HTTP_INTERNAL_SERVER_ERROR, 613 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 614 "Derive context string"); 615 } 616 617 /* calculate own commitment and share */ 618 if (GNUNET_OK != FROSIX_dkg_commitment_generate_ ( 619 &dkg_commitments[dc->provider_index - 1], 620 &cs_h, 621 NULL, 622 dc->provider_index, 623 dc->threshold, 624 dc->num_of_participants)) 625 { 626 FROST_free_dkg_commitment (dkg_commitments, 627 dc->num_of_participants); 628 GNUNET_break (0); 629 return TALER_MHD_reply_with_error (connection, 630 MHD_HTTP_INTERNAL_SERVER_ERROR, 631 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 632 "Generate DKG commitment"); 633 } 634 635 struct FROST_DkgShare my_shares[dc->num_of_participants]; 636 if (GNUNET_OK != FROSIX_dkg_shares_generate_ ( 637 my_shares, 638 &cs_h, 639 NULL, 640 dc->provider_index, 641 dc->threshold, 642 dc->num_of_participants)) 643 { 644 FROST_free_dkg_commitment (dkg_commitments, 645 dc->num_of_participants); 646 GNUNET_break (0); 647 return TALER_MHD_reply_with_error (connection, 648 MHD_HTTP_INTERNAL_SERVER_ERROR, 649 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 650 "Generate DKG shares"); 651 } 652 653 /* calculate key share */ 654 struct FROST_KeyPair key_pair; 655 generate_key_pair (&key_pair, 656 dkg_shares, 657 my_shares, 658 dkg_commitments, 659 dc->provider_index, 660 dc->threshold, 661 dc->num_of_participants); 662 663 /* free initialized memory */ 664 FROST_free_dkg_commitment (dkg_commitments, 665 dc->num_of_participants); 666 667 /* Derive symmetric key */ 668 struct FROSIX_EncryptionKey encryption_key; 669 if (GNUNET_OK != derive_encryption_key (&encryption_key, 670 &dc->pre_enc_key, 671 &key_pair.group_pk, 672 &dc->provider_salt, 673 dc->provider_index)) 674 { 675 GNUNET_break (0); 676 return TALER_MHD_reply_with_error (connection, 677 MHD_HTTP_INTERNAL_SERVER_ERROR, 678 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 679 "Derive encryption key"); 680 } 681 682 /* check if key is already stored */ 683 /* hash encryption key */ 684 struct FROST_HashCode enc_key_hash; 685 FROSIX_hash_encryption_key (&enc_key_hash, 686 &encryption_key); 687 688 enum GNUNET_DB_QueryStatus qs; 689 qs = db->lookup_key (db->cls, 690 &enc_key_hash); 691 692 switch (qs) 693 { 694 case GNUNET_DB_STATUS_HARD_ERROR: 695 case GNUNET_DB_STATUS_SOFT_ERROR: 696 GNUNET_break (0); 697 return TALER_MHD_reply_with_error (connection, 698 MHD_HTTP_INTERNAL_SERVER_ERROR, 699 TALER_EC_GENERIC_DB_INVARIANT_FAILURE, 700 "lookup key"); 701 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 702 /* store key data */ 703 MHD_RESULT mhd_res; 704 mhd_res = store_key_data (dc, 705 &enc_key_hash, 706 &encryption_key, 707 &key_pair); 708 if (MHD_HTTP_OK != mhd_res) 709 { 710 return mhd_res; 711 } 712 break; 713 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 714 // key is already stored - go ahead 715 break; 716 } 717 718 // Sign data 719 struct FROSIX_DkgKeySignaturePS ks = { 720 .purpose.purpose = htonl (104), 721 .purpose.size = htonl (sizeof (ks)), 722 .public_key = key_pair.group_pk, 723 .auth_hash = dc->auth_hash, 724 }; 725 726 struct GNUNET_CRYPTO_EddsaSignature sig; 727 GNUNET_CRYPTO_eddsa_sign (priv_sig_key, 728 &ks, 729 &sig); 730 731 return TALER_MHD_REPLY_JSON_PACK ( 732 connection, 733 MHD_HTTP_OK, 734 GNUNET_JSON_pack_data_auto ("public_key", 735 &key_pair.group_pk), 736 GNUNET_JSON_pack_data_auto ("signature", 737 &sig)); 738 }