/* This file is part of libbrandt. * Copyright (C) 2016 GNUnet e.V. * * libbrandt is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later * version. * * libbrandt 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along with * libbrandt. If not, see . */ /** * @file test_brandt.c * @brief testing API functions. * @author Markus Teich */ #include "platform.h" #include #include #include #include "brandt.h" #include "crypto.h" #include "util.h" #define MIN(A, B) ((A) < (B) ? (A) : (B)) struct msg { uint16_t sender; uint16_t receiver; void *buf; size_t buf_len; }; struct testcase { uint16_t n; uint16_t k; uint16_t *bids; uint16_t m; uint16_t outcome_public; uint16_t ret; struct BRANDT_Auction **ad; uint16_t *id; uint16_t *result_called; /* key material for signatures */ struct GNUNET_CRYPTO_EddsaPrivateKey *prv; struct GNUNET_CRYPTO_EddsaPublicKey *pub; struct msg **tr; /* transcript of the messages */ size_t tr_idx; struct BRANDT_Auction *rad; /* auction for replay */ struct BRANDT_Result *res; /* result for transcript */ size_t res_len; }; static struct testcase tcase; static struct GNUNET_CRYPTO_EccDlogContext *edc; static struct BRANDT_Result * expected_outcome (uint16_t i, uint16_t *rlen) { struct BRANDT_Result *ret = NULL; int32_t highest_bidder = -1; int32_t highest_bid = -1; int32_t mpf_highest_bidder; int32_t mpf_highest_bid = -1; int32_t prev_mpf_highest_bidder = -1; uint16_t winners = MIN (tcase.m, tcase.n); uint16_t cur_winner = 0; *rlen = 0; if (0 == tcase.n) return NULL; if (0 == tcase.m) { for (uint16_t h = 0; h < tcase.n; h++) if (tcase.bids[h] > highest_bid) highest_bid = tcase.bids[highest_bidder = h]; if (!tcase.outcome_public && !(i == highest_bidder || i == tcase.n)) return NULL; ret = GNUNET_new (struct BRANDT_Result); ret->bidder = highest_bidder; ret->price = highest_bid; ret->status = BRANDT_bidder_won; *rlen = 1; return ret; } /* fewer bidders than needed -> everyone wins with lowest price */ if (tcase.n <= tcase.m) { if (tcase.outcome_public || i == tcase.n) { ret = GNUNET_new_array ((*rlen = tcase.n), struct BRANDT_Result); for (uint16_t h = 0; h < tcase.n; h++) { ret[h].bidder = h; ret[h].price = 0; ret[h].status = BRANDT_bidder_won; } } else { ret = GNUNET_new (struct BRANDT_Result); ret->bidder = i; ret->price = 0; ret->status = BRANDT_bidder_won; *rlen = 1; } return ret; } /* find M+1st highest bidder to determine selling price */ for (uint16_t h = 0; h < tcase.n; h++) if (tcase.bids[h] > mpf_highest_bid) mpf_highest_bid = tcase.bids[prev_mpf_highest_bidder = h]; for (uint16_t m = 0; m < MIN (tcase.m, tcase.n - 1); m++) { mpf_highest_bidder = -1; mpf_highest_bid = -1; for (uint16_t h = 0; h < tcase.n; h++) { if (tcase.bids[h] > mpf_highest_bid && (tcase.bids[h] < tcase.bids[prev_mpf_highest_bidder] || (tcase.bids[h] == tcase.bids[prev_mpf_highest_bidder] && h > prev_mpf_highest_bidder))) { mpf_highest_bid = tcase.bids[mpf_highest_bidder = h]; } } prev_mpf_highest_bidder = mpf_highest_bidder; } /* for simplicity always locate the big block if we need to report at * least one winner. with private outcome for losing bidders or winners * only none or one element will be used respectively. */ if (tcase.outcome_public || i == tcase.n || tcase.bids[i] > mpf_highest_bid || (tcase.bids[i] == mpf_highest_bid && i < mpf_highest_bidder)) ret = GNUNET_new_array (winners, struct BRANDT_Result); /* report winners */ for (uint16_t h = 0; h < tcase.n; h++) { if (((tcase.bids[h] == mpf_highest_bid && h < mpf_highest_bidder) || tcase.bids[h] > mpf_highest_bid) && /* h is a winner */ (tcase.outcome_public || i == h || i == tcase.n)) /* needs report */ { if (cur_winner >= winners) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "got too many winners\n"); _exit (1); } ret[cur_winner].bidder = h; ret[cur_winner].price = mpf_highest_bid; ret[cur_winner].status = BRANDT_bidder_won; cur_winner++; } } *rlen = cur_winner; return ret; } static void bidder_start (void *arg) { uint16_t i = *(uint16_t *) arg; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "starting bidder %d\n", i); BRANDT_bidder_start (tcase.ad[i], i, tcase.n); } static void transfer_message (void *arg) { struct msg *m = (struct msg *) arg; struct msg_head *h = (struct msg_head *) m->buf; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "xfer msg %d %p from %d to %d\n", ntohl (h->msg_type), arg, m->sender, m->receiver); /** keep a transcript as the seller **/ if (tcase.n == m->receiver) { struct msg *m2 = GNUNET_new (struct msg); m2->sender = m->sender; m2->buf_len = m->buf_len; m2->buf = GNUNET_malloc (m->buf_len); GNUNET_memcpy (m2->buf, m->buf, m->buf_len); tcase.tr[tcase.tr_idx++] = m2; } BRANDT_got_message (tcase.ad[m->receiver], m->sender, m->buf, m->buf_len); GNUNET_free (m->buf); GNUNET_free (m); } static uint16_t cb_start (void *auction_closure) { uint16_t *s = (uint16_t *) auction_closure; if (tcase.n != *s) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "start callback called from bidder\n"); _exit (1); } for (uint16_t i = 0; i < tcase.n; i++) GNUNET_SCHEDULER_add_now (&bidder_start, &tcase.id[i]); return tcase.n; } static int cb_broadcast (void *auction_closure, const void *msg, size_t msg_len) { uint16_t *s = (uint16_t *) auction_closure; struct msg *m; for (uint16_t i = 0; i <= tcase.n; i++) { if (i == *s) continue; m = GNUNET_new (struct msg); m->sender = *s; m->receiver = i; m->buf = GNUNET_new_array (msg_len, unsigned char); memcpy (m->buf, msg, msg_len); m->buf_len = msg_len; GNUNET_SCHEDULER_add_now (&transfer_message, m); } return 0; } static int cb_unicast (void *auction_closure, const void *msg, size_t msg_len) { uint16_t *s = (uint16_t *) auction_closure; struct msg *m; m = GNUNET_new (struct msg); m->sender = *s; m->receiver = tcase.n; /* == seller */ m->buf = GNUNET_new_array (msg_len, unsigned char); memcpy (m->buf, msg, msg_len); m->buf_len = msg_len; GNUNET_SCHEDULER_add_now (&transfer_message, m); return 0; } static void cb_result (void *auction_closure, struct BRANDT_Result results[], uint16_t results_len) { uint16_t *s = (uint16_t *) auction_closure; uint16_t mustlen = -1; struct BRANDT_Result *must = expected_outcome (*s, &mustlen); if (mustlen != results_len) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "expected result len is: %d\n", mustlen); GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "computed result len is: %d (by agent %d)\n", results_len, *s); tcase.ret = 1; goto quit; } if (0 == results_len && NULL != must) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "expected result is: %p\n", (void *) must); GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "computed result is: (nil) (by agent %d)\n", *s); tcase.ret = 1; } for (uint16_t i = 0; i < results_len; i++) { GNUNET_log ( GNUNET_ERROR_TYPE_INFO, "[%s] expected result is: bidder %d got status %d with price %d\n", tcase.n == *s ? "seller" : "bidder", must[i].bidder, must[i].status, must[i].price); GNUNET_log ( GNUNET_ERROR_TYPE_INFO, "[%s] computed result is: bidder %d got status %d with price %d (by agent %d)\n", tcase.n == *s ? "seller" : "bidder", results[i].bidder, results[i].status, results[i].price, *s); if (NULL == must || must[i].bidder != results[i].bidder || must[i].status != results[i].status || must[i].price != results[i].price) tcase.ret = 1; } if (*s == tcase.n) { /* save the results of the seller */ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Saving results, len %d\n", results_len); tcase.res = results; tcase.res_len = results_len; } quit: tcase.result_called[*s] = 1; if (must) GNUNET_free (must); } static void run_auction (void *arg) { void *desc; size_t desc_len; tcase.ad[tcase.n] = BRANDT_new (&cb_result, &cb_broadcast, &cb_start, &tcase.id[tcase.n], &desc, &desc_len, GNUNET_TIME_absolute_get (), GNUNET_TIME_UNIT_MINUTES, tcase.k, /* number of prizes */ tcase.m, /* m */ tcase.outcome_public, /* outcome public */ tcase.outcome_public ? edc : NULL); if (!tcase.ad[tcase.n]) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "BRANDT_new() failed.\n"); _exit (1); } for (uint16_t i = 0; i < tcase.n; i++) { tcase.ad[i] = BRANDT_join (&cb_result, &cb_broadcast, &cb_unicast, &tcase.id[i], desc, desc_len, tcase.bids[i], /* bid */ tcase.outcome_public ? edc : NULL); if (!tcase.ad[i]) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "BRANDT_join() failed.\n"); tcase.ret = 1; return; } if (tcase.ad[tcase.n]->k != tcase.ad[i]->k || tcase.ad[tcase.n]->m != tcase.ad[i]->m || tcase.ad[tcase.n]->outcome_public != tcase.ad[i]->outcome_public || tcase.ad[tcase.n]->time_start.abs_value_us != tcase.ad[i]->time_start.abs_value_us || tcase.ad[tcase.n]->time_round.rel_value_us != tcase.ad[i]->time_round.rel_value_us || !tcase.ad[tcase.n]->seller_mode || /* todo: split out */ tcase.ad[i]->seller_mode) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "error/mismatch in basic auction data\n"); tcase.ret = 1; return; } } } static void tr_result (void *auction_closure, struct BRANDT_Result results[], uint16_t results_len) { uint16_t *s = (uint16_t *) auction_closure; uint16_t mustlen = -1; struct BRANDT_Result *must = expected_outcome (*s, &mustlen); if (mustlen != results_len) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "expected result len is: %d\n", mustlen); GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "computed result len is: %d (by seller)\n", results_len); tcase.ret = 1; goto quit; } if (0 == results_len && NULL != must) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "expected result is: %p\n", (void *) must); GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "computed result is: (nil) (by seller)\n"); tcase.ret = 1; } for (uint16_t i = 0; i < results_len; i++) { GNUNET_log ( GNUNET_ERROR_TYPE_INFO, "REPLAY [seller] expected result is: bidder %d got status %d with price %d\n", must[i].bidder, must[i].status, must[i].price); GNUNET_log ( GNUNET_ERROR_TYPE_INFO, "REPLAY [seller] computed result is: bidder %d got status %d with price %d\n", results[i].bidder, results[i].status, results[i].price); if (NULL == must || must[i].bidder != results[i].bidder || must[i].status != results[i].status || must[i].price != results[i].price) tcase.ret = 1; } quit: tcase.result_called[*s] = 1; if (must) GNUNET_free (must); } static uint16_t tr_start (void *auction_closure) { void resend (void *x) { size_t i = (size_t) x; struct msg *m = tcase.tr[i]; if (NULL == m) { dprintf (2, "REPLAY skipping empty msg no. %ld\n", i); return; } dprintf (2, "REPLAY sent msg no. %ld\n", i); BRANDT_got_message (tcase.rad, m->sender, m->buf, m->buf_len); } dprintf (2, "REPLAY start replay auction\n"); for (size_t i = 0; i < 4 * tcase.n; i++) GNUNET_SCHEDULER_add_now (&resend, (void *) i); return tcase.n; } struct some_sig { struct GNUNET_CRYPTO_EccSignaturePurpose purpose; struct GNUNET_HashCode hc GNUNET_PACKED; }; static void print_transcript (uint32_t highestprice) { struct BRANDT_Auction *ad = tcase.ad[tcase.n]; json_t *root; json_t *auction; json_t *prices; json_t *bidders; json_t *sigs; json_t *transcript; json_t *winners; struct GNUNET_HashCode hc; struct some_sig p = { .purpose.size = htonl (sizeof(struct some_sig)) }; char price[256]; prices = json_array (); GNUNET_assert (prices); for (size_t p = 0; p < tcase.k; p++) { sprintf (price, "EUR:%d", highestprice--); GNUNET_assert ( -1 != json_array_append_new ( prices, json_string (price))); } auction = json_pack ("{s:{s:o}, s:{s:o}, s:i, s:b, s:o, s:s, s:s}", "time_start", "t_s", json_integer ( ad->time_start.abs_value_us / 1000LL), "time_round", "d_us", json_integer ( ad->time_round.rel_value_us), "type", ad->m, "is_public", ad->outcome_public == 0 ? json_false () : json_true (), "prices", prices, "payto_uri", "payto://some/iban", "pubkey", GNUNET_CRYPTO_eddsa_public_key_to_string ( &tcase.pub[tcase.n])); GNUNET_assert (auction); bidders = json_array (); GNUNET_assert (bidders); for (size_t b = 0; b < tcase.n; b++) { char *ps = GNUNET_CRYPTO_eddsa_public_key_to_string (&tcase.pub[b]); GNUNET_assert (-1 != json_array_append_new (bidders, json_string (ps))); } // Add signatures form each bidder for the auction. sigs = json_array (); GNUNET_assert (sigs); { char *auc_js = json_dumps (auction, JSON_COMPACT); GNUNET_CRYPTO_hash (auc_js, strlen (auc_js), &hc); p.purpose.purpose = htonl (23); p.hc = hc; for (size_t b = 0; b < tcase.n; b++) { struct GNUNET_CRYPTO_EddsaSignature sig; GNUNET_CRYPTO_eddsa_sign (&tcase.prv[b], &p, &sig); GNUNET_assert (-1 != json_array_append_new ( sigs, GNUNET_JSON_from_data_auto (&sig))); } } transcript = json_array (); GNUNET_assert (transcript); for (size_t i = 0; i < 4 * tcase.n; i++) { json_t *entry; struct msg *msg = tcase.tr[i]; struct GNUNET_CRYPTO_EddsaSignature sig; if (NULL == msg) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "skipping NULL msg[%ld]\n", i); continue; } GNUNET_assert (msg); GNUNET_CRYPTO_hash (msg->buf, msg->buf_len, &hc); p.purpose.purpose = htonl (42); p.hc = hc; GNUNET_CRYPTO_eddsa_sign ( &tcase.prv[msg->sender], &p, &sig); entry = json_pack ("{s:i, s:o, s:o}", "bidder", msg->sender, "msg", GNUNET_JSON_from_data (msg->buf, msg->buf_len), "sig", GNUNET_JSON_from_data_auto (&sig)); GNUNET_assert (entry); GNUNET_assert (-1 != json_array_append_new (transcript, entry)); } winners = json_array (); GNUNET_assert (winners); // add the winner(s) { for (uint16_t i = 0; i < tcase.res_len; i++) { json_t *res = json_pack ("{s:i, s:i, s:o*}", "bidder", tcase.res[i].bidder, "price_idx", tcase.res[i].price, "price", json_array_get (prices, tcase.res[i].price)); GNUNET_assert (res); GNUNET_assert (-1 != json_array_append_new (winners, res)); } } root = json_pack ("{s:o, s:o, s:o, s:o, s:o}", "auction", auction, "bidders", bidders, "signatures", sigs, "transcript", transcript, "winners", winners); GNUNET_assert (root); // Add signature of seller to H(auction, bidders, signatures, transcript, result) { struct GNUNET_CRYPTO_EddsaSignature sig; char *root_js = json_dumps (root, JSON_COMPACT | JSON_SORT_KEYS); GNUNET_CRYPTO_hash (root_js, strlen (root_js), &hc); p.purpose.purpose = htonl (815); p.hc = hc; GNUNET_CRYPTO_eddsa_sign (&tcase.prv[tcase.n], &p, &sig); GNUNET_assert (-1 != json_object_set_new ( root, "sig", GNUNET_JSON_from_data_auto (&sig))); free (root_js); } // After signing the transcript, add private key material, too, so it // can be used in integration tests. { json_t *keys = json_array (); GNUNET_assert (keys); for (size_t b = 0; b <= /* = is important */ tcase.n; b++) { char *ps = GNUNET_CRYPTO_eddsa_private_key_to_string (&tcase.prv[b]); GNUNET_assert (-1 != json_array_append_new (keys, json_string (ps))); } json_object_set_new ( root, "NOTSIGNED_keys", keys); } printf ("\n%s\n", json_dumps (root, JSON_INDENT (2))); json_decref (root); } static void replay_transcript (void *arg) { void *desc; size_t desc_len; static struct GNUNET_CRYPTO_EccDlogContext *redc; print_transcript (96); redc = GNUNET_CRYPTO_ecc_dlog_prepare (1024, 16); dprintf (2,"REPLAY, calling BRANDT_new...\n"); tcase.rad = BRANDT_new (&tr_result, NULL, &tr_start, &tcase.id[tcase.n], &desc, &desc_len, tcase.ad[tcase.n]->time_start, tcase.ad[tcase.n]->time_round, tcase.k, /* number of prizes */ tcase.m, /* m */ tcase.outcome_public, /* outcome public */ tcase.outcome_public ? redc : NULL); if (!tcase.rad) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "REPLAY BRANDT_new() failed.\n"); _exit (1); } } /** * Test a specific auction setup. * * @param[in] n number of bidders. * @param[in] k number of different prices. * @param[in] bids The bid array. Must contain exactly @a n elements each less * than @a k. * @param[in] m If 0 it's a first price auction, else an M+1st price auction * specifying the M. * @param[in] outcome_public 1 means public outcome, 0 means private outcome. * @return 0 on success, 1 on failure */ static int test_auction (uint16_t n, uint16_t k, uint16_t *bids, uint16_t m, uint16_t outcome_public) { tcase.n = n; tcase.k = k; tcase.bids = bids; tcase.m = m; tcase.outcome_public = outcome_public; tcase.ret = 0; tcase.res = NULL; tcase.res_len = 0; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "######################################\n"); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "testing %s auction with m = %d and %s outcome\n", tcase.m > 0 ? "M+1ST PRICE" : "FIRST PRICE", tcase.m, tcase.outcome_public ? "PUBLIC" : "PRIVATE"); /** \todo: output bids */ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "######################################\n"); tcase.ad = GNUNET_new_array (tcase.n + 1, struct BRANDT_Auction *); tcase.id = GNUNET_new_array (tcase.n + 1, uint16_t); tcase.prv = GNUNET_new_array (tcase.n + 1, struct GNUNET_CRYPTO_EddsaPrivateKey); tcase.pub = GNUNET_new_array (tcase.n + 1, struct GNUNET_CRYPTO_EddsaPublicKey); for (uint16_t i = 0; i <= tcase.n; i++) { tcase.id[i] = i; GNUNET_CRYPTO_eddsa_key_create (&tcase.prv[i]); GNUNET_CRYPTO_eddsa_key_get_public (&tcase.prv[i], &tcase.pub[i]); } tcase.result_called = GNUNET_new_array (tcase.n + 1, uint16_t); tcase.tr = GNUNET_new_array (4 * tcase.n, struct msg *); /* transcript, one message per bidder per round */ GNUNET_SCHEDULER_run (&run_auction, NULL); for (uint16_t i = 0; i <= tcase.n; i++) { BRANDT_destroy (tcase.ad[i]); if (!tcase.result_called[i]) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "result callback not called for bidder %d\n", i); tcase.ret = 1; } } GNUNET_SCHEDULER_run (&replay_transcript, NULL); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "DONE testing auction\n"); tcase.tr_idx = 0; GNUNET_free (tcase.tr); GNUNET_free (tcase.ad); GNUNET_free (tcase.id); GNUNET_free (tcase.result_called); GNUNET_free (tcase.prv); GNUNET_free (tcase.pub); return tcase.ret; } int main (int argc, char *argv[]) { int ret = 0; if (GNUNET_OK != GNUNET_log_setup ("test_brandt", "INFO", NULL)) return 1; edc = GNUNET_CRYPTO_ecc_dlog_prepare (1024, 16); BRANDT_init (); ret |= 0 || /* // zero bidders test_auction (0, 2, NULL, 0, 0) || test_auction (0, 2, NULL, 0, 1) || test_auction (0, 2, NULL, 1, 0) || test_auction (0, 2, NULL, 2, 0) || // too few bidders => outcome is lowest possible price test_auction (1, 2, (uint16_t[]) { 1 }, 1, 0) || test_auction (1, 2, (uint16_t[]) { 0 }, 2, 0) || test_auction (2, 2, (uint16_t[]) { 1, 0 }, 2, 0) || test_auction (2, 2, (uint16_t[]) { 1, 0 }, 1, 0) || test_auction (3, 2, (uint16_t[]) { 0, 0, 1 }, 2, 0) || // general checks of all four algorithms test_auction (3, 2, (uint16_t[]) { 0, 1, 1 }, 0, 0) || test_auction (3, 2, (uint16_t[]) { 0, 1, 1 }, 0, 1) || */ // test_auction (3, 5, (uint16_t[]) { 4, 3, 1 }, 0, 1) || // test_auction (3, 5, (uint16_t[]) { 1, 2, 3 }, 0, 1) || test_auction (3, 5, (uint16_t[]) { 1, 2, 3 }, 0, 1) || test_auction (5, 6, (uint16_t[]) {5,4,2,1,0,3}, 1, 1) || // test_auction (5, 6, (uint16_t[]) {5,4,2,1,0,3}, 1, 0) || test_auction (10, 3, (uint16_t[]) {1,2,0,1,1,0,1,0,1,0}, 0, 1) || test_auction (10, 6, (uint16_t[]) {3,4,5,2,1,0,1,2,3,4}, 2, 1) || // test_auction (10, 3, (uint16_t[]) {1,2,0,1,1,0,1,0,1,0}, 1, 0) || 0; GNUNET_CRYPTO_ecc_dlog_release (edc); return ret; }