From a93ccc1ebb57f50ce6e90c7f0e57a3e58020b05e Mon Sep 17 00:00:00 2001 From: Martin Schanzenbach Date: Thu, 19 Oct 2023 10:00:01 +0200 Subject: BUILD: Move scalarproduct to contrib/service --- src/contrib/service/Makefile.am | 1 + src/contrib/service/scalarproduct/.gitignore | 6 + src/contrib/service/scalarproduct/Makefile.am | 116 ++ .../service/scalarproduct/gnunet-scalarproduct.c | 401 ++++++ .../gnunet-service-scalarproduct-ecc.h | 118 ++ .../gnunet-service-scalarproduct-ecc_alice.c | 1150 ++++++++++++++++ .../gnunet-service-scalarproduct-ecc_bob.c | 1060 +++++++++++++++ .../scalarproduct/gnunet-service-scalarproduct.h | 142 ++ .../gnunet-service-scalarproduct_alice.c | 1388 ++++++++++++++++++++ .../gnunet-service-scalarproduct_bob.c | 1384 +++++++++++++++++++ src/contrib/service/scalarproduct/meson.build | 102 ++ .../service/scalarproduct/perf_scalarproduct.sh | 73 + .../service/scalarproduct/scalarproduct.conf.in | 27 + src/contrib/service/scalarproduct/scalarproduct.h | 177 +++ .../service/scalarproduct/scalarproduct_api.c | 468 +++++++ .../service/scalarproduct/test_ecc_scalarproduct.c | 212 +++ .../service/scalarproduct/test_scalarproduct.conf | 23 + .../service/scalarproduct/test_scalarproduct.sh | 48 + .../scalarproduct/test_scalarproduct_negative.sh | 49 + .../test_scalarproduct_negativezero.sh | 46 + 20 files changed, 6991 insertions(+) create mode 100644 src/contrib/service/scalarproduct/.gitignore create mode 100644 src/contrib/service/scalarproduct/Makefile.am create mode 100644 src/contrib/service/scalarproduct/gnunet-scalarproduct.c create mode 100644 src/contrib/service/scalarproduct/gnunet-service-scalarproduct-ecc.h create mode 100644 src/contrib/service/scalarproduct/gnunet-service-scalarproduct-ecc_alice.c create mode 100644 src/contrib/service/scalarproduct/gnunet-service-scalarproduct-ecc_bob.c create mode 100644 src/contrib/service/scalarproduct/gnunet-service-scalarproduct.h create mode 100644 src/contrib/service/scalarproduct/gnunet-service-scalarproduct_alice.c create mode 100644 src/contrib/service/scalarproduct/gnunet-service-scalarproduct_bob.c create mode 100644 src/contrib/service/scalarproduct/meson.build create mode 100755 src/contrib/service/scalarproduct/perf_scalarproduct.sh create mode 100644 src/contrib/service/scalarproduct/scalarproduct.conf.in create mode 100644 src/contrib/service/scalarproduct/scalarproduct.h create mode 100644 src/contrib/service/scalarproduct/scalarproduct_api.c create mode 100644 src/contrib/service/scalarproduct/test_ecc_scalarproduct.c create mode 100644 src/contrib/service/scalarproduct/test_scalarproduct.conf create mode 100755 src/contrib/service/scalarproduct/test_scalarproduct.sh create mode 100755 src/contrib/service/scalarproduct/test_scalarproduct_negative.sh create mode 100755 src/contrib/service/scalarproduct/test_scalarproduct_negativezero.sh (limited to 'src/contrib') diff --git a/src/contrib/service/Makefile.am b/src/contrib/service/Makefile.am index cfab56206..4092cc46c 100644 --- a/src/contrib/service/Makefile.am +++ b/src/contrib/service/Makefile.am @@ -10,4 +10,5 @@ SUBDIRS = \ set \ consensus \ secretsharing \ + scalarproduct \ $(EXP_DIR) diff --git a/src/contrib/service/scalarproduct/.gitignore b/src/contrib/service/scalarproduct/.gitignore new file mode 100644 index 000000000..19909a3f9 --- /dev/null +++ b/src/contrib/service/scalarproduct/.gitignore @@ -0,0 +1,6 @@ +gnunet-service-scalarproduct-ecc-bob +gnunet-scalarproduct +gnunet-service-scalarproduct-alice +gnunet-service-scalarproduct-bob +gnunet-service-scalarproduct-ecc-alice +test_ecc_scalarproduct diff --git a/src/contrib/service/scalarproduct/Makefile.am b/src/contrib/service/scalarproduct/Makefile.am new file mode 100644 index 000000000..77a17ec20 --- /dev/null +++ b/src/contrib/service/scalarproduct/Makefile.am @@ -0,0 +1,116 @@ +# This Makefile.am is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include + +pkgcfgdir= $(pkgdatadir)/config.d/ + +libexecdir= $(pkglibdir)/libexec/ + +pkgcfg_DATA = \ + scalarproduct.conf + +if USE_COVERAGE + AM_CFLAGS = -fprofile-arcs -ftest-coverage +endif + +bin_PROGRAMS = \ + gnunet-scalarproduct + +libexec_PROGRAMS = \ + gnunet-service-scalarproduct-alice \ + gnunet-service-scalarproduct-bob \ + gnunet-service-scalarproduct-ecc-alice \ + gnunet-service-scalarproduct-ecc-bob + +lib_LTLIBRARIES = \ + libgnunetscalarproduct.la + +gnunet_scalarproduct_SOURCES = \ + gnunet-scalarproduct.c +gnunet_scalarproduct_LDADD = \ + $(top_builddir)/src/lib/util/libgnunetutil.la \ + libgnunetscalarproduct.la \ + $(LIBGCRYPT_LIBS) \ + -lgcrypt \ + $(GN_LIBINTL) + +gnunet_service_scalarproduct_alice_SOURCES = \ + gnunet-service-scalarproduct.h \ + gnunet-service-scalarproduct_alice.c +gnunet_service_scalarproduct_alice_LDADD = \ + $(top_builddir)/src/lib/util/libgnunetutil.la \ + $(top_builddir)/src/service/cadet/libgnunetcadet.la \ + $(top_builddir)/src/seti/libgnunetseti.la \ + $(LIBGCRYPT_LIBS) \ + -lgcrypt \ + $(GN_LIBINTL) + +gnunet_service_scalarproduct_bob_SOURCES = \ + gnunet-service-scalarproduct.h \ + gnunet-service-scalarproduct_bob.c +gnunet_service_scalarproduct_bob_LDADD = \ + $(top_builddir)/src/lib/util/libgnunetutil.la \ + $(top_builddir)/src/service/cadet/libgnunetcadet.la \ + $(top_builddir)/src/seti/libgnunetseti.la \ + $(LIBGCRYPT_LIBS) \ + -lgcrypt \ + $(GN_LIBINTL) + +gnunet_service_scalarproduct_ecc_alice_SOURCES = \ + gnunet-service-scalarproduct-ecc.h \ + gnunet-service-scalarproduct-ecc_alice.c +gnunet_service_scalarproduct_ecc_alice_LDADD = \ + $(top_builddir)/src/lib/util/libgnunetutil.la \ + $(top_builddir)/src/service/cadet/libgnunetcadet.la \ + $(top_builddir)/src/seti/libgnunetseti.la \ + $(LIBGCRYPT_LIBS) \ + -lsodium \ + -lgcrypt \ + $(GN_LIBINTL) + +gnunet_service_scalarproduct_ecc_bob_SOURCES = \ + gnunet-service-scalarproduct-ecc.h \ + gnunet-service-scalarproduct-ecc_bob.c +gnunet_service_scalarproduct_ecc_bob_LDADD = \ + $(top_builddir)/src/lib/util/libgnunetutil.la \ + $(top_builddir)/src/service/cadet/libgnunetcadet.la \ + $(top_builddir)/src/seti/libgnunetseti.la \ + $(LIBGCRYPT_LIBS) \ + -lsodium \ + -lgcrypt \ + $(GN_LIBINTL) + +libgnunetscalarproduct_la_SOURCES = \ + scalarproduct_api.c \ + scalarproduct.h +libgnunetscalarproduct_la_LIBADD = \ + $(top_builddir)/src/lib/util/libgnunetutil.la \ + $(top_builddir)/src/service/statistics/libgnunetstatistics.la \ + $(LIBGCRYPT_LIBS) \ + -lgcrypt \ + $(LTLIBINTL) +libgnunetscalarproduct_la_LDFLAGS = \ + $(GN_LIB_LDFLAGS) + +EXTRA_DIST = \ + test_scalarproduct.conf \ + $(check_SCRIPTS) + +check_SCRIPTS = \ + test_scalarproduct.sh \ + test_scalarproduct_negative.sh \ + test_scalarproduct_negativezero.sh + +check_PROGRAMS = \ + # test_ecc_scalarproduct + +if ENABLE_TEST_RUN + AM_TESTS_ENVIRONMENT=export GNUNET_PREFIX=$${GNUNET_PREFIX:-@libdir@};export PATH=$${GNUNET_PREFIX:-@prefix@}/bin:$$PATH;unset XDG_DATA_HOME;unset XDG_CONFIG_HOME; + TESTS = $(check_SCRIPTS) $(check_PROGRAMS) +endif + + +test_ecc_scalarproduct_SOURCES = \ + test_ecc_scalarproduct.c +test_ecc_scalarproduct_LDADD = \ + $(top_builddir)/src/lib/util/libgnunetutil.la \ + -lsodium diff --git a/src/contrib/service/scalarproduct/gnunet-scalarproduct.c b/src/contrib/service/scalarproduct/gnunet-scalarproduct.c new file mode 100644 index 000000000..5ebab5baf --- /dev/null +++ b/src/contrib/service/scalarproduct/gnunet-scalarproduct.c @@ -0,0 +1,401 @@ +/* + This file is part of GNUnet. + Copyright (C) 2013, 2014 GNUnet e.V. + + GNUnet is free software: you can redistribute it and/or modify it + under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ + +/** + * @file scalarproduct/gnunet-scalarproduct.c + * @brief scalarproduct client + * @author Christian M. Fuchs + */ +#define GCRYPT_NO_DEPRECATED +#include "platform.h" +#include +#include + +#include "gnunet_util_lib.h" +#include "gnunet_scalarproduct_service.h" +#include "gnunet_protocols.h" +#include "scalarproduct.h" + +#define LOG(kind, ...) GNUNET_log_from (kind, "gnunet-scalarproduct", \ + __VA_ARGS__) + + +/** + * the session key identifying this computation + */ +static struct GNUNET_HashCode session_key; + +/** + * PeerID we want to compute a scalar product with + */ +static struct GNUNET_PeerIdentity peer_id; + +/** + * Option -p: destination peer identity for checking message-ids with + */ +static char *input_peer_id; + +/** + * Option -p: destination peer identity for checking message-ids with + */ +static char *input_session_key; + +/** + * Option -e: vector to calculate a scalarproduct with + */ +static char *input_elements; + +/** + * Global return value + */ +static int ret = -1; + +/** + * our Scalarproduct Computation handle + */ +static struct GNUNET_SCALARPRODUCT_ComputationHandle *computation; + + +/** + * Callback called if we are initiating a new computation session + * + * @param cls unused + * @param status if our job was successfully processed + */ +static void +responder_callback (void *cls, + enum GNUNET_SCALARPRODUCT_ResponseStatus status) +{ + switch (status) + { + case GNUNET_SCALARPRODUCT_STATUS_SUCCESS: + ret = 0; + LOG (GNUNET_ERROR_TYPE_INFO, + "Session %s concluded.\n", + GNUNET_h2s (&session_key)); + break; + + case GNUNET_SCALARPRODUCT_STATUS_INVALID_RESPONSE: + LOG (GNUNET_ERROR_TYPE_ERROR, + "Session %s failed: invalid response\n", + GNUNET_h2s (&session_key)); + break; + + case GNUNET_SCALARPRODUCT_STATUS_FAILURE: + LOG (GNUNET_ERROR_TYPE_ERROR, + "Session %s failed: service failure\n", + GNUNET_h2s (&session_key)); + break; + + case GNUNET_SCALARPRODUCT_STATUS_DISCONNECTED: + LOG (GNUNET_ERROR_TYPE_ERROR, + "Session %s failed: service disconnect!\n", + GNUNET_h2s (&session_key)); + break; + + default: + LOG (GNUNET_ERROR_TYPE_ERROR, + "Session %s failed: return code %d\n", + GNUNET_h2s (&session_key), + status); + } + computation = NULL; + GNUNET_SCHEDULER_shutdown (); +} + + +/** + * Callback called if we are initiating a new computation session + * + * @param cls unused + * @param status if our job was successfully processed + * @param result the result in gnu/gcry MPI format + */ +static void +requester_callback (void *cls, + enum GNUNET_SCALARPRODUCT_ResponseStatus status, + gcry_mpi_t result) +{ + unsigned char *buf; + gcry_error_t rc; + + switch (status) + { + case GNUNET_SCALARPRODUCT_STATUS_SUCCESS: + if (0 == (rc = gcry_mpi_aprint (GCRYMPI_FMT_HEX, &buf, NULL, result))) + { + ret = 0; + fprintf (stdout, + "%s\n", + buf); + fflush (stdout); + } + else + LOG_GCRY (GNUNET_ERROR_TYPE_ERROR, + "gcry_mpi_aprint", + rc); + break; + + case GNUNET_SCALARPRODUCT_STATUS_INVALID_RESPONSE: + LOG (GNUNET_ERROR_TYPE_ERROR, + "Session %s with peer %s failed: invalid response received\n", + GNUNET_h2s (&session_key), + GNUNET_i2s (&peer_id)); + break; + + case GNUNET_SCALARPRODUCT_STATUS_FAILURE: + LOG (GNUNET_ERROR_TYPE_ERROR, + "Session %s with peer %s failed: API failure\n", + GNUNET_h2s (&session_key), + GNUNET_i2s (&peer_id)); + break; + + case GNUNET_SCALARPRODUCT_STATUS_DISCONNECTED: + LOG (GNUNET_ERROR_TYPE_ERROR, + "Session %s with peer %s was disconnected from service.\n", + GNUNET_h2s (&session_key), + GNUNET_i2s (&peer_id)); + break; + + default: + LOG (GNUNET_ERROR_TYPE_ERROR, + "Session %s with peer %s failed: return code %d\n", + GNUNET_h2s (&session_key), + GNUNET_i2s (&peer_id), + status); + } + computation = NULL; + GNUNET_SCHEDULER_shutdown (); +} + + +/** + * Task run during shutdown. + * + * @param cls unused + */ +static void +shutdown_task (void *cls) +{ + if (NULL != computation) + { + GNUNET_SCALARPRODUCT_cancel (computation); + ret = 1; /* aborted */ + } +} + + +/** + * Main function that will be run by the scheduler. + * + * @param cls closure + * @param args remaining command-line arguments + * @param cfgfile name of the configuration file used (for saving, can be NULL!) + * @param cfg configuration + */ +static void +run (void *cls, + char *const *args, + const char *cfgfile, + const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + char *begin = input_elements; + char *end; + unsigned int i; + struct GNUNET_SCALARPRODUCT_Element *elements; + uint32_t element_count = 0; + + if (NULL == input_elements) + { + LOG (GNUNET_ERROR_TYPE_ERROR, + _ ("You must specify at least one message ID to check!\n")); + return; + } + if ((NULL == input_session_key) || + (0 == strlen (input_session_key))) + { + LOG (GNUNET_ERROR_TYPE_ERROR, + _ ( + "This program needs a session identifier for comparing vectors.\n")); + return; + } + GNUNET_CRYPTO_hash (input_session_key, + strlen (input_session_key), + &session_key); + if ((NULL != input_peer_id) && + (GNUNET_OK != + GNUNET_CRYPTO_eddsa_public_key_from_string (input_peer_id, + strlen (input_peer_id), + &peer_id.public_key))) + { + LOG (GNUNET_ERROR_TYPE_ERROR, + _ ("Tried to set initiator mode, as peer ID was given. " + "However, `%s' is not a valid peer identifier.\n"), + input_peer_id); + return; + } + if (('\'' == *begin) && + ('\'' == begin[strlen (begin) - 1])) + { + begin[strlen (begin) - 1] = '\0'; + if (strlen (begin) > 0) + begin++; + } + for (end = begin; 0 != *end; end++) + if (*end == ';') + element_count++; + if (0 == element_count) + { + LOG (GNUNET_ERROR_TYPE_ERROR, + _ ("Need elements to compute the scalarproduct, got none.\n")); + return; + } + + elements = GNUNET_malloc (sizeof(struct GNUNET_SCALARPRODUCT_Element) + * element_count); + + for (i = 0; i < element_count; i++) + { + struct GNUNET_SCALARPRODUCT_Element element; + char*separator = NULL; + + /* get the length of the current key,value; tuple */ + for (end = begin; *end != ';'; end++) + if (*end == ',') + separator = end; + + /* final element */ + if ((NULL == separator) || + (begin == separator) || + (separator == end - 1)) + { + LOG (GNUNET_ERROR_TYPE_ERROR, + _ ("Malformed input, could not parse `%s'\n"), + begin); + GNUNET_free (elements); + return; + } + *separator = 0; + /* read the element's key */ + GNUNET_CRYPTO_hash (begin, + strlen (begin), + &element.key); + + /* read the element's value */ + if (1 != + sscanf (separator + 1, + "%" SCNd64 ";", + &element.value)) + { + LOG (GNUNET_ERROR_TYPE_ERROR, + _ ("Could not convert `%s' to int64_t.\n"), + begin); + GNUNET_free (elements); + return; + } + element.value = GNUNET_htonll (element.value); + elements[i] = element; + begin = end + 1; + } + + if (((NULL != input_peer_id) && + (NULL == (computation + = GNUNET_SCALARPRODUCT_start_computation (cfg, + &session_key, + &peer_id, + elements, + element_count, + &requester_callback, + NULL)))) || + ((NULL == input_peer_id) && + (NULL == (computation + = GNUNET_SCALARPRODUCT_accept_computation (cfg, + &session_key, + elements, + element_count, + & + responder_callback, + NULL))))) + { + fprintf (stderr, + _ ("Failed to initiate computation, were all keys unique?\n")); + GNUNET_free (elements); + return; + } + GNUNET_free (elements); + GNUNET_SCHEDULER_add_shutdown (&shutdown_task, + NULL); + ret = 0; +} + + +/** + * The main function to the scalarproduct client. + * + * @param argc number of arguments from the command line + * @param argv command line arguments + * @return 0 ok, 1 on error + */ +int +main (int argc, char *const *argv) +{ + struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_option_string ('e', + "elements", + "\"key1,val1;key2,val2;...,keyn,valn;\"", + gettext_noop ( + "A comma separated list of elements to compare as vector with our remote peer."), + &input_elements), + + GNUNET_GETOPT_option_string ('e', + "elements", + "\"key1,val1;key2,val2;...,keyn,valn;\"", + gettext_noop ( + "A comma separated list of elements to compare as vector with our remote peer."), + &input_elements), + + GNUNET_GETOPT_option_string ('p', + "peer", + "PEERID", + gettext_noop ( + "[Optional] peer to calculate our scalarproduct with. If this parameter is not given, the service will wait for a remote peer to compute the request."), + &input_peer_id), + + GNUNET_GETOPT_option_string ('k', + "key", + "TRANSACTION_ID", + gettext_noop ( + "Transaction ID shared with peer."), + &input_session_key), + + GNUNET_GETOPT_OPTION_END + }; + + return (GNUNET_OK == + GNUNET_PROGRAM_run (argc, + argv, + "gnunet-scalarproduct", + gettext_noop ( + "Calculate the Vectorproduct with a GNUnet peer."), + options, &run, NULL)) ? ret : 1; +} + + +/* end of gnunet-scalarproduct.c */ diff --git a/src/contrib/service/scalarproduct/gnunet-service-scalarproduct-ecc.h b/src/contrib/service/scalarproduct/gnunet-service-scalarproduct-ecc.h new file mode 100644 index 000000000..5c7a8d8f1 --- /dev/null +++ b/src/contrib/service/scalarproduct/gnunet-service-scalarproduct-ecc.h @@ -0,0 +1,118 @@ +/* + This file is part of GNUnet. + Copyright (C) 2015 GNUnet e.V. + + GNUnet is free software: you can redistribute it and/or modify it + under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ +/** + * @file scalarproduct/gnunet-service-scalarproduct-ecc.h + * @brief scalarproduct service P2P messages + * @author Christian M. Fuchs + * @author Christian Grothoff + */ +#ifndef GNUNET_SERVICE_SCALARPRODUCT_ECC_H +#define GNUNET_SERVICE_SCALARPRODUCT_ECC_H + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Message type passed from requesting service Alice to responding + * service Bob to initiate a request and make Bob participate in our + * protocol. Afterwards, Bob is expected to perform the set + * intersection with Alice. Once that has succeeded, Alice will + * send a `struct AliceCryptodataMessage *`. Bob is not expected + * to respond via CADET in the meantime. + */ +struct EccServiceRequestMessage +{ + /** + * Type is #GNUNET_MESSAGE_TYPE_SCALARPRODUCT_ECC_SESSION_INITIALIZATION + */ + struct GNUNET_MessageHeader header; + + /** + * For alignment. Always zero. + */ + uint32_t reserved; + + /** + * The transaction/session key used to identify a session + */ + struct GNUNET_HashCode session_id; +}; + + +/** + * Vector of ECC-encrypted values sent by Alice to Bob + * (after set intersection). Alice may send messages of this + * type repeatedly to transmit all values. + */ +struct EccAliceCryptodataMessage +{ + /** + * Type is #GNUNET_MESSAGE_TYPE_SCALARPRODUCT_ECC_ALICE_CRYPTODATA + */ + struct GNUNET_MessageHeader header; + + /** + * How many elements we appended to this message? In NBO. + */ + uint32_t contained_element_count GNUNET_PACKED; + + /** + * struct GNUNET_CRYPTO_EccPoint[contained_element_count] + */ +}; + + +/** + * Message type passed from responding service Bob to responding + * service Alice to complete a request and allow Alice to compute the + * result. If Bob's reply does not fit into this one message, the + * conversation may be continued with `struct BobCryptodataMultipartMessage` + * messages afterwards. + */ +struct EccBobCryptodataMessage +{ + /** + * GNUNET message header with type + * #GNUNET_MESSAGE_TYPE_SCALARPRODUCT_ECC_BOB_CRYPTODATA. + */ + struct GNUNET_MessageHeader header; + + /** + * How many elements this individual message delivers (in NBO), + * always TWO. + */ + uint32_t contained_element_count GNUNET_PACKED; + + /** + * The product of the g_i^{b_i} values. + */ + struct GNUNET_CRYPTO_EccPoint prod_g_i_b_i; + + /** + * The product of the h_i^{b_i} values. + */ + struct GNUNET_CRYPTO_EccPoint prod_h_i_b_i; +}; + + +GNUNET_NETWORK_STRUCT_END + + +#endif diff --git a/src/contrib/service/scalarproduct/gnunet-service-scalarproduct-ecc_alice.c b/src/contrib/service/scalarproduct/gnunet-service-scalarproduct-ecc_alice.c new file mode 100644 index 000000000..b8bac0803 --- /dev/null +++ b/src/contrib/service/scalarproduct/gnunet-service-scalarproduct-ecc_alice.c @@ -0,0 +1,1150 @@ +/* + This file is part of GNUnet. + Copyright (C) 2013-2017, 2021 GNUnet e.V. + + GNUnet is free software: you can redistribute it and/or modify it + under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ +/** + * @file scalarproduct/gnunet-service-scalarproduct-ecc_alice.c + * @brief scalarproduct service implementation + * @author Christian M. Fuchs + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#include "gnunet_util_lib.h" +#include "gnunet_core_service.h" +#include "gnunet_cadet_service.h" +#include "gnunet_applications.h" +#include "gnunet_protocols.h" +#include "gnunet_scalarproduct_service.h" +#include "gnunet_seti_service.h" +#include "scalarproduct.h" +#include "gnunet-service-scalarproduct-ecc.h" +#include "gnunet_constants.h" + +#define LOG(kind, ...) \ + GNUNET_log_from (kind, "scalarproduct-alice", __VA_ARGS__) + +/** + * Maximum allowed result value for the scalarproduct computation. + * DLOG will fail if the result is bigger. At 1 million, the + * precomputation takes about 2s on a fast machine. + */ +#define MAX_RESULT (1024 * 1024) + +/** + * How many values should DLOG store in memory (determines baseline + * RAM consumption, roughly 100 bytes times the value given here). + * Should be about SQRT (MAX_RESULT), larger values will make the + * online computation faster. + */ +#define MAX_RAM (1024) + +/** + * An encrypted element key-value pair. + */ +struct MpiElement +{ + /** + * Key used to identify matching pairs of values to multiply. + * Points into an existing data structure, to avoid copying + * and doubling memory use. + */ + const struct GNUNET_HashCode *key; + + /** + * a_i value, not disclosed to Bob. + */ + int64_t value; +}; + + +/** + * A scalarproduct session which tracks + * a request form the client to our final response. + */ +struct AliceServiceSession +{ + /** + * (hopefully) unique transaction ID + */ + struct GNUNET_HashCode session_id; + + /** + * Alice or Bob's peerID + */ + struct GNUNET_PeerIdentity peer; + + /** + * The client this request is related to. + */ + struct GNUNET_SERVICE_Client *client; + + /** + * The message queue for the client. + */ + struct GNUNET_MQ_Handle *client_mq; + + /** + * The message queue for CADET. + */ + struct GNUNET_MQ_Handle *cadet_mq; + + /** + * all non-0-value'd elements transmitted to us. + * Values are of type `struct GNUNET_SCALARPRODUCT_Element *` + */ + struct GNUNET_CONTAINER_MultiHashMap *intersected_elements; + + /** + * Set of elements for which will conduction an intersection. + * the resulting elements are then used for computing the scalar product. + */ + struct GNUNET_SETI_Handle *intersection_set; + + /** + * Set of elements for which will conduction an intersection. + * the resulting elements are then used for computing the scalar product. + */ + struct GNUNET_SETI_OperationHandle *intersection_op; + + /** + * Handle to Alice's Intersection operation listening for Bob + */ + struct GNUNET_SETI_ListenHandle *intersection_listen; + + /** + * channel-handle associated with our cadet handle + */ + struct GNUNET_CADET_Channel *channel; + + /** + * a(Alice), sorted array by key of length @e used_element_count. + */ + struct MpiElement *sorted_elements; + + /** + * The computed scalar product. INT_MAX if the computation failed. + */ + int product; + + /** + * How many elements we were supplied with from the client (total + * count before intersection). + */ + uint32_t total; + + /** + * How many elements actually are used for the scalar product. + * Size of the arrays in @e r and @e r_prime. Sometimes also + * reset to 0 and used as a counter! + */ + uint32_t used_element_count; + + /** + * Already transferred elements from client to us. + * Less or equal than @e total. + */ + uint32_t client_received_element_count; + + /** + * State of this session. In + * #GNUNET_SCALARPRODUCT_STATUS_ACTIVE while operation is + * ongoing, afterwards in #GNUNET_SCALARPRODUCT_STATUS_SUCCESS or + * #GNUNET_SCALARPRODUCT_STATUS_FAILURE. + */ + enum GNUNET_SCALARPRODUCT_ResponseStatus status; + + /** + * Flag to prevent recursive calls to #destroy_service_session() from + * doing harm. + */ + int in_destroy; +}; + + +/** + * GNUnet configuration handle + */ +static const struct GNUNET_CONFIGURATION_Handle *cfg; + +/** + * Context for DLOG operations on a curve. + */ +static struct GNUNET_CRYPTO_EccDlogContext *edc; + +/** + * Alice's private key ('a'). + */ +static struct GNUNET_CRYPTO_EccScalar my_privkey; + +/** + * Inverse of Alice's private key ('a_inv'). + */ +static struct GNUNET_CRYPTO_EccScalar my_privkey_inv; + +/** + * Handle to the CADET service. + */ +static struct GNUNET_CADET_Handle *my_cadet; + + +/** + * Iterator called to free elements. + * + * @param cls the `struct AliceServiceSession *` (unused) + * @param key the key (unused) + * @param value value to free + * @return #GNUNET_OK (continue to iterate) + */ +static int +free_element_cb (void *cls, + const struct GNUNET_HashCode *key, + void *value) +{ + struct GNUNET_SCALARPRODUCT_Element *e = value; + + GNUNET_free (e); + return GNUNET_OK; +} + + +/** + * Destroy session state, we are done with it. + * + * @param s the session to free elements from + */ +static void +destroy_service_session (struct AliceServiceSession *s) +{ + if (GNUNET_YES == s->in_destroy) + return; + s->in_destroy = GNUNET_YES; + if (NULL != s->client) + { + struct GNUNET_SERVICE_Client *c = s->client; + + s->client = NULL; + GNUNET_SERVICE_client_drop (c); + } + if (NULL != s->channel) + { + GNUNET_CADET_channel_destroy (s->channel); + s->channel = NULL; + } + if (NULL != s->intersected_elements) + { + GNUNET_CONTAINER_multihashmap_iterate (s->intersected_elements, + &free_element_cb, + s); + GNUNET_CONTAINER_multihashmap_destroy (s->intersected_elements); + s->intersected_elements = NULL; + } + if (NULL != s->intersection_listen) + { + GNUNET_SETI_listen_cancel (s->intersection_listen); + s->intersection_listen = NULL; + } + if (NULL != s->intersection_op) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Set intersection, op still ongoing!\n"); + GNUNET_SETI_operation_cancel (s->intersection_op); + s->intersection_op = NULL; + } + if (NULL != s->intersection_set) + { + GNUNET_SETI_destroy (s->intersection_set); + s->intersection_set = NULL; + } + if (NULL != s->sorted_elements) + { + GNUNET_free (s->sorted_elements); + s->sorted_elements = NULL; + } + GNUNET_free (s); +} + + +/** + * Notify the client that the session has failed. A message gets sent + * to Alice's client if we encountered any error. + * + * @param session the associated client session to fail or succeed + */ +static void +prepare_client_end_notification (struct AliceServiceSession *session) +{ + struct ClientResponseMessage *msg; + struct GNUNET_MQ_Envelope *e; + + if (NULL == session->client_mq) + return; /* no client left to be notified */ + GNUNET_log ( + GNUNET_ERROR_TYPE_DEBUG, + "Sending session-end notification with status %d to client for session %s\n", + session->status, + GNUNET_h2s (&session->session_id)); + e = GNUNET_MQ_msg (msg, + GNUNET_MESSAGE_TYPE_SCALARPRODUCT_RESULT); + msg->product_length = htonl (0); + msg->status = htonl (session->status); + GNUNET_MQ_send (session->client_mq, + e); +} + + +/** + * Prepare the final (positive) response we will send to Alice's + * client. + * + * @param s the session associated with our client. + */ +static void +transmit_client_response (struct AliceServiceSession *s) +{ + struct ClientResponseMessage *msg; + struct GNUNET_MQ_Envelope *e; + unsigned char *product_exported = NULL; + size_t product_length = 0; + int32_t range; + gcry_error_t rc; + gcry_mpi_t value; + + if (INT_MAX == s->product) + { + GNUNET_break (0); + prepare_client_end_notification (s); + return; + } + value = gcry_mpi_new (32); + if (0 > s->product) + { + range = -1; + gcry_mpi_set_ui (value, + -s->product); + } + else if (0 < s->product) + { + range = 1; + gcry_mpi_set_ui (value, + s->product); + } + else + { + /* result is exactly zero */ + range = 0; + } + if ( (0 != range) && + (0 != (rc = gcry_mpi_aprint (GCRYMPI_FMT_STD, + &product_exported, + &product_length, + value)))) + { + LOG_GCRY (GNUNET_ERROR_TYPE_ERROR, + "gcry_mpi_scan", + rc); + prepare_client_end_notification (s); + return; + } + gcry_mpi_release (value); + e = GNUNET_MQ_msg_extra (msg, + product_length, + GNUNET_MESSAGE_TYPE_SCALARPRODUCT_RESULT); + msg->status = htonl (GNUNET_SCALARPRODUCT_STATUS_SUCCESS); + msg->range = htonl (range); + msg->product_length = htonl (product_length); + if (NULL != product_exported) + { + GNUNET_memcpy (&msg[1], + product_exported, + product_length); + GNUNET_free (product_exported); + } + GNUNET_MQ_send (s->client_mq, + e); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Sent result to client, session %s has ended!\n", + GNUNET_h2s (&s->session_id)); +} + + +/** + * Function called whenever a channel is destroyed. Should clean up + * any associated state. + * + * It must NOT call #GNUNET_CADET_channel_destroy() on the channel. + * + * @param cls the `struct AliceServiceSession` + * @param channel connection to the other end (henceforth invalid) + */ +static void +cb_channel_destruction (void *cls, + const struct GNUNET_CADET_Channel *channel) +{ + struct AliceServiceSession *s = cls; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Peer disconnected, terminating session %s with peer %s\n", + GNUNET_h2s (&s->session_id), + GNUNET_i2s (&s->peer)); + s->channel = NULL; + if (GNUNET_SCALARPRODUCT_STATUS_ACTIVE == s->status) + { + /* We didn't get an answer yet, fail with error */ + s->status = GNUNET_SCALARPRODUCT_STATUS_FAILURE; + prepare_client_end_notification (s); + } +} + + +/** + * Handle a response we got from another service we wanted to + * calculate a scalarproduct with. + * + * @param cls the `struct AliceServiceSession *` + * @param msg the actual message + */ +static void +handle_bobs_cryptodata_message (void *cls, + const struct EccBobCryptodataMessage *msg) +{ + struct AliceServiceSession *s = cls; + uint32_t contained; + + contained = ntohl (msg->contained_element_count); + if (2 != contained) + { + GNUNET_break_op (0); + destroy_service_session (s); + return; + } + if (NULL == s->sorted_elements) + { + /* we're not ready yet, how can Bob be? */ + GNUNET_break_op (0); + destroy_service_session (s); + return; + } + if (s->total != s->client_received_element_count) + { + /* we're not ready yet, how can Bob be? */ + GNUNET_break_op (0); + destroy_service_session (s); + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received %u crypto values from Bob\n", + (unsigned int) contained); + GNUNET_CADET_receive_done (s->channel); + { + struct GNUNET_CRYPTO_EccPoint g_i_b_i_a_inv; + struct GNUNET_CRYPTO_EccPoint g_ai_bi; + + GNUNET_assert ( + GNUNET_OK == + GNUNET_CRYPTO_ecc_pmul_mpi (&msg->prod_g_i_b_i, + &my_privkey_inv, + &g_i_b_i_a_inv)); + GNUNET_assert ( + GNUNET_OK == + GNUNET_CRYPTO_ecc_add (&g_i_b_i_a_inv, + &msg->prod_h_i_b_i, + &g_ai_bi)); + s->product = GNUNET_CRYPTO_ecc_dlog (edc, + &g_ai_bi); + if (INT_MAX == s->product) + { + /* result too big */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Scalar product result out of range\n"); + } + } + transmit_client_response (s); +} + + +/** + * Iterator to copy over messages from the hash map + * into an array for sorting. + * + * @param cls the `struct AliceServiceSession *` + * @param key the key (unused) + * @param value the `struct GNUNET_SCALARPRODUCT_Element *` + */ +static int +copy_element_cb (void *cls, + const struct GNUNET_HashCode *key, + void *value) +{ + struct AliceServiceSession *s = cls; + struct GNUNET_SCALARPRODUCT_Element *e = value; + + s->sorted_elements[s->used_element_count].value = (int64_t) GNUNET_ntohll ( + e->value); + s->sorted_elements[s->used_element_count].key = &e->key; + s->used_element_count++; + return GNUNET_OK; +} + + +/** + * Compare two `struct MpiValue`s by key for sorting. + * + * @param a pointer to first `struct MpiValue *` + * @param b pointer to first `struct MpiValue *` + * @return -1 for a < b, 0 for a=b, 1 for a > b. + */ +static int +element_cmp (const void *a, + const void *b) +{ + const struct MpiElement *ma = a; + const struct MpiElement *mb = b; + + return GNUNET_CRYPTO_hash_cmp (ma->key, + mb->key); +} + + +/** + * Maximum number of elements we can put into a single cryptodata + * message + */ +#define ELEMENT_CAPACITY \ + ((GNUNET_CONSTANTS_MAX_CADET_MESSAGE_SIZE - 1 \ + - sizeof(struct EccAliceCryptodataMessage)) \ + / sizeof(struct GNUNET_CRYPTO_EccPoint)) + + +/** + * Send the cryptographic data from Alice to Bob. + * Does nothing if we already transferred all elements. + * + * @param s the associated service session + */ +static void +send_alices_cryptodata_message (struct AliceServiceSession *s) +{ + struct EccAliceCryptodataMessage *msg; + struct GNUNET_MQ_Envelope *e; + struct GNUNET_CRYPTO_EccPoint *payload; + struct GNUNET_CRYPTO_EccScalar r_ia; + struct GNUNET_CRYPTO_EccScalar r_ia_ai; + unsigned int off; + unsigned int todo_count; + + s->sorted_elements = GNUNET_new_array (GNUNET_CONTAINER_multihashmap_size ( + s->intersected_elements), + struct MpiElement); + s->used_element_count = 0; + GNUNET_CONTAINER_multihashmap_iterate (s->intersected_elements, + ©_element_cb, + s); + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Finished intersection, %d items remain\n", + s->used_element_count); + qsort (s->sorted_elements, + s->used_element_count, + sizeof(struct MpiElement), + &element_cmp); + off = 0; + while (off < s->used_element_count) + { + todo_count = s->used_element_count - off; + if (todo_count > ELEMENT_CAPACITY) + todo_count = ELEMENT_CAPACITY; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Sending %u/%u crypto values to Bob\n", + (unsigned int) todo_count, + (unsigned int) s->used_element_count); + + e = + GNUNET_MQ_msg_extra (msg, + todo_count * 2 + * sizeof(struct GNUNET_CRYPTO_EccPoint), + GNUNET_MESSAGE_TYPE_SCALARPRODUCT_ECC_ALICE_CRYPTODATA); + msg->contained_element_count = htonl (todo_count); + payload = (struct GNUNET_CRYPTO_EccPoint *) &msg[1]; + for (unsigned int i = off; i < off + todo_count; i++) + { + struct GNUNET_CRYPTO_EccScalar r_i; + struct GNUNET_CRYPTO_EccPoint g_i; + struct GNUNET_CRYPTO_EccPoint h_i; + + /* r_i = random() mod n */ + GNUNET_CRYPTO_ecc_random_mod_n (&r_i); + /* g_i = g^{r_i} */ + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_ecc_dexp_mpi (&r_i, + &g_i)); + /* r_ia = r_i * a */ + crypto_core_ed25519_scalar_mul (r_ia.v, + r_i.v, + my_privkey.v); + /* r_ia_ai = r_ia + a_i */ + { + int64_t val = s->sorted_elements[i].value; + struct GNUNET_CRYPTO_EccScalar vali; + + GNUNET_assert (INT64_MIN != val); + GNUNET_CRYPTO_ecc_scalar_from_int (val > 0 ? val : -val, + &vali); + if (val > 0) + crypto_core_ed25519_scalar_add (r_ia_ai.v, + r_ia.v, + vali.v); + else + crypto_core_ed25519_scalar_sub (r_ia_ai.v, + r_ia.v, + vali.v); + } + /* h_i = g^{r_ia_ai} */ + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_ecc_dexp_mpi (&r_ia_ai, + &h_i)); + memcpy (&payload[(i - off) * 2], + &g_i, + sizeof (g_i)); + memcpy (&payload[(i - off) * 2 + 1], + &h_i, + sizeof (h_i)); + } + off += todo_count; + GNUNET_MQ_send (s->cadet_mq, + e); + } +} + + +/** + * Callback for set operation results. Called for each element + * that should be removed from the result set, and then once + * to indicate that the set intersection operation is done. + * + * @param cls closure with the `struct AliceServiceSession` + * @param element a result element, only valid if status is #GNUNET_SETI_STATUS_OK + * @param current_size current set size + * @param status what has happened with the set intersection? + */ +static void +cb_intersection_element_removed (void *cls, + const struct GNUNET_SETI_Element *element, + uint64_t current_size, + enum GNUNET_SETI_Status status) +{ + struct AliceServiceSession *s = cls; + struct GNUNET_SCALARPRODUCT_Element *se; + + switch (status) + { + case GNUNET_SETI_STATUS_DEL_LOCAL: + /* this element has been removed from the set */ + se = GNUNET_CONTAINER_multihashmap_get (s->intersected_elements, + element->data); + GNUNET_assert (NULL != se); + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Intersection removed element with key %s and value %lld\n", + GNUNET_h2s (&se->key), + (long long) GNUNET_ntohll (se->value)); + GNUNET_assert ( + GNUNET_YES == + GNUNET_CONTAINER_multihashmap_remove (s->intersected_elements, + element->data, + se)); + GNUNET_free (se); + return; + case GNUNET_SETI_STATUS_DONE: + s->intersection_op = NULL; + if (NULL != s->intersection_set) + { + GNUNET_SETI_destroy (s->intersection_set); + s->intersection_set = NULL; + } + send_alices_cryptodata_message (s); + return; + case GNUNET_SETI_STATUS_FAILURE: + /* unhandled status code */ + LOG (GNUNET_ERROR_TYPE_DEBUG, "Set intersection failed!\n"); + if (NULL != s->intersection_listen) + { + GNUNET_SETI_listen_cancel (s->intersection_listen); + s->intersection_listen = NULL; + } + s->intersection_op = NULL; + if (NULL != s->intersection_set) + { + GNUNET_SETI_destroy (s->intersection_set); + s->intersection_set = NULL; + } + s->status = GNUNET_SCALARPRODUCT_STATUS_FAILURE; + prepare_client_end_notification (s); + return; + + default: + GNUNET_break (0); + return; + } +} + + +/** + * Called when another peer wants to do a set operation with the + * local peer. If a listen error occurs, the @a request is NULL. + * + * @param cls closure with the `struct AliceServiceSession *` + * @param other_peer the other peer + * @param context_msg message with application specific information from + * the other peer + * @param request request from the other peer (never NULL), use GNUNET_SETI_accept() + * to accept it, otherwise the request will be refused + * Note that we can't just return value from the listen callback, + * as it is also necessary to specify the set we want to do the + * operation with, which sometimes can be derived from the context + * message. It's necessary to specify the timeout. + */ +static void +cb_intersection_request_alice (void *cls, + const struct GNUNET_PeerIdentity *other_peer, + const struct GNUNET_MessageHeader *context_msg, + struct GNUNET_SETI_Request *request) +{ + struct AliceServiceSession *s = cls; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received intersection request from %s!\n", + GNUNET_i2s (other_peer)); + if (0 != GNUNET_memcmp (other_peer, + &s->peer)) + { + GNUNET_break_op (0); + return; + } + s->intersection_op + = GNUNET_SETI_accept (request, + (struct GNUNET_SETI_Option[]){ { 0 } }, + &cb_intersection_element_removed, + s); + if (NULL == s->intersection_op) + { + GNUNET_break (0); + s->status = GNUNET_SCALARPRODUCT_STATUS_FAILURE; + prepare_client_end_notification (s); + return; + } + if (GNUNET_OK != + GNUNET_SETI_commit (s->intersection_op, + s->intersection_set)) + { + GNUNET_break (0); + s->status = GNUNET_SCALARPRODUCT_STATUS_FAILURE; + prepare_client_end_notification (s); + return; + } +} + + +/** + * Our client has finished sending us its multipart message. + * + * @param s the service session context + */ +static void +client_request_complete_alice (struct AliceServiceSession *s) +{ + struct GNUNET_MQ_MessageHandler cadet_handlers[] = { + GNUNET_MQ_hd_fixed_size (bobs_cryptodata_message, + GNUNET_MESSAGE_TYPE_SCALARPRODUCT_ECC_BOB_CRYPTODATA, + struct EccBobCryptodataMessage, + s), + GNUNET_MQ_handler_end () + }; + struct EccServiceRequestMessage *msg; + struct GNUNET_MQ_Envelope *e; + struct GNUNET_HashCode set_sid; + + GNUNET_CRYPTO_hash (&s->session_id, + sizeof(struct GNUNET_HashCode), + &set_sid); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Creating new channel for session with key %s.\n", + GNUNET_h2s (&s->session_id)); + s->channel = GNUNET_CADET_channel_create (my_cadet, + s, + &s->peer, + &s->session_id, + NULL, + &cb_channel_destruction, + cadet_handlers); + if (NULL == s->channel) + { + s->status = GNUNET_SCALARPRODUCT_STATUS_FAILURE; + prepare_client_end_notification (s); + return; + } + s->cadet_mq = GNUNET_CADET_get_mq (s->channel); + s->intersection_listen = GNUNET_SETI_listen (cfg, + &set_sid, + &cb_intersection_request_alice, + s); + if (NULL == s->intersection_listen) + { + s->status = GNUNET_SCALARPRODUCT_STATUS_FAILURE; + GNUNET_CADET_channel_destroy (s->channel); + s->channel = NULL; + prepare_client_end_notification (s); + return; + } + + e = + GNUNET_MQ_msg (msg, + GNUNET_MESSAGE_TYPE_SCALARPRODUCT_ECC_SESSION_INITIALIZATION); + GNUNET_MQ_env_set_options (e, GNUNET_MQ_PRIO_CRITICAL_CONTROL); + msg->session_id = s->session_id; + GNUNET_MQ_send (s->cadet_mq, e); +} + + +/** + * We're receiving additional set data. Check if + * @a msg is well-formed. + * + * @param cls client identification of the client + * @param msg the actual message + * @return #GNUNET_OK if @a msg is well-formed + */ +static int +check_alice_client_message_multipart ( + void *cls, + const struct ComputationBobCryptodataMultipartMessage *msg) +{ + struct AliceServiceSession *s = cls; + uint32_t contained_count; + uint16_t msize; + + msize = ntohs (msg->header.size); + contained_count = ntohl (msg->element_count_contained); + if ((msize != + (sizeof(struct ComputationBobCryptodataMultipartMessage) + + contained_count * sizeof(struct GNUNET_SCALARPRODUCT_Element))) || + (0 == contained_count) || + (s->total == s->client_received_element_count) || + (s->total < s->client_received_element_count + contained_count)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * We're receiving additional set data. Add it to our + * set and if we are done, initiate the transaction. + * + * @param cls client identification of the client + * @param msg the actual message + */ +static void +handle_alice_client_message_multipart ( + void *cls, + const struct ComputationBobCryptodataMultipartMessage *msg) +{ + struct AliceServiceSession *s = cls; + uint32_t contained_count; + const struct GNUNET_SCALARPRODUCT_Element *elements; + struct GNUNET_SETI_Element set_elem; + struct GNUNET_SCALARPRODUCT_Element *elem; + + contained_count = ntohl (msg->element_count_contained); + s->client_received_element_count += contained_count; + elements = (const struct GNUNET_SCALARPRODUCT_Element *) &msg[1]; + for (uint32_t i = 0; i < contained_count; i++) + { + elem = GNUNET_new (struct GNUNET_SCALARPRODUCT_Element); + GNUNET_memcpy (elem, + &elements[i], + sizeof(struct GNUNET_SCALARPRODUCT_Element)); + if (GNUNET_SYSERR == GNUNET_CONTAINER_multihashmap_put ( + s->intersected_elements, + &elem->key, + elem, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)) + { + GNUNET_break (0); + GNUNET_free (elem); + continue; + } + set_elem.data = &elem->key; + set_elem.size = sizeof(elem->key); + set_elem.element_type = 0; + GNUNET_SETI_add_element (s->intersection_set, &set_elem, NULL, NULL); + s->used_element_count++; + } + GNUNET_SERVICE_client_continue (s->client); + if (s->total != s->client_received_element_count) + { + /* more to come */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received client multipart data, waiting for more!\n"); + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Launching computation\n"); + client_request_complete_alice (s); +} + + +/** + * Handler for Alice's client request message. + * Check that @a msg is well-formed. + * + * @param cls identification of the client + * @param msg the actual message + * @return #GNUNET_OK if @a msg is well-formed + */ +static int +check_alice_client_message (void *cls, + const struct AliceComputationMessage *msg) +{ + struct AliceServiceSession *s = cls; + uint16_t msize; + uint32_t total_count; + uint32_t contained_count; + + if (NULL != s->intersected_elements) + { + /* only one concurrent session per client connection allowed, + simplifies logic a lot... */ + GNUNET_break (0); + return GNUNET_SYSERR; + } + msize = ntohs (msg->header.size); + total_count = ntohl (msg->element_count_total); + contained_count = ntohl (msg->element_count_contained); + if ((0 == total_count) || (0 == contained_count) || + (msize != + (sizeof(struct AliceComputationMessage) + + contained_count * sizeof(struct GNUNET_SCALARPRODUCT_Element)))) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Handler for Alice's client request message. + * We are doing request-initiation to compute a scalar product with a peer. + * + * @param cls identification of the client + * @param msg the actual message + */ +static void +handle_alice_client_message (void *cls, + const struct AliceComputationMessage *msg) +{ + struct AliceServiceSession *s = cls; + uint32_t contained_count; + uint32_t total_count; + const struct GNUNET_SCALARPRODUCT_Element *elements; + struct GNUNET_SETI_Element set_elem; + struct GNUNET_SCALARPRODUCT_Element *elem; + + total_count = ntohl (msg->element_count_total); + contained_count = ntohl (msg->element_count_contained); + s->peer = msg->peer; + s->status = GNUNET_SCALARPRODUCT_STATUS_ACTIVE; + s->total = total_count; + s->client_received_element_count = contained_count; + s->session_id = msg->session_key; + elements = (const struct GNUNET_SCALARPRODUCT_Element *) &msg[1]; + s->intersected_elements = + GNUNET_CONTAINER_multihashmap_create (s->total, + GNUNET_YES); + s->intersection_set = GNUNET_SETI_create (cfg); + for (uint32_t i = 0; i < contained_count; i++) + { + if (0 == GNUNET_ntohll (elements[i].value)) + continue; + elem = GNUNET_new (struct GNUNET_SCALARPRODUCT_Element); + *elem = elements[i]; + if (GNUNET_SYSERR == + GNUNET_CONTAINER_multihashmap_put ( + s->intersected_elements, + &elem->key, + elem, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)) + { + /* element with same key encountered twice! */ + GNUNET_break (0); + GNUNET_free (elem); + continue; + } + set_elem.data = &elem->key; + set_elem.size = sizeof(elem->key); + set_elem.element_type = 0; + GNUNET_SETI_add_element (s->intersection_set, + &set_elem, + NULL, + NULL); + s->used_element_count++; + } + GNUNET_SERVICE_client_continue (s->client); + if (s->total != s->client_received_element_count) + { + /* wait for multipart msg */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received partial client request, waiting for more!\n"); + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Launching computation\n"); + client_request_complete_alice (s); +} + + +/** + * Task run during shutdown. + * + * @param cls unused + * @param tc unused + */ +static void +shutdown_task (void *cls) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Shutting down, initiating cleanup.\n"); + // FIXME: we have to cut our connections to CADET first! + if (NULL != my_cadet) + { + GNUNET_CADET_disconnect (my_cadet); + my_cadet = NULL; + } + if (NULL != edc) + { + GNUNET_CRYPTO_ecc_dlog_release (edc); + edc = NULL; + } +} + + +/** + * A client connected. + * + * Setup the associated data structure. + * + * @param cls closure, NULL + * @param client identification of the client + * @param mq message queue to communicate with @a client + * @return our `struct AliceServiceSession` + */ +static void * +client_connect_cb (void *cls, + struct GNUNET_SERVICE_Client *client, + struct GNUNET_MQ_Handle *mq) +{ + struct AliceServiceSession *s; + + s = GNUNET_new (struct AliceServiceSession); + s->client = client; + s->client_mq = mq; + return s; +} + + +/** + * A client disconnected. + * + * Remove the associated session(s), release data structures + * and cancel pending outgoing transmissions to the client. + * + * @param cls closure, NULL + * @param client identification of the client + * @param app_cls our `struct AliceServiceSession` + */ +static void +client_disconnect_cb (void *cls, + struct GNUNET_SERVICE_Client *client, + void *app_cls) +{ + struct AliceServiceSession *s = app_cls; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Client %p disconnected from us.\n", + client); + s->client = NULL; + s->client_mq = NULL; + destroy_service_session (s); +} + + +/** + * Initialization of the program and message handlers + * + * @param cls closure + * @param c configuration to use + * @param service the initialized service + */ +static void +run (void *cls, + const struct GNUNET_CONFIGURATION_Handle *c, + struct GNUNET_SERVICE_Handle *service) +{ + cfg = c; + edc = GNUNET_CRYPTO_ecc_dlog_prepare (MAX_RESULT, + MAX_RAM); + /* Select a random 'a' value for Alice */ + GNUNET_CRYPTO_ecc_rnd_mpi (&my_privkey, + &my_privkey_inv); + my_cadet = GNUNET_CADET_connect (cfg); + if (NULL == my_cadet) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _ ("Connect to CADET failed\n")); + GNUNET_SCHEDULER_shutdown (); + return; + } + GNUNET_SCHEDULER_add_shutdown (&shutdown_task, + NULL); +} + + +/** + * Define "main" method using service macro. + */ +GNUNET_SERVICE_MAIN ( + "scalarproduct-alice", + GNUNET_SERVICE_OPTION_NONE, + &run, + &client_connect_cb, + &client_disconnect_cb, + NULL, + GNUNET_MQ_hd_var_size (alice_client_message, + GNUNET_MESSAGE_TYPE_SCALARPRODUCT_CLIENT_TO_ALICE, + struct AliceComputationMessage, + NULL), + GNUNET_MQ_hd_var_size ( + alice_client_message_multipart, + GNUNET_MESSAGE_TYPE_SCALARPRODUCT_CLIENT_MULTIPART_ALICE, + struct ComputationBobCryptodataMultipartMessage, + NULL), + GNUNET_MQ_handler_end ()); + + +/* end of gnunet-service-scalarproduct-ecc_alice.c */ diff --git a/src/contrib/service/scalarproduct/gnunet-service-scalarproduct-ecc_bob.c b/src/contrib/service/scalarproduct/gnunet-service-scalarproduct-ecc_bob.c new file mode 100644 index 000000000..1945f1937 --- /dev/null +++ b/src/contrib/service/scalarproduct/gnunet-service-scalarproduct-ecc_bob.c @@ -0,0 +1,1060 @@ +/* + This file is part of GNUnet. + Copyright (C) 2013-2017, 2021 GNUnet e.V. + + GNUnet is free software: you can redistribute it and/or modify it + under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ +/** + * @file scalarproduct/gnunet-service-scalarproduct-ecc_bob.c + * @brief scalarproduct service implementation + * @author Christian M. Fuchs + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#include "gnunet_util_lib.h" +#include "gnunet_core_service.h" +#include "gnunet_cadet_service.h" +#include "gnunet_applications.h" +#include "gnunet_protocols.h" +#include "gnunet_scalarproduct_service.h" +#include "gnunet_seti_service.h" +#include "scalarproduct.h" +#include "gnunet-service-scalarproduct-ecc.h" + +#define LOG(kind, ...) GNUNET_log_from (kind, "scalarproduct-bob", __VA_ARGS__) + + +/** + * An encrypted element key-value pair. + */ +struct MpiElement +{ + /** + * Key used to identify matching pairs of values to multiply. + * Points into an existing data structure, to avoid copying + * and doubling memory use. + */ + const struct GNUNET_HashCode *key; + + /** + * Value represented (a). + */ + int64_t value; +}; + + +/** + * A scalarproduct session which tracks an offer for a + * multiplication service by a local client. + */ +struct BobServiceSession +{ + /** + * The client this request is related to. + */ + struct GNUNET_SERVICE_Client *client; + + /** + * Client message queue. + */ + struct GNUNET_MQ_Handle *client_mq; + + /** + * All non-0-value'd elements transmitted to us. + */ + struct GNUNET_CONTAINER_MultiHashMap *intersected_elements; + + /** + * Set of elements for which we will be conducting an intersection. + * The resulting elements are then used for computing the scalar product. + */ + struct GNUNET_SETI_Handle *intersection_set; + + /** + * Set of elements for which will conduction an intersection. + * the resulting elements are then used for computing the scalar product. + */ + struct GNUNET_SETI_OperationHandle *intersection_op; + + /** + * Our open port. + */ + struct GNUNET_CADET_Port *port; + + /** + * b(Bob) + */ + struct MpiElement *sorted_elements; + + /** + * Product of the g_i^{b_i} + */ + struct GNUNET_CRYPTO_EccPoint prod_g_i_b_i; + + /** + * Product of the h_i^{b_i} + */ + struct GNUNET_CRYPTO_EccPoint prod_h_i_b_i; + + /** + * How many elements will be supplied in total from the client. + */ + uint32_t total; + + /** + * Already transferred elements (received) for multipart + * messages from client. Always less than @e total. + */ + uint32_t client_received_element_count; + + /** + * How many elements actually are used for the scalar product. + * Size of the arrays in @e r and @e r_prime. Also sometimes + * used as an index into the arrays during construction. + */ + uint32_t used_element_count; + + /** + * Counts the number of values received from Alice by us. + * Always less than @e used_element_count. + */ + uint32_t cadet_received_element_count; + + /** + * State of this session. In + * #GNUNET_SCALARPRODUCT_STATUS_ACTIVE while operation is + * ongoing, afterwards in #GNUNET_SCALARPRODUCT_STATUS_SUCCESS or + * #GNUNET_SCALARPRODUCT_STATUS_FAILURE. + */ + enum GNUNET_SCALARPRODUCT_ResponseStatus status; + + /** + * Are we already in #destroy_service_session()? + */ + int in_destroy; + + /** + * The CADET channel. + */ + struct GNUNET_CADET_Channel *channel; + + /** + * Originator's peer identity. (Only for diagnostics.) + */ + struct GNUNET_PeerIdentity peer; + + /** + * (hopefully) unique transaction ID + */ + struct GNUNET_HashCode session_id; + + /** + * The message queue for this channel. + */ + struct GNUNET_MQ_Handle *cadet_mq; +}; + + +/** + * GNUnet configuration handle + */ +static const struct GNUNET_CONFIGURATION_Handle *cfg; + +/** + * Handle to the CADET service. + */ +static struct GNUNET_CADET_Handle *my_cadet; + +/** + * Context for DLOG operations on a curve. + */ +static struct GNUNET_CRYPTO_EccDlogContext *edc; + + +/** + * Callback used to free the elements in the map. + * + * @param cls NULL + * @param key key of the element + * @param value the value to free + */ +static int +free_element_cb (void *cls, + const struct GNUNET_HashCode *key, + void *value) +{ + struct GNUNET_SCALARPRODUCT_Element *element = value; + + GNUNET_free (element); + return GNUNET_OK; +} + + +/** + * Destroy session state, we are done with it. + * + * @param s the session to free elements from + */ +static void +destroy_service_session (struct BobServiceSession *s) +{ + if (GNUNET_YES == s->in_destroy) + return; + s->in_destroy = GNUNET_YES; + if (NULL != s->client) + { + struct GNUNET_SERVICE_Client *c = s->client; + + s->client = NULL; + GNUNET_SERVICE_client_drop (c); + } + if (NULL != s->intersected_elements) + { + GNUNET_CONTAINER_multihashmap_iterate (s->intersected_elements, + &free_element_cb, + NULL); + GNUNET_CONTAINER_multihashmap_destroy (s->intersected_elements); + s->intersected_elements = NULL; + } + if (NULL != s->intersection_op) + { + GNUNET_SETI_operation_cancel (s->intersection_op); + s->intersection_op = NULL; + } + if (NULL != s->intersection_set) + { + GNUNET_SETI_destroy (s->intersection_set); + s->intersection_set = NULL; + } + if (NULL != s->sorted_elements) + { + GNUNET_free (s->sorted_elements); + s->sorted_elements = NULL; + } + if (NULL != s->port) + { + GNUNET_CADET_close_port (s->port); + s->port = NULL; + } + if (NULL != s->channel) + { + GNUNET_CADET_channel_destroy (s->channel); + s->channel = NULL; + } + GNUNET_free (s); +} + + +/** + * Notify the client that the session has succeeded or failed. This + * message gets sent to Bob's client if the operation completed or + * Alice disconnected. + * + * @param session the associated client session to fail or succeed + */ +static void +prepare_client_end_notification (struct BobServiceSession *session) +{ + struct ClientResponseMessage *msg; + struct GNUNET_MQ_Envelope *e; + + if (NULL == session->client_mq) + return; /* no client left to be notified */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Sending session-end notification with status %d to client for session %s\n", + session->status, + GNUNET_h2s (&session->session_id)); + e = GNUNET_MQ_msg (msg, + GNUNET_MESSAGE_TYPE_SCALARPRODUCT_RESULT); + msg->range = 0; + msg->product_length = htonl (0); + msg->status = htonl (session->status); + GNUNET_MQ_send (session->client_mq, + e); +} + + +/** + * Function called whenever a channel is destroyed. Should clean up + * any associated state. + * + * It must NOT call #GNUNET_CADET_channel_destroy() on the channel. + * + * @param cls the `struct BobServiceSession` + * @param channel connection to the other end (henceforth invalid) + */ +static void +cb_channel_destruction (void *cls, + const struct GNUNET_CADET_Channel *channel) +{ + struct BobServiceSession *s = cls; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Peer disconnected, terminating session %s with peer %s\n", + GNUNET_h2s (&s->session_id), + GNUNET_i2s (&s->peer)); + s->channel = NULL; + if (GNUNET_SCALARPRODUCT_STATUS_ACTIVE == s->status) + { + s->status = GNUNET_SCALARPRODUCT_STATUS_FAILURE; + prepare_client_end_notification (s); + } + destroy_service_session (s); +} + + +/** + * MQ finished giving our last message to CADET, now notify + * the client that we are finished. + */ +static void +bob_cadet_done_cb (void *cls) +{ + struct BobServiceSession *session = cls; + + session->status = GNUNET_SCALARPRODUCT_STATUS_SUCCESS; + prepare_client_end_notification (session); +} + + +/** + * Bob generates the response message to be sent to Alice. + * + * @param s the associated requesting session with Alice + */ +static void +transmit_bobs_cryptodata_message (struct BobServiceSession *s) +{ + struct EccBobCryptodataMessage *msg; + struct GNUNET_MQ_Envelope *e; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Sending response to Alice\n"); + e = GNUNET_MQ_msg (msg, + GNUNET_MESSAGE_TYPE_SCALARPRODUCT_ECC_BOB_CRYPTODATA); + msg->contained_element_count = htonl (2); + msg->prod_g_i_b_i = s->prod_g_i_b_i; + msg->prod_h_i_b_i = s->prod_h_i_b_i; + GNUNET_MQ_notify_sent (e, + &bob_cadet_done_cb, + s); + GNUNET_MQ_send (s->cadet_mq, + e); +} + + +/** + * Iterator to copy over messages from the hash map + * into an array for sorting. + * + * @param cls the `struct AliceServiceSession *` + * @param key the key (unused) + * @param value the `struct GNUNET_SCALARPRODUCT_Element *` + */ +static int +copy_element_cb (void *cls, + const struct GNUNET_HashCode *key, + void *value) +{ + struct BobServiceSession *s = cls; + struct GNUNET_SCALARPRODUCT_Element *e = value; + + s->sorted_elements[s->used_element_count].value = (int64_t) GNUNET_ntohll ( + e->value); + s->sorted_elements[s->used_element_count].key = &e->key; + s->used_element_count++; + return GNUNET_OK; +} + + +/** + * Compare two `struct MpiValue`s by key for sorting. + * + * @param a pointer to first `struct MpiValue *` + * @param b pointer to first `struct MpiValue *` + * @return -1 for a < b, 0 for a=b, 1 for a > b. + * TODO: code duplication with Alice! + */ +static int +element_cmp (const void *a, + const void *b) +{ + const struct MpiElement *ma = a; + const struct MpiElement *mb = b; + + return GNUNET_CRYPTO_hash_cmp (ma->key, + mb->key); +} + + +/** + * Check a multipart-chunk of a request from another service to + * calculate a scalarproduct with us. + * + * @param cls closure (set from #GNUNET_CADET_connect) + * @param msg the actual message + * @return #GNUNET_OK to keep the connection open, + * #GNUNET_SYSERR to close it (signal serious error) + */ +static int +check_alices_cryptodata_message (void *cls, + const struct EccAliceCryptodataMessage *msg) +{ + struct BobServiceSession *s = cls; + uint32_t contained_elements; + size_t msg_length; + uint16_t msize; + unsigned int max; + + msize = ntohs (msg->header.size); + if (msize <= sizeof(struct EccAliceCryptodataMessage)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + contained_elements = ntohl (msg->contained_element_count); + /* Our intersection may still be ongoing, but this is nevertheless + an upper bound on the required array size */ + max = GNUNET_CONTAINER_multihashmap_size (s->intersected_elements); + msg_length = sizeof(struct EccAliceCryptodataMessage) + + contained_elements * sizeof(struct GNUNET_CRYPTO_EccPoint) * 2; + if ((msize != msg_length) || + (0 == contained_elements) || + (contained_elements > UINT16_MAX) || + (max < contained_elements + s->cadet_received_element_count)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Handle a multipart-chunk of a request from another service to + * calculate a scalarproduct with us. + * + * @param cls closure (set from #GNUNET_CADET_connect) + * @param msg the actual message + */ +static void +handle_alices_cryptodata_message (void *cls, + const struct EccAliceCryptodataMessage *msg) +{ + struct BobServiceSession *s = cls; + const struct GNUNET_CRYPTO_EccPoint *payload; + uint32_t contained_elements; + unsigned int max; + const struct GNUNET_CRYPTO_EccPoint *g_i; + const struct GNUNET_CRYPTO_EccPoint *h_i; + struct GNUNET_CRYPTO_EccPoint g_i_b_i; + struct GNUNET_CRYPTO_EccPoint h_i_b_i; + + contained_elements = ntohl (msg->contained_element_count); + max = GNUNET_CONTAINER_multihashmap_size (s->intersected_elements); + /* sort our vector for the computation */ + if (NULL == s->sorted_elements) + { + s->sorted_elements + = GNUNET_new_array (GNUNET_CONTAINER_multihashmap_size ( + s->intersected_elements), + struct MpiElement); + s->used_element_count = 0; + GNUNET_CONTAINER_multihashmap_iterate (s->intersected_elements, + ©_element_cb, + s); + qsort (s->sorted_elements, + s->used_element_count, + sizeof(struct MpiElement), + &element_cmp); + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received %u crypto values from Alice\n", + (unsigned int) contained_elements); + payload = (const struct GNUNET_CRYPTO_EccPoint *) &msg[1]; + + for (unsigned int i = 0; i < contained_elements; i++) + { + int64_t val = s->sorted_elements[i + s->cadet_received_element_count].value; + struct GNUNET_CRYPTO_EccScalar vali; + + GNUNET_assert (INT64_MIN != val); + GNUNET_CRYPTO_ecc_scalar_from_int (val > 0 ? val : -val, + &vali); + if (val < 0) + crypto_core_ed25519_scalar_negate (vali.v, + vali.v); + g_i = &payload[i * 2]; + /* g_i_b_i = g_i^vali */ + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_ecc_pmul_mpi (g_i, + &vali, + &g_i_b_i)); + h_i = &payload[i * 2 + 1]; + /* h_i_b_i = h_i^vali */ + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_ecc_pmul_mpi (h_i, + &vali, + &h_i_b_i)); + if (0 == i + s->cadet_received_element_count) + { + /* first iteration, nothing to add */ + s->prod_g_i_b_i = g_i_b_i; + s->prod_h_i_b_i = h_i_b_i; + } + else + { + /* further iterations, cummulate resulting value */ + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_ecc_add (&s->prod_g_i_b_i, + &g_i_b_i, + &s->prod_g_i_b_i)); + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_ecc_add (&s->prod_h_i_b_i, + &h_i_b_i, + &s->prod_h_i_b_i)); + } + } + s->cadet_received_element_count += contained_elements; + if ((s->cadet_received_element_count == max) && + (NULL == s->intersection_op)) + { + /* intersection has finished also on our side, and + we got the full set, so we can proceed with the + CADET response(s) */ + transmit_bobs_cryptodata_message (s); + } + GNUNET_CADET_receive_done (s->channel); +} + + +/** + * Callback for set operation results. Called for each element + * that needs to be removed from the result set. + * + * @param cls closure with the `struct BobServiceSession` + * @param element a result element, only valid if status is #GNUNET_SETI_STATUS_OK + * @param current_size current set size + * @param status what has happened with the set intersection? + */ +static void +cb_intersection_element_removed (void *cls, + const struct GNUNET_SETI_Element *element, + uint64_t current_size, + enum GNUNET_SETI_Status status) +{ + struct BobServiceSession *s = cls; + struct GNUNET_SCALARPRODUCT_Element *se; + + switch (status) + { + case GNUNET_SETI_STATUS_DEL_LOCAL: + /* this element has been removed from the set */ + se = GNUNET_CONTAINER_multihashmap_get (s->intersected_elements, + element->data); + GNUNET_assert (NULL != se); + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Removed element with key %s and value %lld\n", + GNUNET_h2s (&se->key), + (long long) GNUNET_ntohll (se->value)); + GNUNET_assert (GNUNET_YES == + GNUNET_CONTAINER_multihashmap_remove ( + s->intersected_elements, + element->data, + se)); + GNUNET_free (se); + return; + case GNUNET_SETI_STATUS_DONE: + s->intersection_op = NULL; + GNUNET_break (NULL == s->intersection_set); + GNUNET_CADET_receive_done (s->channel); + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Finished intersection, %d items remain\n", + GNUNET_CONTAINER_multihashmap_size (s->intersected_elements)); + if (s->client_received_element_count == + GNUNET_CONTAINER_multihashmap_size (s->intersected_elements)) + { + /* CADET transmission from Alice is also already done, + start with our own reply */ + transmit_bobs_cryptodata_message (s); + } + return; + case GNUNET_SETI_STATUS_FAILURE: + /* unhandled status code */ + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Set intersection failed!\n"); + s->intersection_op = NULL; + if (NULL != s->intersection_set) + { + GNUNET_SETI_destroy (s->intersection_set); + s->intersection_set = NULL; + } + s->status = GNUNET_SCALARPRODUCT_STATUS_FAILURE; + prepare_client_end_notification (s); + return; + + default: + GNUNET_break (0); + return; + } +} + + +/** + * We've paired up a client session with an incoming CADET request. + * Initiate set intersection work. + * + * @param s client session to start intersection for + */ +static void +start_intersection (struct BobServiceSession *s) +{ + struct GNUNET_HashCode set_sid; + + GNUNET_CRYPTO_hash (&s->session_id, + sizeof(struct GNUNET_HashCode), + &set_sid); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Got session with key %s and %u elements, starting intersection.\n", + GNUNET_h2s (&s->session_id), + (unsigned int) s->total); + + s->intersection_op + = GNUNET_SETI_prepare (&s->peer, + &set_sid, + NULL, + (struct GNUNET_SETI_Option[]) { { 0 } }, + &cb_intersection_element_removed, + s); + if (GNUNET_OK != + GNUNET_SETI_commit (s->intersection_op, + s->intersection_set)) + { + GNUNET_break (0); + s->status = GNUNET_SCALARPRODUCT_STATUS_FAILURE; + prepare_client_end_notification (s); + return; + } + GNUNET_SETI_destroy (s->intersection_set); + s->intersection_set = NULL; +} + + +/** + * Handle a request from Alice to calculate a scalarproduct with us (Bob). + * + * @param cls closure (set from #GNUNET_CADET_connect) + * @param msg the actual message + */ +static void +handle_alices_computation_request (void *cls, + const struct EccServiceRequestMessage *msg) +{ + struct BobServiceSession *s = cls; + + s->session_id = msg->session_id; // ?? + if (s->client_received_element_count < s->total) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Alice ready, still waiting for Bob client data!\n"); + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Both ready, launching intersection!\n"); + start_intersection (s); +} + + +/** + * Function called for inbound channels on Bob's end. Does some + * preliminary initialization, more happens after we get Alice's first + * message. + * + * @param cls our `struct BobServiceSession` + * @param channel new handle to the channel + * @param initiator peer that started the channel + * @return session associated with the channel + */ +static void * +cb_channel_incoming (void *cls, + struct GNUNET_CADET_Channel *channel, + const struct GNUNET_PeerIdentity *initiator) +{ + struct BobServiceSession *s = cls; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "New incoming channel from peer %s.\n", + GNUNET_i2s (initiator)); + GNUNET_CADET_close_port (s->port); + s->port = NULL; + s->peer = *initiator; + s->channel = channel; + s->cadet_mq = GNUNET_CADET_get_mq (s->channel); + return s; +} + + +/** + * We're receiving additional set data. Check it is well-formed. + * + * @param cls identification of the client + * @param msg the actual message + * @return #GNUNET_OK if @a msg is well-formed + */ +static int +check_bob_client_message_multipart ( + void *cls, + const struct ComputationBobCryptodataMultipartMessage *msg) +{ + struct BobServiceSession *s = cls; + uint32_t contained_count; + uint16_t msize; + + msize = ntohs (msg->header.size); + contained_count = ntohl (msg->element_count_contained); + if ((msize != (sizeof(struct ComputationBobCryptodataMultipartMessage) + + contained_count * sizeof(struct + GNUNET_SCALARPRODUCT_Element))) || + (0 == contained_count) || + (UINT16_MAX < contained_count) || + (s->total == s->client_received_element_count) || + (s->total < s->client_received_element_count + contained_count)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * We're receiving additional set data. Add it to our + * set and if we are done, initiate the transaction. + * + * @param cls identification of the client + * @param msg the actual message + */ +static void +handle_bob_client_message_multipart ( + void *cls, + const struct ComputationBobCryptodataMultipartMessage *msg) +{ + struct BobServiceSession *s = cls; + uint32_t contained_count; + const struct GNUNET_SCALARPRODUCT_Element *elements; + struct GNUNET_SETI_Element set_elem; + struct GNUNET_SCALARPRODUCT_Element *elem; + + contained_count = ntohl (msg->element_count_contained); + elements = (const struct GNUNET_SCALARPRODUCT_Element *) &msg[1]; + for (uint32_t i = 0; i < contained_count; i++) + { + elem = GNUNET_new (struct GNUNET_SCALARPRODUCT_Element); + GNUNET_memcpy (elem, + &elements[i], + sizeof(struct GNUNET_SCALARPRODUCT_Element)); + if (GNUNET_SYSERR == + GNUNET_CONTAINER_multihashmap_put (s->intersected_elements, + &elem->key, + elem, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)) + { + GNUNET_break (0); + GNUNET_free (elem); + continue; + } + set_elem.data = &elem->key; + set_elem.size = sizeof(elem->key); + set_elem.element_type = 0; + GNUNET_SETI_add_element (s->intersection_set, + &set_elem, + NULL, NULL); + } + s->client_received_element_count += contained_count; + GNUNET_SERVICE_client_continue (s->client); + if (s->total != s->client_received_element_count) + { + /* more to come */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Request still partial, waiting for more client data!\n"); + return; + } + if (NULL == s->channel) + { + /* no Alice waiting for this request, wait for Alice */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Client ready, still waiting for Alice!\n"); + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Both ready, launching intersection!\n"); + start_intersection (s); +} + + +/** + * Handler for Bob's a client request message. Check @a msg is + * well-formed. + * + * @param cls identification of the client + * @param msg the actual message + * @return #GNUNET_OK if @a msg is well-formed + */ +static int +check_bob_client_message (void *cls, + const struct BobComputationMessage *msg) +{ + struct BobServiceSession *s = cls; + uint32_t contained_count; + uint32_t total_count; + uint16_t msize; + + if (GNUNET_SCALARPRODUCT_STATUS_INIT != s->status) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + msize = ntohs (msg->header.size); + total_count = ntohl (msg->element_count_total); + contained_count = ntohl (msg->element_count_contained); + if ((0 == total_count) || + (0 == contained_count) || + (UINT16_MAX < contained_count) || + (msize != (sizeof(struct BobComputationMessage) + + contained_count * sizeof(struct + GNUNET_SCALARPRODUCT_Element)))) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Handler for Bob's a client request message. Bob is in the response + * role, keep the values + session and waiting for a matching session + * or process a waiting request from Alice. + * + * @param cls identification of the client + * @param msg the actual message + */ +static void +handle_bob_client_message (void *cls, + const struct BobComputationMessage *msg) +{ + struct BobServiceSession *s = cls; + struct GNUNET_MQ_MessageHandler cadet_handlers[] = { + GNUNET_MQ_hd_fixed_size (alices_computation_request, + GNUNET_MESSAGE_TYPE_SCALARPRODUCT_ECC_SESSION_INITIALIZATION, + struct EccServiceRequestMessage, + s), + GNUNET_MQ_hd_var_size (alices_cryptodata_message, + GNUNET_MESSAGE_TYPE_SCALARPRODUCT_ECC_ALICE_CRYPTODATA, + struct EccAliceCryptodataMessage, + s), + GNUNET_MQ_handler_end () + }; + uint32_t contained_count; + uint32_t total_count; + const struct GNUNET_SCALARPRODUCT_Element *elements; + struct GNUNET_SETI_Element set_elem; + struct GNUNET_SCALARPRODUCT_Element *elem; + + total_count = ntohl (msg->element_count_total); + contained_count = ntohl (msg->element_count_contained); + + s->status = GNUNET_SCALARPRODUCT_STATUS_ACTIVE; + s->total = total_count; + s->client_received_element_count = contained_count; + s->session_id = msg->session_key; + elements = (const struct GNUNET_SCALARPRODUCT_Element *) &msg[1]; + s->intersected_elements + = GNUNET_CONTAINER_multihashmap_create (s->total, + GNUNET_YES); + s->intersection_set = GNUNET_SETI_create (cfg); + for (uint32_t i = 0; i < contained_count; i++) + { + if (0 == GNUNET_ntohll (elements[i].value)) + continue; + elem = GNUNET_new (struct GNUNET_SCALARPRODUCT_Element); + GNUNET_memcpy (elem, + &elements[i], + sizeof(struct GNUNET_SCALARPRODUCT_Element)); + if (GNUNET_SYSERR == + GNUNET_CONTAINER_multihashmap_put (s->intersected_elements, + &elem->key, + elem, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)) + { + GNUNET_break (0); + GNUNET_free (elem); + continue; + } + set_elem.data = &elem->key; + set_elem.size = sizeof(elem->key); + set_elem.element_type = 0; + GNUNET_SETI_add_element (s->intersection_set, + &set_elem, + NULL, NULL); + s->used_element_count++; + } + GNUNET_SERVICE_client_continue (s->client); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received client request, opening port %s!\n", + GNUNET_h2s (&msg->session_key)); + s->port = GNUNET_CADET_open_port (my_cadet, + &msg->session_key, + &cb_channel_incoming, + s, + NULL, + &cb_channel_destruction, + cadet_handlers); + if (NULL == s->port) + { + GNUNET_break (0); + GNUNET_SERVICE_client_drop (s->client); + return; + } +} + + +/** + * Task run during shutdown. + * + * @param cls unused + */ +static void +shutdown_task (void *cls) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Shutting down, initiating cleanup.\n"); + // FIXME: we have to cut our connections to CADET first! + if (NULL != my_cadet) + { + GNUNET_CADET_disconnect (my_cadet); + my_cadet = NULL; + } + if (NULL != edc) + { + GNUNET_CRYPTO_ecc_dlog_release (edc); + edc = NULL; + } +} + + +/** + * A client connected. + * + * Setup the associated data structure. + * + * @param cls closure, NULL + * @param client identification of the client + * @param mq message queue to communicate with @a client + * @return our `struct BobServiceSession` + */ +static void * +client_connect_cb (void *cls, + struct GNUNET_SERVICE_Client *client, + struct GNUNET_MQ_Handle *mq) +{ + struct BobServiceSession *s; + + s = GNUNET_new (struct BobServiceSession); + s->client = client; + s->client_mq = mq; + return s; +} + + +/** + * A client disconnected. + * + * Remove the associated session(s), release data structures + * and cancel pending outgoing transmissions to the client. + * + * @param cls closure, NULL + * @param client identification of the client + * @param app_cls our `struct BobServiceSession` + */ +static void +client_disconnect_cb (void *cls, + struct GNUNET_SERVICE_Client *client, + void *app_cls) +{ + struct BobServiceSession *s = app_cls; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Client disconnected from us.\n"); + s->client = NULL; + destroy_service_session (s); +} + + +/** + * Initialization of the program and message handlers + * + * @param cls closure + * @param c configuration to use + * @param service the initialized service + */ +static void +run (void *cls, + const struct GNUNET_CONFIGURATION_Handle *c, + struct GNUNET_SERVICE_Handle *service) +{ + cfg = c; + /* We don't really do DLOG, so we can setup with very minimal resources */ + edc = GNUNET_CRYPTO_ecc_dlog_prepare (4 /* max value */, + 2 /* RAM */); + my_cadet = GNUNET_CADET_connect (cfg); + GNUNET_SCHEDULER_add_shutdown (&shutdown_task, + NULL); + if (NULL == my_cadet) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _ ("Connect to CADET failed\n")); + GNUNET_SCHEDULER_shutdown (); + return; + } +} + + +/** + * Define "main" method using service macro. + */ +GNUNET_SERVICE_MAIN + ("scalarproduct-bob", + GNUNET_SERVICE_OPTION_NONE, + &run, + &client_connect_cb, + &client_disconnect_cb, + NULL, + GNUNET_MQ_hd_var_size (bob_client_message, + GNUNET_MESSAGE_TYPE_SCALARPRODUCT_CLIENT_TO_BOB, + struct BobComputationMessage, + NULL), + GNUNET_MQ_hd_var_size (bob_client_message_multipart, + GNUNET_MESSAGE_TYPE_SCALARPRODUCT_CLIENT_MULTIPART_BOB, + struct ComputationBobCryptodataMultipartMessage, + NULL), + GNUNET_MQ_handler_end ()); + + +/* end of gnunet-service-scalarproduct-ecc_bob.c */ diff --git a/src/contrib/service/scalarproduct/gnunet-service-scalarproduct.h b/src/contrib/service/scalarproduct/gnunet-service-scalarproduct.h new file mode 100644 index 000000000..4e79afa2f --- /dev/null +++ b/src/contrib/service/scalarproduct/gnunet-service-scalarproduct.h @@ -0,0 +1,142 @@ +/* + This file is part of GNUnet. + Copyright (C) 2013, 2014 GNUnet e.V. + + GNUnet is free software: you can redistribute it and/or modify it + under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ +/** + * @file scalarproduct/gnunet-service-scalarproduct.h + * @brief scalarproduct service P2P messages + * @author Christian M. Fuchs + * @author Christian Grothoff + */ +#ifndef GNUNET_SERVICE_SCALARPRODUCT_H +#define GNUNET_SERVICE_SCALARPRODUCT_H + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Message type passed from requesting service Alice to responding + * service Bob to initiate a request and make Bob participate in our + * protocol. Afterwards, Bob is expected to perform the set + * intersection with Alice. Once that has succeeded, Alice will + * send a `struct AliceCryptodataMessage *`. Bob is not expected + * to respond via CADET in the meantime. + */ +struct ServiceRequestMessage +{ + /** + * Type is #GNUNET_MESSAGE_TYPE_SCALARPRODUCT_SESSION_INITIALIZATION + */ + struct GNUNET_MessageHeader header; + + /** + * For alignment. Always zero. + */ + uint32_t reserved; + + /** + * The transaction/session key used to identify a session + */ + struct GNUNET_HashCode session_id; + + /** + * Alice's public key + */ + struct GNUNET_CRYPTO_PaillierPublicKey public_key; +}; + + +/** + * Vector of Pallier-encrypted values sent by Alice to Bob + * (after set intersection). Alice may send messages of this + * type repeatedly to transmit all values. + */ +struct AliceCryptodataMessage +{ + /** + * Type is #GNUNET_MESSAGE_TYPE_SCALARPRODUCT_ALICE_CRYPTODATA + */ + struct GNUNET_MessageHeader header; + + /** + * How many elements we appended to this message? In NBO. + */ + uint32_t contained_element_count GNUNET_PACKED; + + /** + * struct GNUNET_CRYPTO_PaillierCiphertext[contained_element_count] + */ +}; + + +/** + * Message type passed from responding service Bob to responding + * service Alice to complete a request and allow Alice to compute the + * result. If Bob's reply does not fit into this one message, the + * conversation may be continued with `struct BobCryptodataMultipartMessage` + * messages afterwards. + */ +struct BobCryptodataMessage +{ + /** + * GNUNET message header with type + * #GNUNET_MESSAGE_TYPE_SCALARPRODUCT_BOB_CRYPTODATA. + */ + struct GNUNET_MessageHeader header; + + /** + * How many elements this individual message delivers (in NBO). + */ + uint32_t contained_element_count GNUNET_PACKED; + + /** + * followed by s | s' | k[i][perm] + */ +}; + + +/** + * Multipart Message type passed between to supply additional elements + * for the peer. Send from Bob to Alice with additional elements + * of k[i][perm] after his `struct BobCryptodataMessage *`. + * Once all k-values have been transmitted, Bob is finished and + * Alice can transmit the final result to the client. + */ +struct BobCryptodataMultipartMessage +{ + /** + * GNUNET message header + */ + struct GNUNET_MessageHeader header; + + /** + * How many elements we supply within this message? In NBO. + */ + uint32_t contained_element_count GNUNET_PACKED; + + /** + * Followed by `struct + * GNUNET_CRYPTO_PaillierCiphertext[contained_element_count]` + */ +}; + + +GNUNET_NETWORK_STRUCT_END + + +#endif diff --git a/src/contrib/service/scalarproduct/gnunet-service-scalarproduct_alice.c b/src/contrib/service/scalarproduct/gnunet-service-scalarproduct_alice.c new file mode 100644 index 000000000..0149f45ba --- /dev/null +++ b/src/contrib/service/scalarproduct/gnunet-service-scalarproduct_alice.c @@ -0,0 +1,1388 @@ +/* + This file is part of GNUnet. + Copyright (C) 2013, 2014, 2017 GNUnet e.V. + + GNUnet is free software: you can redistribute it and/or modify it + under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ +/** + * @file scalarproduct/gnunet-service-scalarproduct_alice.c + * @brief scalarproduct service implementation + * @author Christian M. Fuchs + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#include "gnunet_util_lib.h" +#include "gnunet_core_service.h" +#include "gnunet_cadet_service.h" +#include "gnunet_applications.h" +#include "gnunet_protocols.h" +#include "gnunet_scalarproduct_service.h" +#include "gnunet_seti_service.h" +#include "scalarproduct.h" +#include "gnunet-service-scalarproduct.h" +#include "gnunet_constants.h" + +#define LOG(kind, ...) \ + GNUNET_log_from (kind, "scalarproduct-alice", __VA_ARGS__) + +/** + * An encrypted element key-value pair. + */ +struct MpiElement +{ + /** + * Key used to identify matching pairs of values to multiply. + * Points into an existing data structure, to avoid copying + * and doubling memory use. + */ + const struct GNUNET_HashCode *key; + + /** + * Value represented (a). + */ + gcry_mpi_t value; +}; + + +/** + * A scalarproduct session which tracks + * a request form the client to our final response. + */ +struct AliceServiceSession +{ + /** + * (hopefully) unique transaction ID + */ + struct GNUNET_HashCode session_id; + + /** + * Alice or Bob's peerID + */ + struct GNUNET_PeerIdentity peer; + + /** + * The client this request is related to. + */ + struct GNUNET_SERVICE_Client *client; + + /** + * The message queue for the client. + */ + struct GNUNET_MQ_Handle *client_mq; + + /** + * The message queue for CADET. + */ + struct GNUNET_MQ_Handle *cadet_mq; + + /** + * all non-0-value'd elements transmitted to us. + * Values are of type `struct GNUNET_SCALARPRODUCT_Element *` + */ + struct GNUNET_CONTAINER_MultiHashMap *intersected_elements; + + /** + * Set of elements for which will conduction an intersection. + * the resulting elements are then used for computing the scalar product. + */ + struct GNUNET_SETI_Handle *intersection_set; + + /** + * Set of elements for which will conduction an intersection. + * the resulting elements are then used for computing the scalar product. + */ + struct GNUNET_SETI_OperationHandle *intersection_op; + + /** + * Handle to Alice's Intersection operation listening for Bob + */ + struct GNUNET_SETI_ListenHandle *intersection_listen; + + /** + * channel-handle associated with our cadet handle + */ + struct GNUNET_CADET_Channel *channel; + + /** + * a(Alice), sorted array by key of length @e used_element_count. + */ + struct MpiElement *sorted_elements; + + /** + * Bob's permutation p of R + */ + struct GNUNET_CRYPTO_PaillierCiphertext *r; + + /** + * Bob's permutation q of R + */ + struct GNUNET_CRYPTO_PaillierCiphertext *r_prime; + + /** + * Bob's "s" + */ + struct GNUNET_CRYPTO_PaillierCiphertext s; + + /** + * Bob's "s'" + */ + struct GNUNET_CRYPTO_PaillierCiphertext s_prime; + + /** + * The computed scalar + */ + gcry_mpi_t product; + + /** + * How many elements we were supplied with from the client (total + * count before intersection). + */ + uint32_t total; + + /** + * How many elements actually are used for the scalar product. + * Size of the arrays in @e r and @e r_prime. Sometimes also + * reset to 0 and used as a counter! + */ + uint32_t used_element_count; + + /** + * Already transferred elements from client to us. + * Less or equal than @e total. + */ + uint32_t client_received_element_count; + + /** + * Already transferred elements from Bob to us. + * Less or equal than @e total. + */ + uint32_t cadet_received_element_count; + + /** + * State of this session. In + * #GNUNET_SCALARPRODUCT_STATUS_ACTIVE while operation is + * ongoing, afterwards in #GNUNET_SCALARPRODUCT_STATUS_SUCCESS or + * #GNUNET_SCALARPRODUCT_STATUS_FAILURE. + */ + enum GNUNET_SCALARPRODUCT_ResponseStatus status; + + /** + * Flag to prevent recursive calls to #destroy_service_session() from + * doing harm. + */ + int in_destroy; +}; + + +/** + * GNUnet configuration handle + */ +static const struct GNUNET_CONFIGURATION_Handle *cfg; + +/** + * Service's own public key + */ +static struct GNUNET_CRYPTO_PaillierPublicKey my_pubkey; + +/** + * Service's own private key + */ +static struct GNUNET_CRYPTO_PaillierPrivateKey my_privkey; + +/** + * Service's offset for values that could possibly be negative but are plaintext for encryption. + */ +static gcry_mpi_t my_offset; + +/** + * Handle to the CADET service. + */ +static struct GNUNET_CADET_Handle *my_cadet; + + +/** + * Iterator called to free elements. + * + * @param cls the `struct AliceServiceSession *` (unused) + * @param key the key (unused) + * @param value value to free + * @return #GNUNET_OK (continue to iterate) + */ +static int +free_element_cb (void *cls, const struct GNUNET_HashCode *key, void *value) +{ + struct GNUNET_SCALARPRODUCT_Element *e = value; + + GNUNET_free (e); + return GNUNET_OK; +} + + +/** + * Destroy session state, we are done with it. + * + * @param s the session to free elements from + */ +static void +destroy_service_session (struct AliceServiceSession *s) +{ + if (GNUNET_YES == s->in_destroy) + return; + s->in_destroy = GNUNET_YES; + if (NULL != s->client) + { + struct GNUNET_SERVICE_Client *c = s->client; + + s->client = NULL; + GNUNET_SERVICE_client_drop (c); + } + if (NULL != s->channel) + { + GNUNET_CADET_channel_destroy (s->channel); + s->channel = NULL; + } + if (NULL != s->intersected_elements) + { + GNUNET_CONTAINER_multihashmap_iterate (s->intersected_elements, + &free_element_cb, + s); + GNUNET_CONTAINER_multihashmap_destroy (s->intersected_elements); + s->intersected_elements = NULL; + } + if (NULL != s->intersection_listen) + { + GNUNET_SETI_listen_cancel (s->intersection_listen); + s->intersection_listen = NULL; + } + if (NULL != s->intersection_op) + { + GNUNET_SETI_operation_cancel (s->intersection_op); + s->intersection_op = NULL; + } + if (NULL != s->intersection_set) + { + GNUNET_SETI_destroy (s->intersection_set); + s->intersection_set = NULL; + } + if (NULL != s->sorted_elements) + { + for (unsigned int i = 0; i < s->used_element_count; i++) + gcry_mpi_release (s->sorted_elements[i].value); + GNUNET_free (s->sorted_elements); + s->sorted_elements = NULL; + } + if (NULL != s->r) + { + GNUNET_free (s->r); + s->r = NULL; + } + if (NULL != s->r_prime) + { + GNUNET_free (s->r_prime); + s->r_prime = NULL; + } + if (NULL != s->product) + { + gcry_mpi_release (s->product); + s->product = NULL; + } + GNUNET_free (s); +} + + +/** + * Notify the client that the session has failed. A message gets sent + * to Alice's client if we encountered any error. + * + * @param session the associated client session to fail or succeed + */ +static void +prepare_client_end_notification (struct AliceServiceSession *session) +{ + struct ClientResponseMessage *msg; + struct GNUNET_MQ_Envelope *e; + + if (NULL == session->client_mq) + return; /* no client left to be notified */ + GNUNET_log ( + GNUNET_ERROR_TYPE_DEBUG, + "Sending session-end notification with status %d to client for session %s\n", + session->status, + GNUNET_h2s (&session->session_id)); + e = GNUNET_MQ_msg (msg, GNUNET_MESSAGE_TYPE_SCALARPRODUCT_RESULT); + msg->product_length = htonl (0); + msg->status = htonl (session->status); + GNUNET_MQ_send (session->client_mq, e); +} + + +/** + * Prepare the final (positive) response we will send to Alice's + * client. + * + * @param s the session associated with our client. + */ +static void +transmit_client_response (struct AliceServiceSession *s) +{ + struct ClientResponseMessage *msg; + struct GNUNET_MQ_Envelope *e; + unsigned char *product_exported = NULL; + size_t product_length = 0; + int32_t range; + gcry_error_t rc; + int sign; + gcry_mpi_t value; + + if (NULL == s->product) + { + GNUNET_break (0); + prepare_client_end_notification (s); + return; + } + value = gcry_mpi_new (0); + sign = gcry_mpi_cmp_ui (s->product, 0); + if (0 > sign) + { + range = -1; + gcry_mpi_sub (value, value, s->product); + } + else if (0 < sign) + { + range = 1; + gcry_mpi_add (value, value, s->product); + } + else + { + /* result is exactly zero */ + range = 0; + } + gcry_mpi_release (s->product); + s->product = NULL; + + if ((0 != range) && (0 != (rc = gcry_mpi_aprint (GCRYMPI_FMT_STD, + &product_exported, + &product_length, + value)))) + { + LOG_GCRY (GNUNET_ERROR_TYPE_ERROR, "gcry_mpi_scan", rc); + prepare_client_end_notification (s); + return; + } + gcry_mpi_release (value); + e = GNUNET_MQ_msg_extra (msg, + product_length, + GNUNET_MESSAGE_TYPE_SCALARPRODUCT_RESULT); + msg->status = htonl (GNUNET_SCALARPRODUCT_STATUS_SUCCESS); + msg->range = htonl (range); + msg->product_length = htonl (product_length); + if (NULL != product_exported) + { + GNUNET_memcpy (&msg[1], product_exported, product_length); + GNUNET_free (product_exported); + } + GNUNET_MQ_send (s->client_mq, e); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Sent result to client, session %s has ended!\n", + GNUNET_h2s (&s->session_id)); +} + + +/** + * Function called whenever a channel is destroyed. Should clean up + * any associated state. + * + * It must NOT call #GNUNET_CADET_channel_destroy() on the channel. + * + * @param cls our `struct AliceServiceSession` + * @param channel connection to the other end (henceforth invalid) + */ +static void +cb_channel_destruction (void *cls, const struct GNUNET_CADET_Channel *channel) +{ + struct AliceServiceSession *s = cls; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Peer disconnected, terminating session %s with peer %s\n", + GNUNET_h2s (&s->session_id), + GNUNET_i2s (&s->peer)); + if (GNUNET_SCALARPRODUCT_STATUS_ACTIVE == s->status) + { + /* We didn't get an answer yet, fail with error */ + s->status = GNUNET_SCALARPRODUCT_STATUS_FAILURE; + prepare_client_end_notification (s); + } + s->channel = NULL; +} + + +/** + * Computes the square sum over a vector of a given length. + * + * @param vector the vector to compute over + * @param length the length of the vector + * @return an MPI value containing the calculated sum, never NULL + */ +static gcry_mpi_t +compute_square_sum_mpi_elements (const struct MpiElement *vector, + uint32_t length) +{ + gcry_mpi_t elem; + gcry_mpi_t sum; + uint32_t i; + + GNUNET_assert (NULL != (sum = gcry_mpi_new (0))); + GNUNET_assert (NULL != (elem = gcry_mpi_new (0))); + for (i = 0; i < length; i++) + { + gcry_mpi_mul (elem, vector[i].value, vector[i].value); + gcry_mpi_add (sum, sum, elem); + } + gcry_mpi_release (elem); + return sum; +} + + +/** + * Computes the square sum over a vector of a given length. + * + * @param vector the vector to compute over + * @param length the length of the vector + * @return an MPI value containing the calculated sum, never NULL + */ +static gcry_mpi_t +compute_square_sum (const gcry_mpi_t *vector, uint32_t length) +{ + gcry_mpi_t elem; + gcry_mpi_t sum; + uint32_t i; + + GNUNET_assert (NULL != (sum = gcry_mpi_new (0))); + GNUNET_assert (NULL != (elem = gcry_mpi_new (0))); + for (i = 0; i < length; i++) + { + gcry_mpi_mul (elem, vector[i], vector[i]); + gcry_mpi_add (sum, sum, elem); + } + gcry_mpi_release (elem); + return sum; +} + + +/** + * Compute our scalar product, done by Alice + * + * @param session the session associated with this computation + * @return product as MPI, never NULL + */ +static gcry_mpi_t +compute_scalar_product (struct AliceServiceSession *session) +{ + uint32_t count; + gcry_mpi_t t; + gcry_mpi_t u; + gcry_mpi_t u_prime; + gcry_mpi_t p; + gcry_mpi_t p_prime; + gcry_mpi_t tmp; + gcry_mpi_t r[session->used_element_count]; + gcry_mpi_t r_prime[session->used_element_count]; + gcry_mpi_t s; + gcry_mpi_t s_prime; + unsigned int i; + + count = session->used_element_count; + // due to the introduced static offset S, we now also have to remove this + // from the E(a_pi)(+)E(-b_pi-r_pi) and E(a_qi)(+)E(-r_qi) twice each, + // the result is E((S + a_pi) + (S -b_pi-r_pi)) and E(S + a_qi + S - r_qi) + for (i = 0; i < count; i++) + { + r[i] = gcry_mpi_new (0); + GNUNET_CRYPTO_paillier_decrypt (&my_privkey, + &my_pubkey, + &session->r[i], + r[i]); + gcry_mpi_sub (r[i], r[i], my_offset); + gcry_mpi_sub (r[i], r[i], my_offset); + r_prime[i] = gcry_mpi_new (0); + GNUNET_CRYPTO_paillier_decrypt (&my_privkey, + &my_pubkey, + &session->r_prime[i], + r_prime[i]); + gcry_mpi_sub (r_prime[i], r_prime[i], my_offset); + gcry_mpi_sub (r_prime[i], r_prime[i], my_offset); + } + + // calculate t = sum(ai) + t = compute_square_sum_mpi_elements (session->sorted_elements, count); + // calculate U + u = gcry_mpi_new (0); + tmp = compute_square_sum (r, count); + gcry_mpi_sub (u, u, tmp); + gcry_mpi_release (tmp); + + // calculate U' + u_prime = gcry_mpi_new (0); + tmp = compute_square_sum (r_prime, count); + gcry_mpi_sub (u_prime, u_prime, tmp); + + GNUNET_assert (p = gcry_mpi_new (0)); + GNUNET_assert (p_prime = gcry_mpi_new (0)); + GNUNET_assert (s = gcry_mpi_new (0)); + GNUNET_assert (s_prime = gcry_mpi_new (0)); + + // compute P + GNUNET_CRYPTO_paillier_decrypt (&my_privkey, &my_pubkey, &session->s, s); + GNUNET_CRYPTO_paillier_decrypt (&my_privkey, + &my_pubkey, + &session->s_prime, + s_prime); + + // compute P + gcry_mpi_add (p, s, t); + gcry_mpi_add (p, p, u); + + // compute P' + gcry_mpi_add (p_prime, s_prime, t); + gcry_mpi_add (p_prime, p_prime, u_prime); + + gcry_mpi_release (t); + gcry_mpi_release (u); + gcry_mpi_release (u_prime); + gcry_mpi_release (s); + gcry_mpi_release (s_prime); + + // compute product + gcry_mpi_sub (p, p, p_prime); + gcry_mpi_release (p_prime); + tmp = gcry_mpi_set_ui (tmp, 2); + gcry_mpi_div (p, NULL, p, tmp, 0); + + gcry_mpi_release (tmp); + for (i = 0; i < count; i++) + { + gcry_mpi_release (session->sorted_elements[i].value); + gcry_mpi_release (r[i]); + gcry_mpi_release (r_prime[i]); + } + GNUNET_free (session->sorted_elements); + session->sorted_elements = NULL; + GNUNET_free (session->r); + session->r = NULL; + GNUNET_free (session->r_prime); + session->r_prime = NULL; + + return p; +} + + +/** + * Check a multipart chunk of a response we got from another service + * we wanted to calculate a scalarproduct with. + * + * @param cls the `struct AliceServiceSession` + * @param msg the actual message + * @return #GNUNET_OK to keep the connection open, + * #GNUNET_SYSERR to close it (signal serious error) + */ +static int +check_bobs_cryptodata_multipart ( + void *cls, + const struct BobCryptodataMultipartMessage *msg) +{ + struct AliceServiceSession *s = cls; + uint32_t contained; + size_t msg_size; + size_t required_size; + + msg_size = ntohs (msg->header.size); + contained = ntohl (msg->contained_element_count); + required_size = + sizeof(struct BobCryptodataMultipartMessage) + + 2 * contained * sizeof(struct GNUNET_CRYPTO_PaillierCiphertext); + if ((required_size != msg_size) || + (s->cadet_received_element_count + contained > s->used_element_count)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Handle a multipart chunk of a response we got from another service + * we wanted to calculate a scalarproduct with. + * + * @param cls the `struct AliceServiceSession` + * @param msg the actual message + */ +static void +handle_bobs_cryptodata_multipart ( + void *cls, + const struct BobCryptodataMultipartMessage *msg) +{ + struct AliceServiceSession *s = cls; + const struct GNUNET_CRYPTO_PaillierCiphertext *payload; + size_t i; + uint32_t contained; + + contained = ntohl (msg->contained_element_count); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received %u additional crypto values from Bob\n", + (unsigned int) contained); + + payload = (const struct GNUNET_CRYPTO_PaillierCiphertext *) &msg[1]; + /* Convert each k[][perm] to its MPI_value */ + for (i = 0; i < contained; i++) + { + GNUNET_memcpy (&s->r[s->cadet_received_element_count + i], + &payload[2 * i], + sizeof(struct GNUNET_CRYPTO_PaillierCiphertext)); + GNUNET_memcpy (&s->r_prime[s->cadet_received_element_count + i], + &payload[2 * i], + sizeof(struct GNUNET_CRYPTO_PaillierCiphertext)); + } + s->cadet_received_element_count += contained; + GNUNET_CADET_receive_done (s->channel); + if (s->cadet_received_element_count != s->used_element_count) + return; /* more to come */ + + s->product = compute_scalar_product (s); + transmit_client_response (s); +} + + +/** + * Check a response we got from another service we wanted to + * calculate a scalarproduct with. + * + * @param cls our `struct AliceServiceSession` + * @param msg the actual message + * @return #GNUNET_OK to keep the connection open, + * #GNUNET_SYSERR to close it (we are done) + */ +static int +check_bobs_cryptodata_message (void *cls, + const struct BobCryptodataMessage *msg) +{ + struct AliceServiceSession *s = cls; + uint32_t contained; + uint16_t msg_size; + size_t required_size; + + msg_size = ntohs (msg->header.size); + contained = ntohl (msg->contained_element_count); + required_size = + sizeof(struct BobCryptodataMessage) + + 2 * contained * sizeof(struct GNUNET_CRYPTO_PaillierCiphertext) + + 2 * sizeof(struct GNUNET_CRYPTO_PaillierCiphertext); + if ((msg_size != required_size) || (contained > UINT16_MAX) || + (s->used_element_count < contained)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (NULL == s->sorted_elements) + { + /* we're not ready yet, how can Bob be? */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (s->total != s->client_received_element_count) + { + /* we're not ready yet, how can Bob be? */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Handle a response we got from another service we wanted to + * calculate a scalarproduct with. + * + * @param cls our `struct AliceServiceSession` + * @param msg the actual message + */ +static void +handle_bobs_cryptodata_message (void *cls, + const struct BobCryptodataMessage *msg) +{ + struct AliceServiceSession *s = cls; + const struct GNUNET_CRYPTO_PaillierCiphertext *payload; + uint32_t i; + uint32_t contained; + + contained = ntohl (msg->contained_element_count); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received %u crypto values from Bob\n", + (unsigned int) contained); + payload = (const struct GNUNET_CRYPTO_PaillierCiphertext *) &msg[1]; + GNUNET_memcpy (&s->s, + &payload[0], + sizeof(struct GNUNET_CRYPTO_PaillierCiphertext)); + GNUNET_memcpy (&s->s_prime, + &payload[1], + sizeof(struct GNUNET_CRYPTO_PaillierCiphertext)); + payload = &payload[2]; + + s->r = GNUNET_new_array (s->used_element_count, + struct GNUNET_CRYPTO_PaillierCiphertext); + s->r_prime = GNUNET_new_array (s->used_element_count, + struct GNUNET_CRYPTO_PaillierCiphertext); + for (i = 0; i < contained; i++) + { + GNUNET_memcpy (&s->r[i], + &payload[2 * i], + sizeof(struct GNUNET_CRYPTO_PaillierCiphertext)); + GNUNET_memcpy (&s->r_prime[i], + &payload[2 * i + 1], + sizeof(struct GNUNET_CRYPTO_PaillierCiphertext)); + } + s->cadet_received_element_count = contained; + GNUNET_CADET_receive_done (s->channel); + + if (s->cadet_received_element_count != s->used_element_count) + { + /* More to come */ + return; + } + s->product = compute_scalar_product (s); + transmit_client_response (s); +} + + +/** + * Iterator to copy over messages from the hash map + * into an array for sorting. + * + * @param cls the `struct AliceServiceSession *` + * @param key the key (unused) + * @param value the `struct GNUNET_SCALARPRODUCT_Element *` + */ +static int +copy_element_cb (void *cls, const struct GNUNET_HashCode *key, void *value) +{ + struct AliceServiceSession *s = cls; + struct GNUNET_SCALARPRODUCT_Element *e = value; + gcry_mpi_t mval; + int64_t val; + + mval = gcry_mpi_new (0); + val = (int64_t) GNUNET_ntohll (e->value); + if (0 > val) + gcry_mpi_sub_ui (mval, mval, -val); + else + gcry_mpi_add_ui (mval, mval, val); + s->sorted_elements[s->used_element_count].value = mval; + s->sorted_elements[s->used_element_count].key = &e->key; + s->used_element_count++; + return GNUNET_OK; +} + + +/** + * Compare two `struct MpiValue`s by key for sorting. + * + * @param a pointer to first `struct MpiValue *` + * @param b pointer to first `struct MpiValue *` + * @return -1 for a < b, 0 for a=b, 1 for a > b. + */ +static int +element_cmp (const void *a, const void *b) +{ + const struct MpiElement *ma = a; + const struct MpiElement *mb = b; + + return GNUNET_CRYPTO_hash_cmp (ma->key, mb->key); +} + + +/** + * Maximum number of elements we can put into a single cryptodata + * message + */ +#define ELEMENT_CAPACITY \ + ((GNUNET_CONSTANTS_MAX_CADET_MESSAGE_SIZE - 1 \ + - sizeof(struct AliceCryptodataMessage)) \ + / sizeof(struct GNUNET_CRYPTO_PaillierCiphertext)) + + +/** + * Send the cryptographic data from Alice to Bob. + * Does nothing if we already transferred all elements. + * + * @param s the associated service session + */ +static void +send_alices_cryptodata_message (struct AliceServiceSession *s) +{ + struct AliceCryptodataMessage *msg; + struct GNUNET_MQ_Envelope *e; + struct GNUNET_CRYPTO_PaillierCiphertext *payload; + unsigned int i; + uint32_t todo_count; + gcry_mpi_t a; + uint32_t off; + + s->sorted_elements = GNUNET_malloc ( + GNUNET_CONTAINER_multihashmap_size (s->intersected_elements) + * sizeof(struct MpiElement)); + s->used_element_count = 0; + GNUNET_CONTAINER_multihashmap_iterate (s->intersected_elements, + ©_element_cb, + s); + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Finished intersection, %d items remain\n", + s->used_element_count); + qsort (s->sorted_elements, + s->used_element_count, + sizeof(struct MpiElement), + &element_cmp); + off = 0; + while (off < s->used_element_count) + { + todo_count = s->used_element_count - off; + if (todo_count > ELEMENT_CAPACITY) + todo_count = ELEMENT_CAPACITY; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Sending %u/%u crypto values to Bob\n", + (unsigned int) todo_count, + (unsigned int) s->used_element_count); + + e = + GNUNET_MQ_msg_extra (msg, + todo_count + * sizeof(struct GNUNET_CRYPTO_PaillierCiphertext), + GNUNET_MESSAGE_TYPE_SCALARPRODUCT_ALICE_CRYPTODATA); + msg->contained_element_count = htonl (todo_count); + payload = (struct GNUNET_CRYPTO_PaillierCiphertext *) &msg[1]; + a = gcry_mpi_new (0); + for (i = off; i < off + todo_count; i++) + { + gcry_mpi_add (a, s->sorted_elements[i].value, my_offset); + GNUNET_assert ( + 3 == + GNUNET_CRYPTO_paillier_encrypt (&my_pubkey, a, 3, &payload[i - off])); + } + gcry_mpi_release (a); + off += todo_count; + GNUNET_MQ_send (s->cadet_mq, e); + } +} + + +/** + * Callback for set operation results. Called for each element + * that should be removed from the result set, and then once + * to indicate that the set intersection operation is done. + * + * @param cls closure with the `struct AliceServiceSession` + * @param element a result element, only valid if status is #GNUNET_SETI_STATUS_OK + * @param current_size current set size + * @param status what has happened with the set intersection? + */ +static void +cb_intersection_element_removed (void *cls, + const struct GNUNET_SETI_Element *element, + uint64_t current_size, + enum GNUNET_SETI_Status status) +{ + struct AliceServiceSession *s = cls; + struct GNUNET_SCALARPRODUCT_Element *se; + + switch (status) + { + case GNUNET_SETI_STATUS_DEL_LOCAL: + /* this element has been removed from the set */ + se = GNUNET_CONTAINER_multihashmap_get (s->intersected_elements, + element->data); + GNUNET_assert (NULL != se); + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Intersection removed element with key %s and value %lld\n", + GNUNET_h2s (&se->key), + (long long) GNUNET_ntohll (se->value)); + GNUNET_assert ( + GNUNET_YES == + GNUNET_CONTAINER_multihashmap_remove (s->intersected_elements, + element->data, + se)); + GNUNET_free (se); + return; + + case GNUNET_SETI_STATUS_DONE: + s->intersection_op = NULL; + if (NULL != s->intersection_set) + { + GNUNET_SETI_destroy (s->intersection_set); + s->intersection_set = NULL; + } + send_alices_cryptodata_message (s); + return; + case GNUNET_SETI_STATUS_FAILURE: + /* unhandled status code */ + LOG (GNUNET_ERROR_TYPE_DEBUG, "Set intersection failed!\n"); + if (NULL != s->intersection_listen) + { + GNUNET_SETI_listen_cancel (s->intersection_listen); + s->intersection_listen = NULL; + } + s->intersection_op = NULL; + if (NULL != s->intersection_set) + { + GNUNET_SETI_destroy (s->intersection_set); + s->intersection_set = NULL; + } + s->status = GNUNET_SCALARPRODUCT_STATUS_FAILURE; + prepare_client_end_notification (s); + return; + + default: + GNUNET_break (0); + return; + } +} + + +/** + * Called when another peer wants to do a set operation with the + * local peer. If a listen error occurs, the @a request is NULL. + * + * @param cls closure with the `struct AliceServiceSession *` + * @param other_peer the other peer + * @param context_msg message with application specific information from + * the other peer + * @param request request from the other peer (never NULL), use GNUNET_SETI_accept() + * to accept it, otherwise the request will be refused + * Note that we can't just return value from the listen callback, + * as it is also necessary to specify the set we want to do the + * operation with, which sometimes can be derived from the context + * message. It's necessary to specify the timeout. + */ +static void +cb_intersection_request_alice (void *cls, + const struct GNUNET_PeerIdentity *other_peer, + const struct GNUNET_MessageHeader *context_msg, + struct GNUNET_SETI_Request *request) +{ + struct AliceServiceSession *s = cls; + + if (0 != GNUNET_memcmp (other_peer, &s->peer)) + { + GNUNET_break_op (0); + return; + } + s->intersection_op = GNUNET_SETI_accept (request, + (struct + GNUNET_SETI_Option[]){ { 0 } }, + &cb_intersection_element_removed, + s); + if (NULL == s->intersection_op) + { + GNUNET_break (0); + s->status = GNUNET_SCALARPRODUCT_STATUS_FAILURE; + prepare_client_end_notification (s); + return; + } + if (GNUNET_OK != GNUNET_SETI_commit (s->intersection_op, s->intersection_set)) + { + GNUNET_break (0); + s->status = GNUNET_SCALARPRODUCT_STATUS_FAILURE; + prepare_client_end_notification (s); + return; + } +} + + +/** + * Our client has finished sending us its multipart message. + * + * @param session the service session context + */ +static void +client_request_complete_alice (struct AliceServiceSession *s) +{ + struct GNUNET_MQ_MessageHandler cadet_handlers[] = + { GNUNET_MQ_hd_var_size (bobs_cryptodata_message, + GNUNET_MESSAGE_TYPE_SCALARPRODUCT_BOB_CRYPTODATA, + struct BobCryptodataMessage, + s), + GNUNET_MQ_hd_var_size ( + bobs_cryptodata_multipart, + GNUNET_MESSAGE_TYPE_SCALARPRODUCT_BOB_CRYPTODATA_MULTIPART, + struct BobCryptodataMultipartMessage, + s), + GNUNET_MQ_handler_end () }; + struct ServiceRequestMessage *msg; + struct GNUNET_MQ_Envelope *e; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Creating new channel for session with key %s.\n", + GNUNET_h2s (&s->session_id)); + s->channel = GNUNET_CADET_channel_create (my_cadet, + s, + &s->peer, + &s->session_id, + NULL, + &cb_channel_destruction, + cadet_handlers); + if (NULL == s->channel) + { + s->status = GNUNET_SCALARPRODUCT_STATUS_FAILURE; + prepare_client_end_notification (s); + return; + } + s->cadet_mq = GNUNET_CADET_get_mq (s->channel); + s->intersection_listen = GNUNET_SETI_listen (cfg, + &s->session_id, + &cb_intersection_request_alice, + s); + if (NULL == s->intersection_listen) + { + s->status = GNUNET_SCALARPRODUCT_STATUS_FAILURE; + GNUNET_CADET_channel_destroy (s->channel); + s->channel = NULL; + prepare_client_end_notification (s); + return; + } + + e = GNUNET_MQ_msg (msg, + GNUNET_MESSAGE_TYPE_SCALARPRODUCT_SESSION_INITIALIZATION); + msg->session_id = s->session_id; + msg->public_key = my_pubkey; + GNUNET_MQ_send (s->cadet_mq, e); +} + + +/** + * We're receiving additional set data. Check if + * @a msg is well-formed. + * + * @param cls client identification of the client + * @param msg the actual message + * @return #GNUNET_OK if @a msg is well-formed + */ +static int +check_alice_client_message_multipart ( + void *cls, + const struct ComputationBobCryptodataMultipartMessage *msg) +{ + struct AliceServiceSession *s = cls; + uint32_t contained_count; + uint16_t msize; + + msize = ntohs (msg->header.size); + contained_count = ntohl (msg->element_count_contained); + if ((msize != + (sizeof(struct ComputationBobCryptodataMultipartMessage) + + contained_count * sizeof(struct GNUNET_SCALARPRODUCT_Element))) || + (0 == contained_count) || + (s->total == s->client_received_element_count) || + (s->total < s->client_received_element_count + contained_count)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * We're receiving additional set data. Add it to our + * set and if we are done, initiate the transaction. + * + * @param cls client identification of the client + * @param msg the actual message + */ +static void +handle_alice_client_message_multipart ( + void *cls, + const struct ComputationBobCryptodataMultipartMessage *msg) +{ + struct AliceServiceSession *s = cls; + uint32_t contained_count; + const struct GNUNET_SCALARPRODUCT_Element *elements; + struct GNUNET_SETI_Element set_elem; + struct GNUNET_SCALARPRODUCT_Element *elem; + + contained_count = ntohl (msg->element_count_contained); + s->client_received_element_count += contained_count; + elements = (const struct GNUNET_SCALARPRODUCT_Element *) &msg[1]; + for (uint32_t i = 0; i < contained_count; i++) + { + elem = GNUNET_new (struct GNUNET_SCALARPRODUCT_Element); + GNUNET_memcpy (elem, + &elements[i], + sizeof(struct GNUNET_SCALARPRODUCT_Element)); + if (GNUNET_SYSERR == GNUNET_CONTAINER_multihashmap_put ( + s->intersected_elements, + &elem->key, + elem, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)) + { + GNUNET_break (0); + GNUNET_free (elem); + continue; + } + set_elem.data = &elem->key; + set_elem.size = sizeof(elem->key); + set_elem.element_type = 0; + GNUNET_SETI_add_element (s->intersection_set, &set_elem, NULL, NULL); + s->used_element_count++; + } + GNUNET_SERVICE_client_continue (s->client); + if (s->total != s->client_received_element_count) + { + /* more to come */ + return; + } + client_request_complete_alice (s); +} + + +/** + * Handler for Alice's client request message. + * Check that @a msg is well-formed. + * + * @param cls identification of the client + * @param msg the actual message + * @return #GNUNET_OK if @a msg is well-formed + */ +static int +check_alice_client_message (void *cls, + const struct AliceComputationMessage *msg) +{ + struct AliceServiceSession *s = cls; + uint16_t msize; + uint32_t total_count; + uint32_t contained_count; + + if (NULL != s->intersected_elements) + { + /* only one concurrent session per client connection allowed, + simplifies logic a lot... */ + GNUNET_break (0); + return GNUNET_SYSERR; + } + msize = ntohs (msg->header.size); + total_count = ntohl (msg->element_count_total); + contained_count = ntohl (msg->element_count_contained); + if ((0 == total_count) || (0 == contained_count) || + (msize != + (sizeof(struct AliceComputationMessage) + + contained_count * sizeof(struct GNUNET_SCALARPRODUCT_Element)))) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Handler for Alice's client request message. + * We are doing request-initiation to compute a scalar product with a peer. + * + * @param cls identification of the client + * @param msg the actual message + */ +static void +handle_alice_client_message (void *cls, + const struct AliceComputationMessage *msg) +{ + struct AliceServiceSession *s = cls; + uint32_t contained_count; + uint32_t total_count; + const struct GNUNET_SCALARPRODUCT_Element *elements; + struct GNUNET_SETI_Element set_elem; + struct GNUNET_SCALARPRODUCT_Element *elem; + + total_count = ntohl (msg->element_count_total); + contained_count = ntohl (msg->element_count_contained); + s->peer = msg->peer; + s->status = GNUNET_SCALARPRODUCT_STATUS_ACTIVE; + s->total = total_count; + s->client_received_element_count = contained_count; + s->session_id = msg->session_key; + elements = (const struct GNUNET_SCALARPRODUCT_Element *) &msg[1]; + s->intersected_elements = + GNUNET_CONTAINER_multihashmap_create (s->total, GNUNET_YES); + s->intersection_set = GNUNET_SETI_create (cfg); + + for (uint32_t i = 0; i < contained_count; i++) + { + if (0 == GNUNET_ntohll (elements[i].value)) + continue; + elem = GNUNET_new (struct GNUNET_SCALARPRODUCT_Element); + GNUNET_memcpy (elem, + &elements[i], + sizeof(struct GNUNET_SCALARPRODUCT_Element)); + if (GNUNET_SYSERR == GNUNET_CONTAINER_multihashmap_put ( + s->intersected_elements, + &elem->key, + elem, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)) + { + /* element with same key encountered twice! */ + GNUNET_break (0); + GNUNET_free (elem); + continue; + } + set_elem.data = &elem->key; + set_elem.size = sizeof(elem->key); + set_elem.element_type = 0; + GNUNET_SETI_add_element (s->intersection_set, + &set_elem, + NULL, + NULL); + s->used_element_count++; + } + GNUNET_SERVICE_client_continue (s->client); + if (s->total != s->client_received_element_count) + { + /* wait for multipart msg */ + return; + } + client_request_complete_alice (s); +} + + +/** + * Task run during shutdown. + * + * @param cls unused + */ +static void +shutdown_task (void *cls) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Shutting down, initiating cleanup.\n"); + // FIXME: we have to cut our connections to CADET first! + if (NULL != my_cadet) + { + GNUNET_CADET_disconnect (my_cadet); + my_cadet = NULL; + } +} + + +/** + * A client connected. + * + * Setup the associated data structure. + * + * @param cls closure, NULL + * @param client identification of the client + * @param mq message queue to communicate with @a client + * @return our `struct AliceServiceSession` + */ +static void * +client_connect_cb (void *cls, + struct GNUNET_SERVICE_Client *client, + struct GNUNET_MQ_Handle *mq) +{ + struct AliceServiceSession *s; + + s = GNUNET_new (struct AliceServiceSession); + s->client = client; + s->client_mq = mq; + return s; +} + + +/** + * A client disconnected. + * + * Remove the associated session(s), release data structures + * and cancel pending outgoing transmissions to the client. + * + * @param cls closure, NULL + * @param client identification of the client + * @param app_cls our `struct AliceServiceSession` + */ +static void +client_disconnect_cb (void *cls, + struct GNUNET_SERVICE_Client *client, + void *app_cls) +{ + struct AliceServiceSession *s = app_cls; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Client %p disconnected from us.\n", + client); + s->client = NULL; + s->client_mq = NULL; + destroy_service_session (s); +} + + +/** + * Initialization of the program and message handlers + * + * @param cls closure + * @param c configuration to use + * @param service the initialized service + */ +static void +run (void *cls, + const struct GNUNET_CONFIGURATION_Handle *c, + struct GNUNET_SERVICE_Handle *service) +{ + cfg = c; + /* + offset has to be sufficiently small to allow computation of: + m1+m2 mod n == (S + a) + (S + b) mod n, + if we have more complex operations, this factor needs to be lowered */ + my_offset = gcry_mpi_new (GNUNET_CRYPTO_PAILLIER_BITS / 3); + gcry_mpi_set_bit (my_offset, GNUNET_CRYPTO_PAILLIER_BITS / 3); + GNUNET_CRYPTO_paillier_create (&my_pubkey, &my_privkey); + my_cadet = GNUNET_CADET_connect (cfg); + GNUNET_SCHEDULER_add_shutdown (&shutdown_task, NULL); + if (NULL == my_cadet) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _ ("Connect to CADET failed\n")); + GNUNET_SCHEDULER_shutdown (); + return; + } +} + + +/** + * Define "main" method using service macro. + */ +GNUNET_SERVICE_MAIN ( + "scalarproduct-alice", + GNUNET_SERVICE_OPTION_NONE, + &run, + &client_connect_cb, + &client_disconnect_cb, + NULL, + GNUNET_MQ_hd_var_size (alice_client_message, + GNUNET_MESSAGE_TYPE_SCALARPRODUCT_CLIENT_TO_ALICE, + struct AliceComputationMessage, + NULL), + GNUNET_MQ_hd_var_size ( + alice_client_message_multipart, + GNUNET_MESSAGE_TYPE_SCALARPRODUCT_CLIENT_MULTIPART_ALICE, + struct ComputationBobCryptodataMultipartMessage, + NULL), + GNUNET_MQ_handler_end ()); + + +/* end of gnunet-service-scalarproduct_alice.c */ diff --git a/src/contrib/service/scalarproduct/gnunet-service-scalarproduct_bob.c b/src/contrib/service/scalarproduct/gnunet-service-scalarproduct_bob.c new file mode 100644 index 000000000..65e732675 --- /dev/null +++ b/src/contrib/service/scalarproduct/gnunet-service-scalarproduct_bob.c @@ -0,0 +1,1384 @@ +/* + This file is part of GNUnet. + Copyright (C) 2013, 2014, 2016 GNUnet e.V. + + GNUnet is free software: you can redistribute it and/or modify it + under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ +/** + * @file scalarproduct/gnunet-service-scalarproduct_bob.c + * @brief scalarproduct service implementation + * @author Christian M. Fuchs + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#include "gnunet_util_lib.h" +#include "gnunet_core_service.h" +#include "gnunet_cadet_service.h" +#include "gnunet_applications.h" +#include "gnunet_protocols.h" +#include "gnunet_scalarproduct_service.h" +#include "gnunet_seti_service.h" +#include "scalarproduct.h" +#include "gnunet-service-scalarproduct.h" +#include "gnunet_constants.h" + +#define LOG(kind, ...) GNUNET_log_from (kind, "scalarproduct-bob", __VA_ARGS__) + + +/** + * An encrypted element key-value pair. + */ +struct MpiElement +{ + /** + * Key used to identify matching pairs of values to multiply. + * Points into an existing data structure, to avoid copying + * and doubling memory use. + */ + const struct GNUNET_HashCode *key; + + /** + * Value represented (a). + */ + gcry_mpi_t value; +}; + + +/** + * A scalarproduct session which tracks an offer for a + * multiplication service by a local client. + */ +struct BobServiceSession +{ + /** + * (hopefully) unique transaction ID + */ + struct GNUNET_HashCode session_id; + + /** + * The client this request is related to. + */ + struct GNUNET_SERVICE_Client *client; + + /** + * Client message queue. + */ + struct GNUNET_MQ_Handle *client_mq; + + /** + * All non-0-value'd elements transmitted to us. + */ + struct GNUNET_CONTAINER_MultiHashMap *intersected_elements; + + /** + * Set of elements for which we will be conducting an intersection. + * The resulting elements are then used for computing the scalar product. + */ + struct GNUNET_SETI_Handle *intersection_set; + + /** + * Set of elements for which will conduction an intersection. + * the resulting elements are then used for computing the scalar product. + */ + struct GNUNET_SETI_OperationHandle *intersection_op; + + /** + * CADET port we are listening on. + */ + struct GNUNET_CADET_Port *port; + + /** + * a(Alice) + */ + struct MpiElement *sorted_elements; + + /** + * E(ai)(Bob) after applying the mask + */ + struct GNUNET_CRYPTO_PaillierCiphertext *e_a; + + /** + * Bob's permutation p of R + */ + struct GNUNET_CRYPTO_PaillierCiphertext *r; + + /** + * Bob's permutation q of R + */ + struct GNUNET_CRYPTO_PaillierCiphertext *r_prime; + + /** + * Bob's "s" + */ + struct GNUNET_CRYPTO_PaillierCiphertext s; + + /** + * Bob's "s'" + */ + struct GNUNET_CRYPTO_PaillierCiphertext s_prime; + + /** + * Handle for our associated incoming CADET session, or NULL + * if we have not gotten one yet. + */ + struct CadetIncomingSession *cadet; + + /** + * How many elements will be supplied in total from the client. + */ + uint32_t total; + + /** + * Already transferred elements (received) for multipart + * messages from client. Always less than @e total. + */ + uint32_t client_received_element_count; + + /** + * How many elements actually are used for the scalar product. + * Size of the arrays in @e r and @e r_prime. Also sometimes + * used as an index into the arrays during construction. + */ + uint32_t used_element_count; + + /** + * Counts the number of values received from Alice by us. + * Always less than @e used_element_count. + */ + uint32_t cadet_received_element_count; + + /** + * Counts the number of values transmitted from us to Alice. + * Always less than @e used_element_count. + */ + uint32_t cadet_transmitted_element_count; + + /** + * State of this session. In + * #GNUNET_SCALARPRODUCT_STATUS_ACTIVE while operation is + * ongoing, afterwards in #GNUNET_SCALARPRODUCT_STATUS_SUCCESS or + * #GNUNET_SCALARPRODUCT_STATUS_FAILURE. + */ + enum GNUNET_SCALARPRODUCT_ResponseStatus status; + + /** + * Are we already in #destroy_service_session()? + */ + int in_destroy; + + /** + * The CADET channel. + */ + struct GNUNET_CADET_Channel *channel; + + /** + * Originator's peer identity. (Only for diagnostics.) + */ + struct GNUNET_PeerIdentity peer; + + /** + * Public key of the remote service. + */ + struct GNUNET_CRYPTO_PaillierPublicKey remote_pubkey; + + /** + * The message queue for this channel. + */ + struct GNUNET_MQ_Handle *cadet_mq; +}; + + +/** + * GNUnet configuration handle + */ +static const struct GNUNET_CONFIGURATION_Handle *cfg; + +/** + * Service's own public key + */ +static struct GNUNET_CRYPTO_PaillierPublicKey my_pubkey; + +/** + * Service's own private key + */ +static struct GNUNET_CRYPTO_PaillierPrivateKey my_privkey; + +/** + * Service's offset for values that could possibly be negative but are plaintext for encryption. + */ +static gcry_mpi_t my_offset; + +/** + * Handle to the CADET service. + */ +static struct GNUNET_CADET_Handle *my_cadet; + + +/** + * Callback used to free the elements in the map. + * + * @param cls NULL + * @param key key of the element + * @param value the value to free + */ +static int +free_element_cb (void *cls, + const struct GNUNET_HashCode *key, + void *value) +{ + struct GNUNET_SCALARPRODUCT_Element *element = value; + + GNUNET_free (element); + return GNUNET_OK; +} + + +/** + * Destroy session state, we are done with it. + * + * @param session the session to free elements from + */ +static void +destroy_service_session (struct BobServiceSession *s) +{ + unsigned int i; + + if (GNUNET_YES == s->in_destroy) + return; + s->in_destroy = GNUNET_YES; + if (NULL != s->client) + { + struct GNUNET_SERVICE_Client *c = s->client; + + s->client = NULL; + GNUNET_SERVICE_client_drop (c); + } + if (NULL != s->intersected_elements) + { + GNUNET_CONTAINER_multihashmap_iterate (s->intersected_elements, + &free_element_cb, + NULL); + GNUNET_CONTAINER_multihashmap_destroy (s->intersected_elements); + s->intersected_elements = NULL; + } + if (NULL != s->intersection_op) + { + GNUNET_SETI_operation_cancel (s->intersection_op); + s->intersection_op = NULL; + } + if (NULL != s->intersection_set) + { + GNUNET_SETI_destroy (s->intersection_set); + s->intersection_set = NULL; + } + if (NULL != s->e_a) + { + GNUNET_free (s->e_a); + s->e_a = NULL; + } + if (NULL != s->sorted_elements) + { + for (i = 0; i < s->used_element_count; i++) + gcry_mpi_release (s->sorted_elements[i].value); + GNUNET_free (s->sorted_elements); + s->sorted_elements = NULL; + } + if (NULL != s->r) + { + GNUNET_free (s->r); + s->r = NULL; + } + if (NULL != s->r_prime) + { + GNUNET_free (s->r_prime); + s->r_prime = NULL; + } + if (NULL != s->port) + { + GNUNET_CADET_close_port (s->port); + s->port = NULL; + } + if (NULL != s->channel) + { + GNUNET_CADET_channel_destroy (s->channel); + s->channel = NULL; + } + GNUNET_free (s); +} + + +/** + * Notify the client that the session has succeeded or failed. This + * message gets sent to Bob's client if the operation completed or + * Alice disconnected. + * + * @param session the associated client session to fail or succeed + */ +static void +prepare_client_end_notification (struct BobServiceSession *session) +{ + struct ClientResponseMessage *msg; + struct GNUNET_MQ_Envelope *e; + + if (NULL == session->client_mq) + return; /* no client left to be notified */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Sending session-end notification with status %d to client for session %s\n", + session->status, + GNUNET_h2s (&session->session_id)); + e = GNUNET_MQ_msg (msg, + GNUNET_MESSAGE_TYPE_SCALARPRODUCT_RESULT); + msg->range = 0; + msg->product_length = htonl (0); + msg->status = htonl (session->status); + GNUNET_MQ_send (session->client_mq, + e); +} + + +/** + * Function called whenever a channel is destroyed. Should clean up + * any associated state. + * + * It must NOT call #GNUNET_CADET_channel_destroy() on the channel. + * + * @param cls the `struct BobServiceSession` + * @param channel connection to the other end (henceforth invalid) + */ +static void +cb_channel_destruction (void *cls, + const struct GNUNET_CADET_Channel *channel) +{ + struct BobServiceSession *s = cls; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Peer disconnected, terminating session %s with peer %s\n", + GNUNET_h2s (&s->session_id), + GNUNET_i2s (&s->peer)); + if (GNUNET_SCALARPRODUCT_STATUS_ACTIVE == s->status) + { + s->status = GNUNET_SCALARPRODUCT_STATUS_FAILURE; + prepare_client_end_notification (s); + } + s->channel = NULL; + destroy_service_session (s); +} + + +/** + * MQ finished giving our last message to CADET, now notify + * the client that we are finished. + */ +static void +bob_cadet_done_cb (void *cls) +{ + struct BobServiceSession *session = cls; + + session->status = GNUNET_SCALARPRODUCT_STATUS_SUCCESS; + prepare_client_end_notification (session); +} + + +/** + * Maximum count of elements we can put into a multipart message + */ +#define ELEMENT_CAPACITY ((GNUNET_CONSTANTS_MAX_CADET_MESSAGE_SIZE - 1 \ + - sizeof(struct BobCryptodataMultipartMessage)) \ + / sizeof(struct \ + GNUNET_CRYPTO_PaillierCiphertext)) + + +/** + * Send a multipart chunk of a service response from Bob to Alice. + * This element only contains the two permutations of R, R'. + * + * @param s the associated service session + */ +static void +transmit_bobs_cryptodata_message_multipart (struct BobServiceSession *s) +{ + struct GNUNET_CRYPTO_PaillierCiphertext *payload; + struct BobCryptodataMultipartMessage *msg; + struct GNUNET_MQ_Envelope *e; + unsigned int i; + unsigned int j; + uint32_t todo_count; + + while (s->cadet_transmitted_element_count != s->used_element_count) + { + todo_count = s->used_element_count - s->cadet_transmitted_element_count; + if (todo_count > ELEMENT_CAPACITY / 2) + todo_count = ELEMENT_CAPACITY / 2; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Sending %u additional crypto values to Alice\n", + (unsigned int) todo_count); + e = GNUNET_MQ_msg_extra (msg, + todo_count * sizeof(struct + GNUNET_CRYPTO_PaillierCiphertext) + * 2, + GNUNET_MESSAGE_TYPE_SCALARPRODUCT_BOB_CRYPTODATA_MULTIPART); + msg->contained_element_count = htonl (todo_count); + payload = (struct GNUNET_CRYPTO_PaillierCiphertext *) &msg[1]; + for (i = s->cadet_transmitted_element_count, j = 0; i < + s->cadet_transmitted_element_count + todo_count; i++) + { + // r[i][p] and r[i][q] + GNUNET_memcpy (&payload[j++], + &s->r[i], + sizeof(struct GNUNET_CRYPTO_PaillierCiphertext)); + GNUNET_memcpy (&payload[j++], + &s->r_prime[i], + sizeof(struct GNUNET_CRYPTO_PaillierCiphertext)); + } + s->cadet_transmitted_element_count += todo_count; + if (s->cadet_transmitted_element_count == s->used_element_count) + GNUNET_MQ_notify_sent (e, + &bob_cadet_done_cb, + s); + GNUNET_MQ_send (s->cadet_mq, + e); + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "All values queued for Alice, Bob is done\n"); +} + + +/** + * Bob generates the response message to be sent to Alice after + * computing the values (1), (2), S and S'. + * + * (1)[]: $E_A(a_{pi(i)}) times E_A(- r_{pi(i)} - b_{pi(i)}) &= E_A(a_{pi(i)} - r_{pi(i)} - b_{pi(i)})$ + * (2)[]: $E_A(a_{pi'(i)}) times E_A(- r_{pi'(i)}) &= E_A(a_{pi'(i)} - r_{pi'(i)})$ + * S: $S := E_A(sum (r_i + b_i)^2)$ + * S': $S' := E_A(sum r_i^2)$ + * + * @param s the associated requesting session with Alice + */ +static void +transmit_bobs_cryptodata_message (struct BobServiceSession *s) +{ + struct BobCryptodataMessage *msg; + struct GNUNET_MQ_Envelope *e; + struct GNUNET_CRYPTO_PaillierCiphertext *payload; + unsigned int i; + + s->cadet_transmitted_element_count + = ((GNUNET_CONSTANTS_MAX_CADET_MESSAGE_SIZE - 1 - sizeof(struct + BobCryptodataMessage)) + / sizeof(struct GNUNET_CRYPTO_PaillierCiphertext) / 2) - 1; + if (s->cadet_transmitted_element_count > s->used_element_count) + s->cadet_transmitted_element_count = s->used_element_count; + + e = GNUNET_MQ_msg_extra (msg, + (2 + s->cadet_transmitted_element_count * 2) + * sizeof(struct GNUNET_CRYPTO_PaillierCiphertext), + GNUNET_MESSAGE_TYPE_SCALARPRODUCT_BOB_CRYPTODATA); + msg->contained_element_count = htonl (s->cadet_transmitted_element_count); + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Sending %u/%u crypto values to Alice\n", + (unsigned int) s->cadet_transmitted_element_count, + (unsigned int) s->used_element_count); + + payload = (struct GNUNET_CRYPTO_PaillierCiphertext *) &msg[1]; + GNUNET_memcpy (&payload[0], + &s->s, + sizeof(struct GNUNET_CRYPTO_PaillierCiphertext)); + GNUNET_memcpy (&payload[1], + &s->s_prime, + sizeof(struct GNUNET_CRYPTO_PaillierCiphertext)); + + payload = &payload[2]; + // convert k[][] + for (i = 0; i < s->cadet_transmitted_element_count; i++) + { + // k[i][p] and k[i][q] + GNUNET_memcpy (&payload[i * 2], + &s->r[i], + sizeof(struct GNUNET_CRYPTO_PaillierCiphertext)); + GNUNET_memcpy (&payload[i * 2 + 1], + &s->r_prime[i], + sizeof(struct GNUNET_CRYPTO_PaillierCiphertext)); + } + if (s->cadet_transmitted_element_count == s->used_element_count) + GNUNET_MQ_notify_sent (e, + &bob_cadet_done_cb, + s); + GNUNET_MQ_send (s->cadet_mq, + e); + transmit_bobs_cryptodata_message_multipart (s); +} + + +#undef ELEMENT_CAPACITY + + +/** + * Computes the square sum over a vector of a given length. + * + * @param vector the vector to compute over + * @param length the length of the vector + * @return an MPI value containing the calculated sum, never NULL + * TODO: code duplication with Alice! + */ +static gcry_mpi_t +compute_square_sum (const gcry_mpi_t *vector, + uint32_t length) +{ + gcry_mpi_t elem; + gcry_mpi_t sum; + uint32_t i; + + GNUNET_assert (NULL != (sum = gcry_mpi_new (0))); + GNUNET_assert (NULL != (elem = gcry_mpi_new (0))); + for (i = 0; i < length; i++) + { + gcry_mpi_mul (elem, vector[i], vector[i]); + gcry_mpi_add (sum, sum, elem); + } + gcry_mpi_release (elem); + return sum; +} + + +/** + * Compute the values + * (1)[]: $E_A(a_{pi(i)}) otimes E_A(- r_{pi(i)} - b_{pi(i)}) &= E_A(a_{pi(i)} - r_{pi(i)} - b_{pi(i)})$ + * (2)[]: $E_A(a_{pi'(i)}) otimes E_A(- r_{pi'(i)}) &= E_A(a_{pi'(i)} - r_{pi'(i)})$ + * S: $S := E_A(sum (r_i + b_i)^2)$ + * S': $S' := E_A(sum r_i^2)$ + * + * @param session the requesting session + bob's requesting peer + * @return #GNUNET_OK on success + */ +static int +compute_service_response (struct BobServiceSession *session) +{ + uint32_t i; + unsigned int *p; + unsigned int *q; + uint32_t count; + gcry_mpi_t *rand; + gcry_mpi_t tmp; + const struct MpiElement *b; + struct GNUNET_CRYPTO_PaillierCiphertext *a; + struct GNUNET_CRYPTO_PaillierCiphertext *r; + struct GNUNET_CRYPTO_PaillierCiphertext *r_prime; + + count = session->used_element_count; + a = session->e_a; + b = session->sorted_elements; + q = GNUNET_CRYPTO_random_permute (GNUNET_CRYPTO_QUALITY_WEAK, + count); + p = GNUNET_CRYPTO_random_permute (GNUNET_CRYPTO_QUALITY_WEAK, + count); + rand = GNUNET_malloc (sizeof(gcry_mpi_t) * count); + for (i = 0; i < count; i++) + GNUNET_assert (NULL != (rand[i] = gcry_mpi_new (0))); + r = GNUNET_malloc (sizeof(struct GNUNET_CRYPTO_PaillierCiphertext) * count); + r_prime = GNUNET_malloc (sizeof(struct GNUNET_CRYPTO_PaillierCiphertext) + * count); + + for (i = 0; i < count; i++) + { + int32_t svalue; + + svalue = (int32_t) GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, + UINT32_MAX); + // long to gcry_mpi_t + if (svalue < 0) + gcry_mpi_sub_ui (rand[i], + rand[i], + -svalue); + else + rand[i] = gcry_mpi_set_ui (rand[i], svalue); + } + + tmp = gcry_mpi_new (0); + // encrypt the element + // for the sake of readability I decided to have dedicated permutation + // vectors, which get rid of all the lookups in p/q. + // however, ap/aq are not absolutely necessary but are just abstraction + // Calculate Kp = E(S + a_pi) (+) E(S - r_pi - b_pi) + for (i = 0; i < count; i++) + { + // E(S - r_pi - b_pi) + gcry_mpi_sub (tmp, my_offset, rand[p[i]]); + gcry_mpi_sub (tmp, tmp, b[p[i]].value); + GNUNET_assert (2 == + GNUNET_CRYPTO_paillier_encrypt (&session->remote_pubkey, + tmp, + 2, + &r[i])); + + // E(S - r_pi - b_pi) * E(S + a_pi) == E(2*S + a - r - b) + if (GNUNET_OK != + GNUNET_CRYPTO_paillier_hom_add (&session->remote_pubkey, + &r[i], + &a[p[i]], + &r[i])) + { + GNUNET_break_op (0); + goto error_cleanup; + } + } + + // Calculate Kq = E(S + a_qi) (+) E(S - r_qi) + for (i = 0; i < count; i++) + { + // E(S - r_qi) + gcry_mpi_sub (tmp, my_offset, rand[q[i]]); + GNUNET_assert (2 == + GNUNET_CRYPTO_paillier_encrypt (&session->remote_pubkey, + tmp, + 2, + &r_prime[i])); + + // E(S - r_qi) * E(S + a_qi) == E(2*S + a_qi - r_qi) + if (GNUNET_OK != + GNUNET_CRYPTO_paillier_hom_add (&session->remote_pubkey, + &r_prime[i], + &a[q[i]], + &r_prime[i])) + { + GNUNET_break_op (0); + goto error_cleanup; + } + } + gcry_mpi_release (tmp); + + // Calculate S' = E(SUM( r_i^2 )) + tmp = compute_square_sum (rand, count); + GNUNET_assert (1 == + GNUNET_CRYPTO_paillier_encrypt (&session->remote_pubkey, + tmp, + 1, + &session->s_prime)); + gcry_mpi_release (tmp); + + // Calculate S = E(SUM( (r_i + b_i)^2 )) + for (i = 0; i < count; i++) + gcry_mpi_add (rand[i], rand[i], b[i].value); + tmp = compute_square_sum (rand, count); + GNUNET_assert (1 == + GNUNET_CRYPTO_paillier_encrypt (&session->remote_pubkey, + tmp, + 1, + &session->s)); + gcry_mpi_release (tmp); + + session->r = r; + session->r_prime = r_prime; + + for (i = 0; i < count; i++) + gcry_mpi_release (rand[i]); + GNUNET_free (session->e_a); + session->e_a = NULL; + GNUNET_free (p); + GNUNET_free (q); + GNUNET_free (rand); + return GNUNET_OK; + +error_cleanup: + GNUNET_free (r); + GNUNET_free (r_prime); + gcry_mpi_release (tmp); + GNUNET_free (p); + GNUNET_free (q); + for (i = 0; i < count; i++) + gcry_mpi_release (rand[i]); + GNUNET_free (rand); + return GNUNET_SYSERR; +} + + +/** + * Iterator to copy over messages from the hash map + * into an array for sorting. + * + * @param cls the `struct BobServiceSession *` + * @param key the key (unused) + * @param value the `struct GNUNET_SCALARPRODUCT_Element *` + * TODO: code duplication with Alice! + */ +static int +copy_element_cb (void *cls, + const struct GNUNET_HashCode *key, + void *value) +{ + struct BobServiceSession *s = cls; + struct GNUNET_SCALARPRODUCT_Element *e = value; + gcry_mpi_t mval; + int64_t val; + + mval = gcry_mpi_new (0); + val = (int64_t) GNUNET_ntohll (e->value); + if (0 > val) + gcry_mpi_sub_ui (mval, mval, -val); + else + gcry_mpi_add_ui (mval, mval, val); + s->sorted_elements [s->used_element_count].value = mval; + s->sorted_elements [s->used_element_count].key = &e->key; + s->used_element_count++; + return GNUNET_OK; +} + + +/** + * Compare two `struct MpiValue`s by key for sorting. + * + * @param a pointer to first `struct MpiValue *` + * @param b pointer to first `struct MpiValue *` + * @return -1 for a < b, 0 for a=b, 1 for a > b. + * TODO: code duplication with Alice! + */ +static int +element_cmp (const void *a, + const void *b) +{ + const struct MpiElement *ma = a; + const struct MpiElement *mb = b; + + return GNUNET_CRYPTO_hash_cmp (ma->key, + mb->key); +} + + +/** + * Intersection operation and receiving data via CADET from + * Alice are both done, compute and transmit our reply via + * CADET. + * + * @param s session to transmit reply for. + */ +static void +transmit_cryptographic_reply (struct BobServiceSession *s) +{ + struct GNUNET_CADET_Channel *channel; + + /* TODO: code duplication with Alice! */ + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Received everything, building reply for Alice\n"); + s->sorted_elements + = GNUNET_malloc (GNUNET_CONTAINER_multihashmap_size ( + s->intersected_elements) + * sizeof(struct MpiElement)); + s->used_element_count = 0; + GNUNET_CONTAINER_multihashmap_iterate (s->intersected_elements, + ©_element_cb, + s); + qsort (s->sorted_elements, + s->used_element_count, + sizeof(struct MpiElement), + &element_cmp); + if (GNUNET_OK != + compute_service_response (s)) + { + channel = s->channel; + s->channel = NULL; + GNUNET_CADET_channel_destroy (channel); + return; + } + transmit_bobs_cryptodata_message (s); +} + + +/** + * Check a multipart-chunk of a request from another service to + * calculate a scalarproduct with us. + * + * @param cls the `struct BobServiceSession *` + * @param msg the actual message + * @return #GNUNET_OK to keep the connection open, + * #GNUNET_SYSERR to close it (signal serious error) + */ +static int +check_alices_cryptodata_message (void *cls, + const struct AliceCryptodataMessage *msg) +{ + struct BobServiceSession *s = cls; + uint32_t contained_elements; + size_t msg_length; + uint16_t msize; + unsigned int max; + + msize = ntohs (msg->header.size); + contained_elements = ntohl (msg->contained_element_count); + /* Our intersection may still be ongoing, but this is nevertheless + an upper bound on the required array size */ + max = GNUNET_CONTAINER_multihashmap_size (s->intersected_elements); + msg_length = sizeof(struct AliceCryptodataMessage) + + contained_elements * sizeof(struct + GNUNET_CRYPTO_PaillierCiphertext); + if ((msize != msg_length) || + (0 == contained_elements) || + (contained_elements > UINT16_MAX) || + (max < contained_elements + s->cadet_received_element_count)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Handle a multipart-chunk of a request from another service to + * calculate a scalarproduct with us. + * + * @param cls the `struct BobServiceSession *` + * @param msg the actual message + */ +static void +handle_alices_cryptodata_message (void *cls, + const struct AliceCryptodataMessage *msg) +{ + struct BobServiceSession *s = cls; + const struct GNUNET_CRYPTO_PaillierCiphertext *payload; + uint32_t contained_elements; + unsigned int max; + + contained_elements = ntohl (msg->contained_element_count); + /* Our intersection may still be ongoing, but this is nevertheless + an upper bound on the required array size */ + max = GNUNET_CONTAINER_multihashmap_size (s->intersected_elements); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received %u crypto values from Alice\n", + (unsigned int) contained_elements); + + payload = (const struct GNUNET_CRYPTO_PaillierCiphertext *) &msg[1]; + if (NULL == s->e_a) + s->e_a = GNUNET_new_array (max, + struct GNUNET_CRYPTO_PaillierCiphertext); + GNUNET_memcpy (&s->e_a[s->cadet_received_element_count], + payload, + sizeof(struct GNUNET_CRYPTO_PaillierCiphertext) + * contained_elements); + s->cadet_received_element_count += contained_elements; + + if ((s->cadet_received_element_count == max) && + (NULL == s->intersection_op)) + { + /* intersection has finished also on our side, and + we got the full set, so we can proceed with the + CADET response(s) */ + transmit_cryptographic_reply (s); + } + GNUNET_CADET_receive_done (s->channel); +} + + +/** + * Callback for set operation results. Called for each element + * that needs to be removed from the result set. + * + * @param cls closure with the `struct BobServiceSession` + * @param element a result element, only valid if status is #GNUNET_SETI_STATUS_OK + * @param current_size current set size + * @param status what has happened with the set intersection? + */ +static void +cb_intersection_element_removed (void *cls, + const struct GNUNET_SETI_Element *element, + uint64_t current_size, + enum GNUNET_SETI_Status status) +{ + struct BobServiceSession *s = cls; + struct GNUNET_SCALARPRODUCT_Element *se; + + switch (status) + { + case GNUNET_SETI_STATUS_DEL_LOCAL: + /* this element has been removed from the set */ + se = GNUNET_CONTAINER_multihashmap_get (s->intersected_elements, + element->data); + GNUNET_assert (NULL != se); + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Removed element with key %s and value %lld\n", + GNUNET_h2s (&se->key), + (long long) GNUNET_ntohll (se->value)); + GNUNET_assert (GNUNET_YES == + GNUNET_CONTAINER_multihashmap_remove ( + s->intersected_elements, + element->data, + se)); + GNUNET_free (se); + return; + case GNUNET_SETI_STATUS_DONE: + s->intersection_op = NULL; + GNUNET_break (NULL == s->intersection_set); + GNUNET_CADET_receive_done (s->channel); + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Finished intersection, %d items remain\n", + GNUNET_CONTAINER_multihashmap_size (s->intersected_elements)); + if (s->client_received_element_count == + GNUNET_CONTAINER_multihashmap_size (s->intersected_elements)) + { + /* CADET transmission from Alice is also already done, + start with our own reply */ + transmit_cryptographic_reply (s); + } + return; + case GNUNET_SETI_STATUS_FAILURE: + /* unhandled status code */ + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Set intersection failed!\n"); + s->intersection_op = NULL; + if (NULL != s->intersection_set) + { + GNUNET_SETI_destroy (s->intersection_set); + s->intersection_set = NULL; + } + s->status = GNUNET_SCALARPRODUCT_STATUS_FAILURE; + prepare_client_end_notification (s); + return; + default: + GNUNET_break (0); + return; + } +} + + +/** + * We've paired up a client session with an incoming CADET request. + * Initiate set intersection work. + * + * @param s client session to start intersection for + */ +static void +start_intersection (struct BobServiceSession *s) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Got session with key %s and %u elements, starting intersection.\n", + GNUNET_h2s (&s->session_id), + (unsigned int) s->total); + + s->intersection_op + = GNUNET_SETI_prepare (&s->peer, + &s->session_id, + NULL, + (struct GNUNET_SETI_Option[]) { { 0 } }, + &cb_intersection_element_removed, + s); + if (GNUNET_OK != + GNUNET_SETI_commit (s->intersection_op, + s->intersection_set)) + { + GNUNET_break (0); + s->status = GNUNET_SCALARPRODUCT_STATUS_FAILURE; + prepare_client_end_notification (s); + return; + } + GNUNET_SETI_destroy (s->intersection_set); + s->intersection_set = NULL; +} + + +/** + * Handle a request from Alice to calculate a scalarproduct with us (Bob). + * + * @param cls the `struct BobServiceSession *` + * @param msg the actual message + */ +static void +handle_alices_computation_request (void *cls, + const struct ServiceRequestMessage *msg) +{ + struct BobServiceSession *s = cls; + + s->session_id = msg->session_id; // ?? + s->remote_pubkey = msg->public_key; + if (s->client_received_element_count == s->total) + start_intersection (s); +} + + +/** + * Function called for inbound channels on Bob's end. Does some + * preliminary initialization, more happens after we get Alice's first + * message. + * + * @param cls closure with the `struct BobServiceSession` + * @param channel new handle to the channel + * @param initiator peer that started the channel + * @return session associated with the channel + */ +static void * +cb_channel_incoming (void *cls, + struct GNUNET_CADET_Channel *channel, + const struct GNUNET_PeerIdentity *initiator) +{ + struct BobServiceSession *s = cls; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "New incoming channel from peer %s.\n", + GNUNET_i2s (initiator)); + GNUNET_CADET_close_port (s->port); + s->port = NULL; + s->channel = channel; + s->peer = *initiator; + s->cadet_mq = GNUNET_CADET_get_mq (s->channel); + return s; +} + + +/** + * We're receiving additional set data. Check it is well-formed. + * + * @param cls identification of the client + * @param msg the actual message + * @return #GNUNET_OK if @a msg is well-formed + */ +static int +check_bob_client_message_multipart (void *cls, + const struct + ComputationBobCryptodataMultipartMessage * + msg) +{ + struct BobServiceSession *s = cls; + uint32_t contained_count; + uint16_t msize; + + msize = ntohs (msg->header.size); + contained_count = ntohl (msg->element_count_contained); + if ((msize != (sizeof(struct ComputationBobCryptodataMultipartMessage) + + contained_count * sizeof(struct + GNUNET_SCALARPRODUCT_Element))) || + (0 == contained_count) || + (UINT16_MAX < contained_count) || + (s->total == s->client_received_element_count) || + (s->total < s->client_received_element_count + contained_count)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * We're receiving additional set data. Add it to our + * set and if we are done, initiate the transaction. + * + * @param cls identification of the client + * @param msg the actual message + */ +static void +handle_bob_client_message_multipart (void *cls, + const struct + ComputationBobCryptodataMultipartMessage * + msg) +{ + struct BobServiceSession *s = cls; + uint32_t contained_count; + const struct GNUNET_SCALARPRODUCT_Element *elements; + struct GNUNET_SETI_Element set_elem; + struct GNUNET_SCALARPRODUCT_Element *elem; + + contained_count = ntohl (msg->element_count_contained); + elements = (const struct GNUNET_SCALARPRODUCT_Element *) &msg[1]; + for (uint32_t i = 0; i < contained_count; i++) + { + elem = GNUNET_new (struct GNUNET_SCALARPRODUCT_Element); + GNUNET_memcpy (elem, + &elements[i], + sizeof(struct GNUNET_SCALARPRODUCT_Element)); + if (GNUNET_SYSERR == + GNUNET_CONTAINER_multihashmap_put (s->intersected_elements, + &elem->key, + elem, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)) + { + GNUNET_break (0); + GNUNET_free (elem); + continue; + } + set_elem.data = &elem->key; + set_elem.size = sizeof(elem->key); + set_elem.element_type = 0; + GNUNET_SETI_add_element (s->intersection_set, + &set_elem, + NULL, NULL); + } + s->client_received_element_count += contained_count; + GNUNET_SERVICE_client_continue (s->client); + if (s->total != s->client_received_element_count) + { + /* more to come */ + return; + } + if (NULL == s->channel) + { + /* no Alice waiting for this request, wait for Alice */ + return; + } + start_intersection (s); +} + + +/** + * Handler for Bob's a client request message. Check @a msg is + * well-formed. + * + * @param cls identification of the client + * @param msg the actual message + * @return #GNUNET_OK if @a msg is well-formed + */ +static int +check_bob_client_message (void *cls, + const struct BobComputationMessage *msg) +{ + struct BobServiceSession *s = cls; + uint32_t contained_count; + uint32_t total_count; + uint16_t msize; + + if (GNUNET_SCALARPRODUCT_STATUS_INIT != s->status) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + msize = ntohs (msg->header.size); + total_count = ntohl (msg->element_count_total); + contained_count = ntohl (msg->element_count_contained); + if ((0 == total_count) || + (0 == contained_count) || + (UINT16_MAX < contained_count) || + (msize != (sizeof(struct BobComputationMessage) + + contained_count * sizeof(struct + GNUNET_SCALARPRODUCT_Element)))) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Handler for Bob's a client request message. Bob is in the response + * role, keep the values + session and waiting for a matching session + * or process a waiting request from Alice. + * + * @param cls identification of the client + * @param msg the actual message + */ +static void +handle_bob_client_message (void *cls, + const struct BobComputationMessage *msg) +{ + struct BobServiceSession *s = cls; + struct GNUNET_MQ_MessageHandler cadet_handlers[] = { + GNUNET_MQ_hd_fixed_size (alices_computation_request, + GNUNET_MESSAGE_TYPE_SCALARPRODUCT_SESSION_INITIALIZATION, + struct ServiceRequestMessage, + NULL), + GNUNET_MQ_hd_var_size (alices_cryptodata_message, + GNUNET_MESSAGE_TYPE_SCALARPRODUCT_ALICE_CRYPTODATA, + struct AliceCryptodataMessage, + NULL), + GNUNET_MQ_handler_end () + }; + uint32_t contained_count; + uint32_t total_count; + const struct GNUNET_SCALARPRODUCT_Element *elements; + struct GNUNET_SETI_Element set_elem; + struct GNUNET_SCALARPRODUCT_Element *elem; + + total_count = ntohl (msg->element_count_total); + contained_count = ntohl (msg->element_count_contained); + + s->status = GNUNET_SCALARPRODUCT_STATUS_ACTIVE; + s->total = total_count; + s->client_received_element_count = contained_count; + s->session_id = msg->session_key; + elements = (const struct GNUNET_SCALARPRODUCT_Element *) &msg[1]; + s->intersected_elements + = GNUNET_CONTAINER_multihashmap_create (s->total, + GNUNET_YES); + s->intersection_set = GNUNET_SETI_create (cfg); + for (uint32_t i = 0; i < contained_count; i++) + { + if (0 == GNUNET_ntohll (elements[i].value)) + continue; + elem = GNUNET_new (struct GNUNET_SCALARPRODUCT_Element); + GNUNET_memcpy (elem, + &elements[i], + sizeof(struct GNUNET_SCALARPRODUCT_Element)); + if (GNUNET_SYSERR == + GNUNET_CONTAINER_multihashmap_put (s->intersected_elements, + &elem->key, + elem, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)) + { + GNUNET_break (0); + GNUNET_free (elem); + continue; + } + set_elem.data = &elem->key; + set_elem.size = sizeof(elem->key); + set_elem.element_type = 0; + GNUNET_SETI_add_element (s->intersection_set, + &set_elem, + NULL, NULL); + s->used_element_count++; + } + GNUNET_SERVICE_client_continue (s->client); + /* We're ready, open the port */ + s->port = GNUNET_CADET_open_port (my_cadet, + &msg->session_key, + &cb_channel_incoming, + s, + NULL, + &cb_channel_destruction, + cadet_handlers); + if (NULL == s->port) + { + GNUNET_break (0); + GNUNET_SERVICE_client_drop (s->client); + return; + } +} + + +/** + * Task run during shutdown. + * + * @param cls unused + */ +static void +shutdown_task (void *cls) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Shutting down, initiating cleanup.\n"); + // FIXME: we have to cut our connections to CADET first! + if (NULL != my_cadet) + { + GNUNET_CADET_disconnect (my_cadet); + my_cadet = NULL; + } +} + + +/** + * A client connected. + * + * Setup the associated data structure. + * + * @param cls closure, NULL + * @param client identification of the client + * @param mq message queue to communicate with @a client + * @return our `struct BobServiceSession` + */ +static void * +client_connect_cb (void *cls, + struct GNUNET_SERVICE_Client *client, + struct GNUNET_MQ_Handle *mq) +{ + struct BobServiceSession *s; + + s = GNUNET_new (struct BobServiceSession); + s->client = client; + s->client_mq = mq; + return s; +} + + +/** + * A client disconnected. + * + * Remove the associated session(s), release data structures + * and cancel pending outgoing transmissions to the client. + * + * @param cls closure, NULL + * @param client identification of the client + * @param app_cls our `struct BobServiceSession` + */ +static void +client_disconnect_cb (void *cls, + struct GNUNET_SERVICE_Client *client, + void *app_cls) +{ + struct BobServiceSession *s = app_cls; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Client disconnected from us.\n"); + s->client = NULL; + destroy_service_session (s); +} + + +/** + * Initialization of the program and message handlers + * + * @param cls closure + * @param c configuration to use + * @param service the initialized service + */ +static void +run (void *cls, + const struct GNUNET_CONFIGURATION_Handle *c, + struct GNUNET_SERVICE_Handle *service) +{ + cfg = c; + /* + offset has to be sufficiently small to allow computation of: + m1+m2 mod n == (S + a) + (S + b) mod n, + if we have more complex operations, this factor needs to be lowered */ + my_offset = gcry_mpi_new (GNUNET_CRYPTO_PAILLIER_BITS / 3); + gcry_mpi_set_bit (my_offset, + GNUNET_CRYPTO_PAILLIER_BITS / 3); + + GNUNET_CRYPTO_paillier_create (&my_pubkey, + &my_privkey); + my_cadet = GNUNET_CADET_connect (cfg); + GNUNET_SCHEDULER_add_shutdown (&shutdown_task, + NULL); + if (NULL == my_cadet) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _ ("Connect to CADET failed\n")); + GNUNET_SCHEDULER_shutdown (); + return; + } +} + + +/** + * Define "main" method using service macro. + */ +GNUNET_SERVICE_MAIN + ("scalarproduct-bob", + GNUNET_SERVICE_OPTION_NONE, + &run, + &client_connect_cb, + &client_disconnect_cb, + NULL, + GNUNET_MQ_hd_var_size (bob_client_message, + GNUNET_MESSAGE_TYPE_SCALARPRODUCT_CLIENT_TO_BOB, + struct BobComputationMessage, + NULL), + GNUNET_MQ_hd_var_size (bob_client_message_multipart, + GNUNET_MESSAGE_TYPE_SCALARPRODUCT_CLIENT_MULTIPART_BOB, + struct ComputationBobCryptodataMultipartMessage, + NULL), + GNUNET_MQ_handler_end ()); + + +/* end of gnunet-service-scalarproduct_bob.c */ diff --git a/src/contrib/service/scalarproduct/meson.build b/src/contrib/service/scalarproduct/meson.build new file mode 100644 index 000000000..296734c2c --- /dev/null +++ b/src/contrib/service/scalarproduct/meson.build @@ -0,0 +1,102 @@ +libgnunetscalarproduct_src = ['scalarproduct_api.c'] + +gnunetservicescalarproducta_src = ['gnunet-service-scalarproduct_alice.c'] +gnunetservicescalarproductb_src = ['gnunet-service-scalarproduct_bob.c'] +gnunetservicescalarproductecca_src = ['gnunet-service-scalarproduct-ecc_alice.c'] +gnunetservicescalarproducteccb_src = ['gnunet-service-scalarproduct-ecc_bob.c'] + +configure_file(input : 'scalarproduct.conf.in', + output : 'scalarproduct.conf', + configuration : cdata, + install: true, + install_dir: pkgcfgdir) + + +if get_option('monolith') + foreach p : libgnunetscalarproduct_src + gnunetservicescalarproducta_src + gnunetservicescalarproductb_src + gnunetservicescalarproductecca_src + gnunetservicescalarproducteccb_src + gnunet_src += 'scalarproduct/' + p + endforeach + subdir_done() +endif + +libgnunetscalarproduct = library('gnunetscalarproduct', + libgnunetscalarproduct_src, + soversion: '0', + version: '0.0.0', + dependencies: [libgnunetutil_dep, + gcrypt_dep], + include_directories: [incdir, configuration_inc], + install: true, + install_dir: get_option('libdir')) +pkg.generate(libgnunetscalarproduct, url: 'https://www.gnunet.org', + description : 'Provides API for accessing the scalarproduct service') +libgnunetscalarproduct_dep = declare_dependency(link_with : libgnunetscalarproduct) + +executable ('gnunet-scalarproduct', + ['gnunet-scalarproduct.c'], + dependencies: [libgnunetscalarproduct_dep, + libgnunetutil_dep, + gcrypt_dep, + libgnunetstatistics_dep, + libgnunetcore_dep, + libgnunetcadet_dep, + libgnunetblock_dep], + include_directories: [incdir, configuration_inc], + install: true, + install_dir: get_option('bindir')) + +executable ('gnunet-service-scalarproduct-alice', + gnunetservicescalarproducta_src, + dependencies: [libgnunetscalarproduct_dep, + libgnunetutil_dep, + gcrypt_dep, + libgnunetseti_dep, + libgnunetstatistics_dep, + libgnunetcore_dep, + libgnunetcadet_dep, + libgnunetblock_dep], + include_directories: [incdir, configuration_inc], + install: true, + install_dir: get_option('libdir') / 'gnunet' / 'libexec') +executable ('gnunet-service-scalarproduct-bob', + gnunetservicescalarproductb_src, + dependencies: [libgnunetscalarproduct_dep, + libgnunetutil_dep, + gcrypt_dep, + libgnunetseti_dep, + libgnunetstatistics_dep, + libgnunetcore_dep, + libgnunetcadet_dep, + libgnunetblock_dep], + include_directories: [incdir, configuration_inc], + install: true, + install_dir: get_option('libdir') / 'gnunet' / 'libexec') +executable ('gnunet-service-scalarproduct-ecc-alice', + gnunetservicescalarproductecca_src, + dependencies: [libgnunetscalarproduct_dep, + libgnunetutil_dep, + libgnunetstatistics_dep, + libgnunetcore_dep, + gcrypt_dep, + sodium_dep, + libgnunetseti_dep, + libgnunetcadet_dep, + libgnunetblock_dep], + include_directories: [incdir, configuration_inc], + install: true, + install_dir: get_option('libdir') / 'gnunet' / 'libexec') +executable ('gnunet-service-scalarproduct-ecc-bob', + gnunetservicescalarproducteccb_src, + dependencies: [libgnunetscalarproduct_dep, + libgnunetutil_dep, + libgnunetstatistics_dep, + libgnunetcore_dep, + gcrypt_dep, + sodium_dep, + libgnunetseti_dep, + libgnunetcadet_dep, + libgnunetblock_dep], + include_directories: [incdir, configuration_inc], + install: true, + install_dir: get_option('libdir') / 'gnunet' / 'libexec') + diff --git a/src/contrib/service/scalarproduct/perf_scalarproduct.sh b/src/contrib/service/scalarproduct/perf_scalarproduct.sh new file mode 100755 index 000000000..b15465c9a --- /dev/null +++ b/src/contrib/service/scalarproduct/perf_scalarproduct.sh @@ -0,0 +1,73 @@ +#!/bin/bash +# Computes a simple scalar product, with configurable vector size. +# +# Some results (wall-clock for Alice+Bob, single-core, i7, libgcrypt): +# SIZE 2048-H(s) 2048-O(s) 1024-O(s) ECC-2^20-H(s) ECC-2^28-H(s) +# 25 10 14 3 2 29 +# 50 17 21 5 2 29 +# 100 32 39 7 2 29 +# 200 77 13 3 30 +# 400 149 23 OOR 31 +# 800 304 32 OOR 33 + +# Bandwidth (including set intersection): +# RSA-1024 RSA-2048 ECC +# 800: 629 kb 1234 kb 65 kb +# +# LIBSODIUM, AMD Threadripper 1950: +# +# SIZE 2048-O(s) 1024-O(s) ECC-2^20-H(s) ECC-2^28-H(s) +# 25 4.3 0.7 0.129 4.233 +# 50 7.7 1.2 0.143 4.267 +# 100 10.3 2.4 0.163 4.282 +# 200 19.8 3.0 0.192 4.326 +# 400 35.9 6.0 0.253 4.358 +# 800 73.7 12.6 0.379 4.533 + +# +# +# Configure benchmark size: +SIZE=800 +# +# Construct input vectors: +INPUTALICE="-k CCC -e '" +INPUTBOB="-k CCC -e '" +for X in `seq 1 $SIZE` +do + INPUTALICE="${INPUTALICE}A${X},$X;" + INPUTBOB="${INPUTBOB}A${X},$X;" +done +INPUTALICE="${INPUTALICE}BC,-20000;RO,1000;FL,100;LOL,24;'" +INPUTBOB="${INPUTBOB}AB,10;RO,3;FL,3;LOL,-1;'" + +# necessary to make the testing prefix deterministic, so we can access the config files +PREFIX=/tmp/test-scalarproduct`date +%H%M%S` + +# where can we find the peers config files? +CFGALICE="-c $PREFIX/0/config" +CFGBOB="-c $PREFIX/1/config" + +# launch two peers in line topology non-interactively +# +# interactive mode would terminate the test immediately +# because the rest of the script is already in stdin, +# thus redirecting stdin does not suffice) +#GNUNET_FORCE_LOG=';;;;ERROR' +#GNUNET_FORCE_LOG='scalarproduct*;;;;DEBUG/cadet-api*;;;;DEBUG' +GNUNET_TESTING_PREFIX=$PREFIX ../testbed/gnunet-testbed-profiler -n -c test_scalarproduct.conf -p 2 & +PID=$! +# sleep 1 is too short on most systems, 2 works on most, 5 seems to be safe +echo "Waiting for peers to start..." +sleep 5 +# get Bob's peer ID, necessary for Alice +PEERIDBOB=`gnunet-peerinfo -qs $CFGBOB` + +echo "Running problem of size $SIZE" +gnunet-scalarproduct $CFGBOB $INPUTBOB & +time RESULT=`gnunet-scalarproduct $CFGALICE $INPUTALICE -p $PEERIDBOB` +gnunet-statistics $CFGALICE -s core | grep "bytes encrypted" +gnunet-statistics $CFGBOB -s core | grep "bytes encrypted" + +echo "Terminating testbed..." +# terminate the testbed +kill $PID diff --git a/src/contrib/service/scalarproduct/scalarproduct.conf.in b/src/contrib/service/scalarproduct/scalarproduct.conf.in new file mode 100644 index 000000000..e2286b076 --- /dev/null +++ b/src/contrib/service/scalarproduct/scalarproduct.conf.in @@ -0,0 +1,27 @@ +[scalarproduct-alice] +START_ON_DEMAND = @START_ON_DEMAND@ +BINARY = gnunet-service-scalarproduct-ecc-alice +UNIXPATH = $GNUNET_RUNTIME_DIR/gnunet-service-scalarproduct-alice.sock +@UNIXONLY@ PORT = 2117 +#ACCEPT_FROM = 127.0.0.1; +#ACCEPT_FROM6 = ::1; +UNIX_MATCH_UID = NO +UNIX_MATCH_GID = YES +#OPTIONS = -L DEBUG +#PREFIX = valgrind + + +[scalarproduct-bob] +START_ON_DEMAND = @START_ON_DEMAND@ +HOSTNAME = localhost +BINARY = gnunet-service-scalarproduct-ecc-bob +UNIXPATH = $GNUNET_RUNTIME_DIR/gnunet-service-scalarproduct-bob.sock +@UNIXONLY@ PORT = 2118 + +#ACCEPT_FROM = 127.0.0.1; +#ACCEPT_FROM6 = ::1; +UNIX_MATCH_UID = NO +UNIX_MATCH_GID = YES +#OPTIONS = -L DEBUG + +#PREFIX = valgrind diff --git a/src/contrib/service/scalarproduct/scalarproduct.h b/src/contrib/service/scalarproduct/scalarproduct.h new file mode 100644 index 000000000..f2311cda0 --- /dev/null +++ b/src/contrib/service/scalarproduct/scalarproduct.h @@ -0,0 +1,177 @@ +/* + This file is part of GNUnet. + Copyright (C) 2009, 2010, 2011, 2012, 2013 GNUnet e.V. + + GNUnet is free software: you can redistribute it and/or modify it + under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ +/** + * @file scalarproduct.h + * @brief Scalar Product API Message Types + * @author Christian M. Fuchs + */ +#ifndef SCALARPRODUCT_H +#define SCALARPRODUCT_H + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Log an error message at log-level 'level' that indicates + * a failure of the command 'cmd' with the message given + * by gcry_strerror(rc). + */ +#define LOG_GCRY(level, cmd, rc) do { LOG (level, _ ( \ + "`%s' failed at %s:%d with error: %s\n"), \ + cmd, __FILE__, __LINE__, \ + gcry_strerror (rc)); } while (0) + + +/** + * Message type passed from client to service + * to initiate a request or responder role + */ +struct AliceComputationMessage +{ + /** + * GNUNET message header with type + * #GNUNET_MESSAGE_TYPE_SCALARPRODUCT_CLIENT_TO_ALICE + */ + struct GNUNET_MessageHeader header; + + /** + * how many elements the vector in payload contains + */ + uint32_t element_count_total GNUNET_PACKED; + + /** + * contained elements the vector in payload contains + */ + uint32_t element_count_contained GNUNET_PACKED; + + /** + * Always zero. + */ + uint32_t reserved GNUNET_PACKED; + + /** + * the transaction/session key used to identify a session + */ + struct GNUNET_HashCode session_key; + + /** + * the identity of a remote peer we want to communicate with + */ + struct GNUNET_PeerIdentity peer; + + /** + * followed by struct GNUNET_SCALARPRODUCT_Element[] + */ +}; + + +/** + * Message type passed from client to service + * to initiate a request or responder role + */ +struct BobComputationMessage +{ + /** + * GNUNET message header with type + * #GNUNET_MESSAGE_TYPE_SCALARPRODUCT_CLIENT_TO_BOB + */ + struct GNUNET_MessageHeader header; + + /** + * how many elements the vector in payload contains + */ + uint32_t element_count_total GNUNET_PACKED; + + /** + * contained elements the vector in payload contains + */ + uint32_t element_count_contained GNUNET_PACKED; + + /** + * Always zero. + */ + uint32_t reserved GNUNET_PACKED; + + /** + * the transaction/session key used to identify a session + */ + struct GNUNET_HashCode session_key; + + /** + * followed by struct GNUNET_SCALARPRODUCT_Element[] + */ +}; + + +/** + * multipart messages following `struct ComputationMessage` + */ +struct ComputationBobCryptodataMultipartMessage +{ + /** + * GNUNET message header + */ + struct GNUNET_MessageHeader header; + + /** + * contained elements the vector in payload contains + */ + uint32_t element_count_contained GNUNET_PACKED; + + /** + * followed by struct GNUNET_SCALARPRODUCT_Element[] + */ +}; + + +/** + * Message type passed from service client + * to finalize a session as requester or responder + */ +struct ClientResponseMessage +{ + /** + * GNUNET message header + */ + struct GNUNET_MessageHeader header; + + /** + * 0 if no product attached + */ + uint32_t product_length GNUNET_PACKED; + + /** + * Status information about the outcome of this session, + * An `enum GNUNET_SCALARPRODUCT_ResponseStatus` (in NBO). + */ + uint32_t status GNUNET_PACKED; + + /** + * Workaround for libgcrypt: -1 if negative, 0 if zero, else 1 + */ + int32_t range GNUNET_PACKED; + + /** + * followed by product of length product_length (or nothing) + */ +}; + +GNUNET_NETWORK_STRUCT_END + +#endif /* SCALARPRODUCT_H */ diff --git a/src/contrib/service/scalarproduct/scalarproduct_api.c b/src/contrib/service/scalarproduct/scalarproduct_api.c new file mode 100644 index 000000000..8c667a72e --- /dev/null +++ b/src/contrib/service/scalarproduct/scalarproduct_api.c @@ -0,0 +1,468 @@ +/* + This file is part of GNUnet. + Copyright (C) 2013, 2014, 2016 GNUnet e.V. + + GNUnet is free software: you can redistribute it and/or modify it + under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ +/** + * @file scalarproduct/scalarproduct_api.c + * @brief API for the scalarproduct + * @author Christian Fuchs + * @author Gaurav Kukreja + * @author Christian Grothoff + */ +#include "platform.h" +#include "gnunet_util_lib.h" +#include "gnunet_statistics_service.h" +#include "gnunet_scalarproduct_service.h" +#include "gnunet_protocols.h" +#include "scalarproduct.h" + +#define LOG(kind, ...) GNUNET_log_from (kind, "scalarproduct-api", __VA_ARGS__) + + +/** + * The abstraction function for our internal callback + * + * @param h computation handle + * @param msg response we got, NULL on errors + * @param status processing status code + */ +typedef void +(*GNUNET_SCALARPRODUCT_ResponseMessageHandler) ( + struct GNUNET_SCALARPRODUCT_ComputationHandle *h, + const struct ClientResponseMessage *msg, + enum GNUNET_SCALARPRODUCT_ResponseStatus status); + + +/** + * A handle returned for each computation + */ +struct GNUNET_SCALARPRODUCT_ComputationHandle +{ + /** + * Our configuration. + */ + const struct GNUNET_CONFIGURATION_Handle *cfg; + + /** + * Current connection to the scalarproduct service. + */ + struct GNUNET_MQ_Handle *mq; + + /** + * Function to call after transmission of the request (Bob). + */ + GNUNET_SCALARPRODUCT_ContinuationWithStatus cont_status; + + /** + * Function to call after transmission of the request (Alice). + */ + GNUNET_SCALARPRODUCT_DatumProcessor cont_datum; + + /** + * Closure for @e cont_status or @e cont_datum. + */ + void *cont_cls; + + /** + * API internal callback for results and failures to be forwarded to + * the client. + */ + GNUNET_SCALARPRODUCT_ResponseMessageHandler response_proc; + + /** + * The shared session key identifying this computation + */ + struct GNUNET_HashCode key; +}; + + +/** + * Called when a response is received from the service. Perform basic + * check that the message is well-formed. + * + * @param cls Pointer to the Master Context + * @param message Pointer to the data received in response + * @return #GNUNET_OK if @a message is well-formed + */ +static int +check_response (void *cls, + const struct ClientResponseMessage *message) +{ + if (ntohs (message->header.size) != + ntohl (message->product_length) + sizeof(struct ClientResponseMessage)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Handles the STATUS received from the service for a response, does + * not contain a payload. Called when we participate as "Bob" via + * #GNUNET_SCALARPRODUCT_accept_computation(). + * + * @param h our Handle + * @param msg the response received + * @param status the condition the request was terminated with (eg: disconnect) + */ +static void +process_status_message (struct GNUNET_SCALARPRODUCT_ComputationHandle *h, + const struct ClientResponseMessage *msg, + enum GNUNET_SCALARPRODUCT_ResponseStatus status) +{ + if (NULL != h->cont_status) + h->cont_status (h->cont_cls, + status); + GNUNET_SCALARPRODUCT_cancel (h); +} + + +/** + * Called when a response is received from the service. After basic + * check, the handler in `h->response_proc` is called. This functions + * handles the response to the client which used the API. + * + * @param cls Pointer to the Master Context + * @param message Pointer to the data received in response + */ +static void +handle_response (void *cls, + const struct ClientResponseMessage *message) +{ + struct GNUNET_SCALARPRODUCT_ComputationHandle *h = cls; + enum GNUNET_SCALARPRODUCT_ResponseStatus status; + + status = (enum GNUNET_SCALARPRODUCT_ResponseStatus) ntohl (message->status); + h->response_proc (h, + message, + status); +} + + +/** + * Check if the keys for all given elements are unique. + * + * @param elements elements to check + * @param element_count size of the @a elements array + * @return #GNUNET_OK if all keys are unique + */ +static int +check_unique (const struct GNUNET_SCALARPRODUCT_Element *elements, + uint32_t element_count) +{ + struct GNUNET_CONTAINER_MultiHashMap *map; + int ok; + + ok = GNUNET_OK; + map = GNUNET_CONTAINER_multihashmap_create (2 * element_count, + GNUNET_YES); + for (uint32_t i = 0; i < element_count; i++) + if (GNUNET_OK != + GNUNET_CONTAINER_multihashmap_put (map, + &elements[i].key, + map, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _ ("Keys given to SCALARPRODUCT not unique!\n")); + ok = GNUNET_SYSERR; + } + GNUNET_CONTAINER_multihashmap_destroy (map); + return ok; +} + + +/** + * We encountered an error communicating with the set service while + * performing a set operation. Report to the application. + * + * @param cls the `struct GNUNET_SCALARPRODUCT_ComputationHandle` + * @param error error code + */ +static void +mq_error_handler (void *cls, + enum GNUNET_MQ_Error error) +{ + struct GNUNET_SCALARPRODUCT_ComputationHandle *h = cls; + + LOG (GNUNET_ERROR_TYPE_INFO, + "Disconnected from SCALARPRODUCT service.\n"); + h->response_proc (h, + NULL, + GNUNET_SCALARPRODUCT_STATUS_DISCONNECTED); +} + + +struct GNUNET_SCALARPRODUCT_ComputationHandle * +GNUNET_SCALARPRODUCT_accept_computation ( + const struct GNUNET_CONFIGURATION_Handle *cfg, + const struct GNUNET_HashCode *session_key, + const struct GNUNET_SCALARPRODUCT_Element *elements, + uint32_t element_count, + GNUNET_SCALARPRODUCT_ContinuationWithStatus cont, + void *cont_cls) +{ + struct GNUNET_SCALARPRODUCT_ComputationHandle *h + = GNUNET_new (struct GNUNET_SCALARPRODUCT_ComputationHandle); + struct GNUNET_MQ_MessageHandler handlers[] = { + GNUNET_MQ_hd_var_size (response, + GNUNET_MESSAGE_TYPE_SCALARPRODUCT_RESULT, + struct ClientResponseMessage, + h), + GNUNET_MQ_handler_end () + }; + struct GNUNET_MQ_Envelope *env; + struct BobComputationMessage *msg; + struct ComputationBobCryptodataMultipartMessage *mmsg; + uint32_t size; + uint16_t possible; + uint16_t todo; + uint32_t element_count_transfered; + + + if (GNUNET_SYSERR == check_unique (elements, + element_count)) + return NULL; + h->cont_status = cont; + h->cont_cls = cont_cls; + h->response_proc = &process_status_message; + h->cfg = cfg; + h->key = *session_key; + h->mq = GNUNET_CLIENT_connect (cfg, + "scalarproduct-bob", + handlers, + &mq_error_handler, + h); + if (NULL == h->mq) + { + /* scalarproduct configuration error */ + GNUNET_break (0); + GNUNET_free (h); + return NULL; + } + possible = (GNUNET_MAX_MESSAGE_SIZE - 1 - sizeof(struct + BobComputationMessage)) + / sizeof(struct GNUNET_SCALARPRODUCT_Element); + todo = GNUNET_MIN (possible, + element_count); + size = todo * sizeof(struct GNUNET_SCALARPRODUCT_Element); + env = GNUNET_MQ_msg_extra (msg, + size, + GNUNET_MESSAGE_TYPE_SCALARPRODUCT_CLIENT_TO_BOB); + msg->element_count_total = htonl (element_count); + msg->element_count_contained = htonl (todo); + msg->session_key = *session_key; + GNUNET_memcpy (&msg[1], + elements, + size); + element_count_transfered = todo; + GNUNET_MQ_send (h->mq, + env); + possible = (GNUNET_MAX_MESSAGE_SIZE - 1 - sizeof(*mmsg)) + / sizeof(struct GNUNET_SCALARPRODUCT_Element); + while (element_count_transfered < element_count) + { + todo = GNUNET_MIN (possible, + element_count - element_count_transfered); + size = todo * sizeof(struct GNUNET_SCALARPRODUCT_Element); + env = GNUNET_MQ_msg_extra (mmsg, + size, + GNUNET_MESSAGE_TYPE_SCALARPRODUCT_CLIENT_MULTIPART_BOB); + mmsg->element_count_contained = htonl (todo); + GNUNET_memcpy (&mmsg[1], + &elements[element_count_transfered], + size); + element_count_transfered += todo; + GNUNET_MQ_send (h->mq, + env); + } + return h; +} + + +/** + * Handles the RESULT received from the service for a request, should + * contain a result MPI value. Called when we participate as "Alice" via + * #GNUNET_SCALARPRODUCT_start_computation(). + * + * @param h our Handle + * @param msg Pointer to the response received + * @param status the condition the request was terminated with (eg: disconnect) + */ +static void +process_result_message (struct GNUNET_SCALARPRODUCT_ComputationHandle *h, + const struct ClientResponseMessage *msg, + enum GNUNET_SCALARPRODUCT_ResponseStatus status) +{ + uint32_t product_len; + gcry_mpi_t result = NULL; + gcry_error_t rc; + gcry_mpi_t num; + size_t rsize; + + if (GNUNET_SCALARPRODUCT_STATUS_SUCCESS == status) + { + result = gcry_mpi_new (0); + + product_len = ntohl (msg->product_length); + if (0 < product_len) + { + rsize = 0; + if (0 != (rc = gcry_mpi_scan (&num, GCRYMPI_FMT_STD, + &msg[1], + product_len, + &rsize))) + { + LOG_GCRY (GNUNET_ERROR_TYPE_ERROR, + "gcry_mpi_scan", + rc); + gcry_mpi_release (result); + result = NULL; + status = GNUNET_SCALARPRODUCT_STATUS_INVALID_RESPONSE; + } + else + { + if (0 < (int32_t) ntohl (msg->range)) + gcry_mpi_add (result, result, num); + else + gcry_mpi_sub (result, result, num); + gcry_mpi_release (num); + } + } + } + if (NULL != h->cont_datum) + h->cont_datum (h->cont_cls, + status, + result); + if (NULL != result) + gcry_mpi_release (result); + GNUNET_SCALARPRODUCT_cancel (h); +} + + +struct GNUNET_SCALARPRODUCT_ComputationHandle * +GNUNET_SCALARPRODUCT_start_computation ( + const struct GNUNET_CONFIGURATION_Handle *cfg, + const struct GNUNET_HashCode *session_key, + const struct GNUNET_PeerIdentity *peer, + const struct GNUNET_SCALARPRODUCT_Element *elements, + uint32_t element_count, + GNUNET_SCALARPRODUCT_DatumProcessor cont, + void *cont_cls) +{ + struct GNUNET_SCALARPRODUCT_ComputationHandle *h + = GNUNET_new (struct GNUNET_SCALARPRODUCT_ComputationHandle); + struct GNUNET_MQ_MessageHandler handlers[] = { + GNUNET_MQ_hd_var_size (response, + GNUNET_MESSAGE_TYPE_SCALARPRODUCT_RESULT, + struct ClientResponseMessage, + h), + GNUNET_MQ_handler_end () + }; + struct GNUNET_MQ_Envelope *env; + struct AliceComputationMessage *msg; + struct ComputationBobCryptodataMultipartMessage *mmsg; + uint32_t size; + uint16_t possible; + uint16_t todo; + uint32_t element_count_transfered; + + if (GNUNET_SYSERR == check_unique (elements, + element_count)) + return NULL; + h->mq = GNUNET_CLIENT_connect (cfg, + "scalarproduct-alice", + handlers, + &mq_error_handler, + h); + if (NULL == h->mq) + { + /* misconfigured scalarproduct service */ + GNUNET_break (0); + GNUNET_free (h); + return NULL; + } + h->cont_datum = cont; + h->cont_cls = cont_cls; + h->response_proc = &process_result_message; + h->cfg = cfg; + h->key = *session_key; + + possible = (GNUNET_MAX_MESSAGE_SIZE - 1 - sizeof(struct + AliceComputationMessage)) + / sizeof(struct GNUNET_SCALARPRODUCT_Element); + todo = GNUNET_MIN (possible, + element_count); + size = todo * sizeof(struct GNUNET_SCALARPRODUCT_Element); + env = GNUNET_MQ_msg_extra (msg, + size, + GNUNET_MESSAGE_TYPE_SCALARPRODUCT_CLIENT_TO_ALICE); + msg->element_count_total = htonl (element_count); + msg->element_count_contained = htonl (todo); + msg->reserved = htonl (0); + msg->peer = *peer; + msg->session_key = *session_key; + GNUNET_memcpy (&msg[1], + elements, + size); + GNUNET_MQ_send (h->mq, + env); + element_count_transfered = todo; + possible = (GNUNET_MAX_MESSAGE_SIZE - 1 - sizeof(*mmsg)) + / sizeof(struct GNUNET_SCALARPRODUCT_Element); + while (element_count_transfered < element_count) + { + todo = GNUNET_MIN (possible, + element_count - element_count_transfered); + size = todo * sizeof(struct GNUNET_SCALARPRODUCT_Element); + env = GNUNET_MQ_msg_extra (mmsg, + size, + GNUNET_MESSAGE_TYPE_SCALARPRODUCT_CLIENT_MULTIPART_ALICE); + mmsg->element_count_contained = htonl (todo); + GNUNET_memcpy (&mmsg[1], + &elements[element_count_transfered], + size); + element_count_transfered += todo; + GNUNET_MQ_send (h->mq, + env); + } + return h; +} + + +/** + * Cancel an ongoing computation or revoke our collaboration offer. + * Closes the connection to the service + * + * @param h computation handle to terminate + */ +void +GNUNET_SCALARPRODUCT_cancel (struct GNUNET_SCALARPRODUCT_ComputationHandle *h) +{ + if (NULL != h->mq) + { + GNUNET_MQ_destroy (h->mq); + h->mq = NULL; + } + GNUNET_free (h); +} + + +/* end of scalarproduct_api.c */ diff --git a/src/contrib/service/scalarproduct/test_ecc_scalarproduct.c b/src/contrib/service/scalarproduct/test_ecc_scalarproduct.c new file mode 100644 index 000000000..85460cb05 --- /dev/null +++ b/src/contrib/service/scalarproduct/test_ecc_scalarproduct.c @@ -0,0 +1,212 @@ +/* + This file is part of GNUnet. + Copyright (C) 2015 GNUnet e.V. + + GNUnet is free software: you can redistribute it and/or modify it + under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + + */ +/** + * @file util/test_ecc_scalarproduct.c + * @brief testcase for math behind ECC SP calculation + * @author Christian Grothoff + */ +#include "platform.h" +#include "gnunet_util_lib.h" +#include + +/** + * Global context. + */ +static struct GNUNET_CRYPTO_EccDlogContext *edc; + + +/** + * Perform SP calculation. + * + * @param avec 0-terminated vector of Alice's values + * @param bvec 0-terminated vector of Bob's values + * @return avec * bvec + */ +static int +test_sp (const unsigned int *avec, + const unsigned int *bvec) +{ + unsigned int len; + struct GNUNET_CRYPTO_EccScalar a; + struct GNUNET_CRYPTO_EccScalar a_neg; + struct GNUNET_CRYPTO_EccPoint *g; + struct GNUNET_CRYPTO_EccPoint *h; + struct GNUNET_CRYPTO_EccPoint pg; + struct GNUNET_CRYPTO_EccPoint ph; + + /* determine length */ + for (len = 0; 0 != avec[len]; len++) + ; + if (0 == len) + return 0; + + /* Alice */ + GNUNET_CRYPTO_ecc_rnd_mpi (&a, + &a_neg); + g = GNUNET_new_array (len, + struct GNUNET_CRYPTO_EccPoint); + h = GNUNET_new_array (len, + struct GNUNET_CRYPTO_EccPoint); + for (unsigned int i = 0; i < len; i++) + { + struct GNUNET_CRYPTO_EccScalar tmp; + struct GNUNET_CRYPTO_EccScalar ri; + struct GNUNET_CRYPTO_EccScalar ria; + + GNUNET_CRYPTO_ecc_random_mod_n (&ri); + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_ecc_dexp_mpi (&ri, + &g[i])); + /* ria = ri * a mod L, where L is the order of the main subgroup */ + crypto_core_ed25519_scalar_mul (ria.v, + ri.v, + a.v); + /* tmp = ria + avec[i] */ + { + int64_t val = avec[i]; + struct GNUNET_CRYPTO_EccScalar vali; + + GNUNET_assert (INT64_MIN != val); + GNUNET_CRYPTO_ecc_scalar_from_int (val > 0 ? val : -val, + &vali); + if (val > 0) + crypto_core_ed25519_scalar_add (tmp.v, + ria.v, + vali.v); + else + crypto_core_ed25519_scalar_sub (tmp.v, + ria.v, + vali.v); + } + /* h[i] = g^tmp = g^{ria + avec[i]} */ + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_ecc_dexp_mpi (&tmp, + &h[i])); + } + + /* Bob */ + for (unsigned int i = 0; i < len; i++) + { + struct GNUNET_CRYPTO_EccPoint gm; + struct GNUNET_CRYPTO_EccPoint hm; + + { + int64_t val = bvec[i]; + struct GNUNET_CRYPTO_EccScalar vali; + + GNUNET_assert (INT64_MIN != val); + GNUNET_CRYPTO_ecc_scalar_from_int (val > 0 ? val : -val, + &vali); + if (val < 0) + crypto_core_ed25519_scalar_negate (vali.v, + vali.v); + /* gm = g[i]^vali */ + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_ecc_pmul_mpi (&g[i], + &vali, + &gm)); + /* hm = h[i]^vali */ + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_ecc_pmul_mpi (&h[i], + &vali, + &hm)); + } + if (0 != i) + { + /* pg += gm */ + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_ecc_add (&gm, + &pg, + &pg)); + /* ph += hm */ + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_ecc_add (&hm, + &ph, + &ph)); + } + else + { + pg = gm; + ph = hm; + } + } + GNUNET_free (g); + GNUNET_free (h); + + /* Alice */ + { + struct GNUNET_CRYPTO_EccPoint pgi; + struct GNUNET_CRYPTO_EccPoint gsp; + + /* pgi = pg^inv */ + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_ecc_pmul_mpi (&pg, + &a_neg, + &pgi)); + /* gsp = pgi + ph */ + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_ecc_add (&pgi, + &ph, + &gsp)); + return GNUNET_CRYPTO_ecc_dlog (edc, + &gsp); + } +} + + +/** + * Macro that checks that @a want is equal to @a have and + * if not returns with a failure code. + */ +#define CHECK(want,have) do { \ + if (want != have) { \ + GNUNET_break (0); \ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, \ + "Wanted %d, got %d\n", want, have); \ + GNUNET_CRYPTO_ecc_dlog_release (edc); \ + return 1; \ + } } while (0) + + +int +main (int argc, char *argv[]) +{ + static unsigned int v11[] = { 1, 1, 0 }; + static unsigned int v22[] = { 2, 2, 0 }; + static unsigned int v35[] = { 3, 5, 0 }; + static unsigned int v24[] = { 2, 4, 0 }; + + GNUNET_log_setup ("test-ecc-scalarproduct", + "WARNING", + NULL); + edc = GNUNET_CRYPTO_ecc_dlog_prepare (128, 128); + CHECK (2, test_sp (v11, v11)); + CHECK (4, test_sp (v22, v11)); + CHECK (8, test_sp (v35, v11)); + CHECK (26, test_sp (v35, v24)); + CHECK (26, test_sp (v24, v35)); + CHECK (16, test_sp (v22, v35)); + GNUNET_CRYPTO_ecc_dlog_release (edc); + return 0; +} + + +/* end of test_ecc_scalarproduct.c */ diff --git a/src/contrib/service/scalarproduct/test_scalarproduct.conf b/src/contrib/service/scalarproduct/test_scalarproduct.conf new file mode 100644 index 000000000..c7e2b55f5 --- /dev/null +++ b/src/contrib/service/scalarproduct/test_scalarproduct.conf @@ -0,0 +1,23 @@ +@INLINE@ ../../contrib/conf/gnunet/no_forcestart.conf + +[PATHS] +GNUNET_TEST_HOME = $GNUNET_TMP/test-scalarproduct/ + +[testbed] +OVERLAY_TOPOLOGY = CLIQUE + +[nse] +WORKBITS=0 + +[scalarproduct-bob] +#PREFIX = valgrind --leak-check=yes + +[scalarproduct-alice] +#PREFIX = valgrind --leak-check=yes + +[ats] +WAN_QUOTA_IN = unlimited +WAN_QUOTA_OUT = unlimited + +[peerinfo] +USE_INCLUDED_HELLOS = NO diff --git a/src/contrib/service/scalarproduct/test_scalarproduct.sh b/src/contrib/service/scalarproduct/test_scalarproduct.sh new file mode 100755 index 000000000..ed91cd5c4 --- /dev/null +++ b/src/contrib/service/scalarproduct/test_scalarproduct.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# compute a simple scalar product +# payload for this test: +INPUTALICE="-k CCC -e 'AB,10;RO,3;FL,3;LOL,-1;'" +INPUTBOB="-k CCC -e 'BC,-20000;RO,1000;FL,100;LOL,24;'" +EXPECTED="0CCC" + +# necessary to make the testing prefix deterministic, so we can access the config files +PREFIX=/tmp/test-scalarproduct`date +%H%M%S` + +# where can we find the peers config files? +CFGALICE="-c $PREFIX/0/config" +CFGBOB="-c $PREFIX/1/config" + +# launch two peers in line topology non-interactively +# +# interactive mode would terminate the test immediately +# because the rest of the script is already in stdin, +# thus redirecting stdin does not suffice) +# GNUNET_FORCE_LOG='scalarproduct*;;;;DEBUG' +GNUNET_TESTING_PREFIX=$PREFIX ../testbed/gnunet-testbed-profiler -n -c test_scalarproduct.conf -p 2 & +PID=$! +# sleep 1 is too short on most systems, 2 works on most, 5 seems to be safe +echo "Waiting for peers to start..." +sleep 5 +echo "Running test..." + +which timeout >/dev/null 2>&1 && DO_TIMEOUT="timeout 15" + +# get bob's peer ID, necessary for alice +PEERIDBOB=`${DO_TIMEOUT} gnunet-peerinfo -qs $CFGBOB` + +#GNUNET_LOG=';;;;DEBUG' +${DO_TIMEOUT} gnunet-scalarproduct $CFGBOB $INPUTBOB & +#GNUNET_LOG=';;;;DEBUG' +RESULT=`${DO_TIMEOUT} gnunet-scalarproduct $CFGALICE $INPUTALICE -p $PEERIDBOB` + +# terminate the testbed +kill $PID + +if [ "$RESULT" = "$EXPECTED" ] +then + echo "OK" + exit 0 +else + echo "Result $RESULT, expected $EXPECTED - NOTOK" + exit 1 +fi diff --git a/src/contrib/service/scalarproduct/test_scalarproduct_negative.sh b/src/contrib/service/scalarproduct/test_scalarproduct_negative.sh new file mode 100755 index 000000000..459406836 --- /dev/null +++ b/src/contrib/service/scalarproduct/test_scalarproduct_negative.sh @@ -0,0 +1,49 @@ +#!/bin/bash +# compute a simple scalar product +# payload for this test: +INPUTALICE="-k CCC -e 'AB,10;RO,-3;FL,-3;LOL,1;'" +INPUTBOB="-k CCC -e 'BC,-20000;RO,1000;FL,100;LOL,24;'" + +# necessary to make the testing prefix deterministic, so we can access the config files +unset XDG_DATA_HOME +unset XDG_CONFIG_HOME + +PREFIX=/tmp/test-scalarproduct`date +%H%M%S` + +# where can we find the peers config files? +CFGALICE="-c $PREFIX/0/config" +CFGBOB="-c $PREFIX/1/config" + +which timeout >/dev/null 2>&1 && DO_TIMEOUT="timeout 15" + +# launch two peers in line topology non-interactively +# +# interactive mode would terminate the test immediately +# because the rest of the script is already in stdin, +# thus redirecting stdin does not suffice) +#GNUNET_LOG='scalarproduct;;;;DEBUG' +GNUNET_TESTING_PREFIX=$PREFIX ../testbed/gnunet-testbed-profiler -n -c test_scalarproduct.conf -p 2 & +PID=$! +# sleep 1 is too short on most systems, 2 works on most, 5 seems to be safe +sleep 5 + +# get bob's peer ID, necessary for alice +PEERIDBOB=`${DO_TIMEOUT} gnunet-peerinfo -qs $CFGBOB` + +#GNUNET_LOG=';;;;DEBUG' +${DO_TIMEOUT} gnunet-scalarproduct $CFGBOB $INPUTBOB & +#RESULT=`GNUNET_LOG=';;;;DEBUG' +RESULT=`${DO_TIMEOUT} gnunet-scalarproduct $CFGALICE $INPUTALICE -p $PEERIDBOB` + +# terminate the testbed +kill $PID + +EXPECTED="-0CCC" +if [ "$RESULT" = "$EXPECTED" ] +then + echo "OK" + exit 0 +else + echo "Result $RESULT, expected $EXPECTED NOTOK" + exit 1 +fi diff --git a/src/contrib/service/scalarproduct/test_scalarproduct_negativezero.sh b/src/contrib/service/scalarproduct/test_scalarproduct_negativezero.sh new file mode 100755 index 000000000..6f80b2ea2 --- /dev/null +++ b/src/contrib/service/scalarproduct/test_scalarproduct_negativezero.sh @@ -0,0 +1,46 @@ +#!/bin/bash +# compute a simple scalar product +# payload for this test: +INPUTALICE="-k CCC -e 'AB,10;RO,-1;FL,1;LOL,1;'" +INPUTBOB="-k CCC -e 'BC,20;RO,1;FL,1;LOL,0;'" + +# necessary to make the testing prefix deterministic, so we can access the config files +PREFIX=/tmp/test-scalarproduct`date +%H%M%S` + +# where can we find the peers config files? +CFGALICE="-c $PREFIX/0/config" +CFGBOB="-c $PREFIX/1/config" + +# launch two peers in line topology non-interactively +# +# interactive mode would terminate the test immediately +# because the rest of the script is already in stdin, +# thus redirecting stdin does not suffice) +# GNUNET_LOG='scalarproduct;;;;DEBUG' +GNUNET_TESTING_PREFIX=$PREFIX ../testbed/gnunet-testbed-profiler -n -c test_scalarproduct.conf -p 2 & +PID=$! +# sleep 1 is too short on most systems, 2 works on most, 5 seems to be safe +sleep 5 + +which timeout >/dev/null 2>&1 && DO_TIMEOUT="timeout 15" + +# get bob's peer ID, necessary for alice +PEERIDBOB=`${DO_TIMEOUT} gnunet-peerinfo -qs $CFGBOB` + +#GNUNET_LOG=';;;;DEBUG' +${DO_TIMEOUT} gnunet-scalarproduct $CFGBOB $INPUTBOB & +#GNUNET_LOG=';;;;DEBUG' +RESULT=`${DO_TIMEOUT} gnunet-scalarproduct $CFGALICE $INPUTALICE -p $PEERIDBOB` + +# terminate the testbed +kill $PID + +EXPECTED="00" +if [ "$RESULT" = "$EXPECTED" ] +then + echo "OK" + exit 0 +else + echo "Result $RESULT NOTOK" + exit 1 +fi -- cgit v1.2.3