diff --git a/Makefile b/Makefile index c98b20e..6b4aa62 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ OBJECTS = $(subst .cpp,.o,$(SOURCES)) HEADERS = api.h internal.h mgf1_8x.h sha256avx.h xn_internal.h \ fips202.h fips202x4.h TEST_SOURCES = test_sphincs.cpp test_keygen.cpp test_sign.cpp \ - test_verify.cpp test_thread.cpp test_testvector.cpp \ + test_verify.cpp test_thread.cpp test_testvector.cpp test_fault.cpp \ test_sha512.cpp TESTS = test PQCgenKAT_sign test_sphincs diff --git a/README.md b/README.md index d6525a8..f8419b7 100644 --- a/README.md +++ b/README.md @@ -8,4 +8,8 @@ The specific features that this implements (that the reference code doesn't): - It can support multiple parameter sets at once +- Optional detection of fault attacks + It does assume that you have the AVX2 and AES_NI instructions available, as well as the Posix multithreading API - if not, well, I'll refer you to the Sphincs+ reference code... + +The fault detection works by the simple expedient of 'computing (most) everything twice; compare results'; we do try to ensure that the two computations are isolated (either by time, or being done by different threads) diff --git a/api.h b/api.h index 5f77370..b4c51ad 100644 --- a/api.h +++ b/api.h @@ -146,6 +146,8 @@ class key { // place tasks in different queues based + // place all tasks in the same queue + //k()) - 1; // Get the number of threads num_thread = parm->num_thread; @@ -90,6 +102,10 @@ class work_center { // single thread mode } } + detected_fault = false; + validated_wots = 0; + wrote_wots = 0; + mask = m; } /// Close up shop @@ -115,21 +131,20 @@ class work_center { /// FORS/Merkle trees we'll be usig struct signature_geometry geo; - /// This will hold the root values for the various FORS trees - unsigned char fors_root[max_fors_trees*max_len_hash]; + /// This will hold the root values for the various FORS trees + unsigned char fors_root[2][max_fors_trees*max_len_hash]; - /// This will hold the values to be signed by the Merkle trees - unsigned char merkle_root[max_merkle_tree][max_len_hash]; + /// This will hold the values to be signed by the Merkle trees + unsigned char merkle_root[2][max_merkle_tree][max_len_hash]; - /// This will hold the values to be signed by each half of the Merkle - /// tree + /// This will hold the values to be signed by each half of the Merkle + /// tree unsigned char half_merkle_root[max_merkle_tree][2*max_len_hash]; unsigned half_merkle_done[max_merkle_tree]; //do_it(this); - // Note: we don't have to worry about memory leaks; all - // the task structures come from the same automatic array - // and so will all be freed when we're done +// If we are in fault-detection mode, then this task will go after one +// of the two queues, and stick with it until that queue is empty, +// and only then switching to the other queue +// We put the primary trees in one queue, and secondary in the other +// This maximizes the chance that two different threads will perform +// the redundant jobs (because they'll be on different queues) +// We set half our threads to start on queue 0, and the other half +// on queue 1, hence (assuming there's an even number of threads) +// we have some balance. +void worker::do_job(void) { + for (;;) { + // Get the next task off the work center list starting with the queue + // we're currently on + unsigned starting_index = index & center->mask; + unsigned this_index = starting_index; + task *t; + center->lock(); + for (;;) { + t = center->head_q[this_index]; + if (t) { + center->head_q[this_index] = t->next; + if (!center->head_q[this_index]) { + center->tail_q[this_index] = 0; + } + break; + } + // The current queue is empty; this about stepping to the next one + index += 1; + this_index = (this_index+1) & center->mask; + if (this_index == starting_index) { + // We cycled all the way around; nothing on any of the queues + center->unlock(); + return; + } + } + center->unlock(); + + t->do_it(center); + + // Note: we don't have to worry about memory leaks; all + // the task structures come from the same automatic array + // and so will all be freed when we're done } } /// This is what a child thread runs - it just does the jobs it /// can grab off the list void *worker_thread( void *arg ) { - work_center* center = static_cast(arg); - center->do_job(); + worker* w = static_cast(arg); + w->do_job(); return 0; } @@ -194,28 +255,17 @@ void *worker_thread( void *arg ) { void work_center::enqueue(task *t) { t->next = 0; + // Appending this on either the even queue or the odd queue, based + // on the lsbit of the level (assuming that we actually do use + // two queues) + unsigned index = t->level & mask; lock(); - if (tail_q) { tail_q->next = t; } - tail_q = t; - if (!head_q) { head_q = t; } - - unlock(); -} - -// Get the next task off the queue -task* work_center::next_task(void) { - lock(); - - task* t = head_q; - if (t) { - head_q = t->next; - if (!head_q) tail_q = 0; - } + if (tail_q[index]) { tail_q[index]->next = t; } + tail_q[index] = t; + if (!head_q[index]) { head_q[index] = t; } unlock(); - - return t; } /// @@ -241,7 +291,14 @@ success_flag key::sign( size_t n = len_hash(); unsigned i; - work_center center(this, signature); + int mask; + if (detect_fault) { + mask = 1; // Put the primary tasks in one queue, the secondary + // tasks in another + } else { + mask = 0; // Put all tasks in the same queue + } + work_center center(this, signature, mask); // Step 1: lay out where the various components of the signature are struct signature_geometry& geo = center.geo; @@ -278,18 +335,35 @@ success_flag key::sign( // how many Merkle trees we'll want to split between two tasks unsigned half_tree_start; if (num_thread == 1 || num_log_track() >= merkle_height()) { - // Don't generate any half-trees (either because there's no - // point, or because the trees are so shallow that we can't + // Don't generate any half-trees, either because there's no + // point with one thread, or because the trees are so shallow that + // we can't half_tree_start = d(); + } else if (detect_fault) { + // If we're in fault detection mode, our half-tree logic can't + // handle that; however we don't use fault detection on the top + // merkle tree, so we can do that in half-tree mode + half_tree_start = d() - 1; } else { + // We can do half-trees as we see fit // Here's the logic; start off with having all tracks do full - // trees, until there's not enough trees left to support them - // all - then, switch to half-trees - // We also always do 2 half-trees, to absorb it if some tree - // happened to take longer than others - int target_half_tree_start = d() - 1; - target_half_tree_start -= target_half_tree_start % num_thread; - half_tree_start = target_half_tree_start; + // trees, until there's not enough trees left to support them + // all - then, switch to half-trees + // We also always do 2 half-trees, to absorb it if some tree + // happened to take longer than others + int target_half_tree_start = d() - 1; + target_half_tree_start -= target_half_tree_start % num_thread; + half_tree_start = target_half_tree_start; + } + + // If we're not in fault-detection mode, build only the primary + // tasks + int incr; + if (detect_fault) { + incr = 1; // In fault detection mode, build both the primary + // and secondary trees + } else { + incr = 2; // In normal mode, just build the primary trees } // Step 4: put togther the list of the tasks needed to generate the @@ -297,47 +371,52 @@ success_flag key::sign( // the complete list // We are place the larger tasks first - this makes it more likely // that the tasks complete at about the same time - task task_list[ max_fors_trees + 2*max_merkle_tree ]; + task task_list[ 2*max_fors_trees + 2*max_merkle_tree ]; int num_task = 0; - for (i = 0; i < half_tree_start; i++) { - // Schedule the task to build Merkle tree #i (and write the + for (i = 0; i < 2*half_tree_start; i += incr) { + // Schedule the task to build Merkle tree (and write the // authentication path to the signature) + if (i == 2*d() - 1) continue; // We don't need to build a + // secondary of the top tree task_list[num_task].set_task( &task::build_merkle_tree, i ); center.enqueue( &task_list[num_task] ); num_task++; } - for (; i < d(); i++) { - // Schedule the task to build Merkle tree #i in two halves - task_list[num_task].set_task( &task::build_half_merkle_tree, 2*i ); + // Schedule the task to build the result of the Merkle tree in + // halves + for (; i < 2*d(); i++) { + task_list[num_task].set_task( &task::build_half_merkle_tree, i ); center.enqueue( &task_list[num_task] ); num_task++; - task_list[num_task].set_task( &task::build_half_merkle_tree, 2*i + 1 ); - center.enqueue( &task_list[num_task] ); - num_task++; - center.half_merkle_done[i] = 0; // Neither half is done } - for (i = 0; i < k(); i++) { - // Schedule the task to build FORS tree #i (and write the - // authentication path to the signature) + // And note that we haven't built any half-trees yet + memset( center.half_merkle_done, 0, sizeof center.half_merkle_done ); + + // And schedule the tasks to build the FORS trees + for (i = 0; i < 2*k(); i += incr) { task_list[num_task].set_task( &task::build_fors_tree, i ); center.enqueue( &task_list[num_task] ); num_task++; } + worker w[max_thread]; + for (unsigned i=0; ip; unsigned n = p.len_hash(); unsigned merkle_h = p.merkle_height(); @@ -365,19 +460,23 @@ void task::build_merkle_tree(work_center *w) { p.set_type(wots_addr, ADDR_TYPE_WOTS); p.set_type(tree_addr, ADDR_TYPE_HASHTREE); - p.set_layer_addr(tree_addr, level); - p.set_tree_addr(tree_addr, shr( w->geo.idx_tree, merkle_h * level)); + p.set_layer_addr(tree_addr, actual_level); + p.set_tree_addr(tree_addr, shr( w->geo.idx_tree, merkle_h * actual_level)); p.copy_subtree_addr(wots_addr, tree_addr); // Look up with leaf of the Merkle tree to generate the authentication // path for unsigned idx_leaf; - if (level == 0) { + if (half > 0) { + // We're the secondary; no reason to ask merkle_sign for the + // authentication path + idx_leaf = ~0; + } else if (actual_level == 0) { // Bottom tree - use the index generated from the message hash idx_leaf = w->geo.idx_leaf; } else { // Upper tree - extract the address from the tree index - idx_leaf = (w->geo.idx_tree >> (merkle_h * (level-1))) & + idx_leaf = (w->geo.idx_tree >> (merkle_h * (actual_level-1))) & ((1 << merkle_h) - 1); } @@ -388,17 +487,19 @@ void task::build_merkle_tree(work_center *w) { // And, copy the root we computed to the work center (someone else // will need it) w->lock(); - memcpy( w->sig + w->geo.merkle[level], auth_path, n * merkle_h ); - if (level != p.d()-1) { - memcpy( w->merkle_root[level+1], root, n ); + if (half == 0) { + memcpy( w->sig + w->geo.merkle[actual_level], auth_path, n * merkle_h ); + } + if (actual_level != p.d()-1) { + memcpy( w->merkle_root[half][actual_level+1], root, n ); } w->unlock(); // And if we're not the root, schedule the task that will do the // WOTS signature right above us // Since we're done with this task structure, just reuse it - if (level != p.d()-1) { - set_task( &task::build_wots_sig, level+1 ); + if (actual_level < p.d()-1) { + set_task( &task::build_wots_sig, level+2 ); w->enqueue(this); } } @@ -463,8 +564,8 @@ void task::build_half_merkle_tree(work_center *w) { // will need it) w->lock(); // Copy out the authentication path (if it lies in our half) - // Copy out the top auth path entry (which is our root if the - // leaf is in the other half) + // Copy out the top auth path entry (which is our root if the + // leaf is in the other half) if (in_our_half) { memcpy( w->sig + w->geo.merkle[actual_level], auth_path, len_auth_string ); @@ -482,12 +583,12 @@ void task::build_half_merkle_tree(work_center *w) { if (done != 0x03) { // Still waiting on the other half - return; + return; } if (actual_level == p.d() - 1) { - // We don't actually need to compute the root for the top node - return; + // We don't actually need to compute the root for the top node + return; } // Now compute the root @@ -497,17 +598,22 @@ void task::build_half_merkle_tree(work_center *w) { // And publish the computed value w->lock(); - memcpy( w->merkle_root[actual_level+1], root, n ); + memcpy( w->merkle_root[0][actual_level+1], root, n ); w->unlock(); // And schedule the task that will do the WOTS signature right above us - set_task( &task::build_wots_sig, actual_level+1 ); + set_task( &task::build_wots_sig, 2*(actual_level + 1) ); w->enqueue(this); } // // This is the threaded procedure to generate one FORS authentication path void task::build_fors_tree(work_center *w) { + // The level encodes both the FORS tree index, and if we're the + // primary or the secondary + unsigned index = level / 2; + unsigned half = level & 1; + key& p = *w->p; unsigned n = p.len_hash(); unsigned fors_h = p.t(); @@ -520,51 +626,76 @@ void task::build_fors_tree(work_center *w) { p.set_keypair_addr(wots_addr, w->geo.idx_leaf); // Do the work to generate the FORS signature (for the one FORS tree) - p.fors_sign(signature, root, level, w->geo.fors[level], wots_addr); + p.fors_sign(signature, root, index, w->geo.fors[index], wots_addr); // Copy the FORS signature into where it goes // And, copy the root we computed to the work center // And, mark off this FORS signature as done (and check if that completes // the set) w->lock(); - memcpy( &w->fors_root[level*n], root, n ); - memcpy( w->sig + w->geo.fors_offset[level], signature, n * (fors_h+1) ); - uint64_t done_so_far = w->fors_done | ((uint64_t)1 << level); - w->fors_done = done_so_far; + memcpy( &w->fors_root[half][index*n], root, n ); + if (half == 0) { + memcpy( w->sig + w->geo.fors_offset[index], signature, n * (fors_h+1) ); + } + uint64_t done_so_far = w->fors_done[half] | ((uint64_t)1 << index); + w->fors_done[half] = done_so_far; bool all_fors_trees_done = (done_so_far == w->fors_target); w->unlock(); if (all_fors_trees_done) { // We just finished off the final FORS tree; hash them together to // come up with the FORS root - hash_fors(w); + level = half; + hash_fors(w); } } // // This is the task that generates the WOTS signature void task::build_wots_sig(work_center *w) { + // The level encodes both the WOTS tree level, and if we're the + // primary or the secondary + unsigned actual_level = level / 2; + unsigned half = level & 1; + key& p = *w->p; unsigned merkle_h = p.merkle_height(); unsigned char wots_signature[ max_wots_bytes ]; - uint64_t tree_idx = shr( w->geo.idx_tree, merkle_h * level ); + uint64_t tree_idx = shr( w->geo.idx_tree, merkle_h * actual_level ); unsigned leaf_idx; - if (level == 0) { + if (actual_level == 0) { // Bottom tree - use the index generated from the message hash leaf_idx = w->geo.idx_leaf; } else { // Upper tree - extract the address from the tree index - leaf_idx = (w->geo.idx_tree >> (merkle_h * (level-1))) & + leaf_idx = (w->geo.idx_tree >> (merkle_h * (actual_level-1))) & ((1 << merkle_h) - 1); } - p.wots_sign( wots_signature, level, tree_idx, leaf_idx, - w->merkle_root[level] ); + p.wots_sign( wots_signature, actual_level, tree_idx, leaf_idx, + w->merkle_root[half][actual_level] ); // Copy it to where it is expected to be + size_t sig_len = p.len_hash() * p.wots_digits(); // Length of the WOTS signature w->lock(); - memcpy( w->sig + w->geo.wots[level], wots_signature, - p.len_hash() * p.wots_digits() ); + if (w->wrote_wots & (1 << actual_level)) { + // We already wrote the WOTS signature; check if we got + // the same one + if (0 == memcmp( w->sig + w->geo.wots[actual_level], wots_signature, + sig_len )) { + // We got the same one - mark this level as double-checked + w->validated_wots |= (1 << actual_level); + } else { + // Got something different - call a foul + w->detected_fault = true; + memset( w->sig + w->geo.wots[actual_level], 0, sig_len ); + } + } else { + // First time for this signature; write it out + memcpy( w->sig + w->geo.wots[actual_level], wots_signature, + sig_len ); + w->wrote_wots |= (1 << actual_level); + } w->unlock(); } @@ -573,6 +704,7 @@ void task::build_wots_sig(work_center *w) { // This is a comparatively simple task (and doesn't involve AVX at all), but // one that must come after all the FORS trees have been built void task::hash_fors(work_center *w) { + unsigned half = level; // This is 0 for the primary, 1 for the secondary unsigned char hash_result[ max_len_hash ]; key& p = *w->p; addr_t fors_pk_addr = { 0 }; @@ -581,15 +713,15 @@ void task::hash_fors(work_center *w) { p.set_tree_addr(fors_pk_addr, w->geo.idx_tree); p.set_keypair_addr(fors_pk_addr, w->geo.idx_leaf); p.set_type(fors_pk_addr, ADDR_TYPE_FORSPK); - p.thash(hash_result, w->fors_root, p.k(), fors_pk_addr); + p.thash(hash_result, w->fors_root[half], p.k(), fors_pk_addr); // Copy it to where it is expected to be w->lock(); - memcpy( w->merkle_root[0], hash_result, p.len_hash() ); + memcpy( w->merkle_root[half][0], hash_result, p.len_hash() ); w->unlock(); // Schedule it to be signed with the bottom WOTS signature - set_task( &task::build_wots_sig, 0 ); + set_task( &task::build_wots_sig, half ); w->enqueue(this); } diff --git a/sphincs-fast.cpp b/sphincs-fast.cpp index 65e97e2..f83523b 100644 --- a/sphincs-fast.cpp +++ b/sphincs-fast.cpp @@ -150,6 +150,7 @@ key::key(void) { offset_tree_index = 28; num_thread = default_thread; + detect_fault = default_detect_fault; } key::~key(void) { diff --git a/stl.cpp b/stl.cpp index 7a5a893..211cf73 100644 --- a/stl.cpp +++ b/stl.cpp @@ -20,8 +20,13 @@ std::unique_ptr key::sign( message, len_message, rand); if (worked != success) { - throw std::runtime_error( "no Sphincs+ private key" ); // The only - // possible reason sign would generate an error + if (!have_private_key) { + // We rather need a private key to sign + throw std::runtime_error( "no Sphincs+ private key" ); + } else { + // The only other possible failure reason + throw std::runtime_error( "fault detected during Sphincs+ signature process" ); + } } return signature; diff --git a/test_fault.cpp b/test_fault.cpp new file mode 100644 index 0000000..a2c4633 --- /dev/null +++ b/test_fault.cpp @@ -0,0 +1,243 @@ +// +// This tests out the fault detection logic of Sphincs+ +// +// This works by injecting errors and seeing what happens + +#include +#include +#include +#include +#include "api.h" +#include "test_sphincs.h" + +enum f_type { prf, f, thash_f, // The types of functions that we can inject + // a fault into; that is, cause to return an + // incorrect value + f_type_count }; + +static enum f_type next_test( enum f_type f ) { + return (enum f_type)(f + 1); +} + +// +// This is a specialized key which can programmably miscompute +// This declares alternative virtual functions that can introduce +// errors; this way, we can introduce faults without changing the +// logic of the signer +// +// Deriving a subclass based on the key type and redefining its +// internal functions is evil (a necessary evil in this case, but +// still evil) +// Don't try this at home +// +// We run this test against a single parameter set; I don't expect +// fault detection to be parameter-set specific, so I just picked +// the fastest one +class faulty_key : public sphincs_plus::key_haraka_128f_simple { + typedef sphincs_plus::key_haraka_128f_simple parent; + bool do_error; // If false, we're not injecting a fault + enum f_type what_type; // If we are injecting a fault, which function + // are we doing it to? + uint32_t count[ f_type_count ]; // The number of times each function + // has been used since the last reset_count() + uint32_t target_count; // If the function in queston has been used + // precisely target_count times, then inject the fault +protected: + // These are the instrumented internal functions + virtual void prf_addr_xn(unsigned char **out, + const sphincs_plus::addr_t* addrxn); + virtual void f_xn(unsigned char **out, unsigned char **in, + sphincs_plus::addr_t* addr); + virtual void thash(unsigned char *out, + const unsigned char *in, + unsigned int inblocks, sphincs_plus::addr_t addr); + virtual void thash_xn(unsigned char **out, + unsigned char **in, + unsigned int inblocks, sphincs_plus::addr_t* addrxn); + // Note that we dont't try to tweak the h_msg and prf_msg + // functions; those wouldn't cause an exploitable error +public: + faulty_key(void) { do_error = false; } + + void reset_count(void) { memset( count, 0, sizeof count ); } + uint32_t get_count(enum f_type type) { return count[ type ]; } + + // Set a fault to happen in the future + void set_error(enum f_type type, uint32_t target) { + do_error = true; what_type = type; target_count = target; + } + + // On a fault, we don't care if the top level Merkle + // signature was affected (as that can't be leveraged into + // a forgery); hence we don't need to compare that part of + // the signature. That Merkle signature happens to be the last + // part of the Sphincs+ signature, hence we just test the + // earlier parts + size_t len_critial_sig(void) { + return len_signature() - merkle_height() * len_hash(); + } +}; + +void faulty_key::prf_addr_xn(unsigned char **out, + const sphincs_plus::addr_t* addrxn) { + parent::prf_addr_xn(out, addrxn); + if (do_error && what_type == prf && count[prf] == target_count) { + // Inject an error + out[0][0] ^= 0x01; + } + count[prf]++; +} + +void faulty_key::f_xn(unsigned char **out, unsigned char **in, + sphincs_plus::addr_t* addr) { + parent::f_xn(out, in, addr); + if (do_error && what_type == f && count[f] == target_count) { + // Inject an error + out[0][0] ^= 0x01; + } + count[f]++; +} + +void faulty_key::thash(unsigned char *out, + const unsigned char *in, + unsigned int inblocks, sphincs_plus::addr_t addr) { + parent::thash(out, in, inblocks, addr); + if (do_error && what_type == thash_f && count[thash_f] == target_count) { + // Inject an error + out[0] ^= 0x01; + } + count[thash_f]++; +} + +void faulty_key::thash_xn(unsigned char **out, + unsigned char **in, + unsigned int inblocks, sphincs_plus::addr_t* addrxn) { + parent::thash_xn(out, in, inblocks, addrxn); + if (do_error && what_type == thash_f && count[thash_f] == target_count) { + // Inject an error + out[0][0] ^= 0x01; + } + count[thash_f]++; +} + +bool test_fault(bool fast_flag, enum noise_level level) { + faulty_key k; + + // Generate the key + if (!k.generate_key_pair()) { + printf( "*** KEY GENERATION FAILED\n" ); + return false; + } + + const unsigned char msg[] = "Hello"; + const unsigned int msg_len = 5; + + // Generate the known good signature (turning off fault detection) + k.set_fault_detection(false); + auto sig = k.sign( msg, msg_len, 0 ); + unsigned sig_len = k.len_signature(); + + // To be thorough, check if the signature validates + if (!k.verify( sig.get(), sig_len, msg, msg_len )) { + printf( "*** INITIAL SIGNATURE GENERATION DID NOT VALIDATE\n" ); + return false; + } + + // Threading will confuse our faulting logic - turn it off + k.set_num_thread(1); + + // Turn on the fault detection logic + k.set_fault_detection(true); + + // Try to generate a signature (and while we're at it, count how + // many times each function is called) + k.reset_count(); + try { + auto sig2 = k.sign( msg, msg_len, 0 ); + // We generated a signature; make sure it's the same + if (0 != memcmp( sig.get(), sig2.get(), sig_len )) { + printf( "*** TURNING ON FAULT DETECTION CHANGED THE SIGNATURE\n" ); + return false; + } + } catch(std::exception& e) { + printf( "*** FAULT DETECTED WHEN ONE WAS NOT INJECTED\n" ); + return false; + } + + // Now, we'll introduce faults, and see what happens + + // On the fast test, introduce errors only occasionally + // On a full test, introduce errors at every possible location + unsigned incr; + if (fast_flag) incr = 43; else incr = 1; + + // First of all, characterize how many times each function is called + // while we're generating the signature + uint32_t count[f_type_count]; + uint32_t total_tests = 0; + for (enum f_type test = prf; test < f_type_count; test = next_test(test)) { + count[test] = k.get_count(test); + total_tests += (count[test] + incr - 1) / incr; + } + + size_t len_crit = k.len_critial_sig(); + + // Now, repeatedly generate signatures, while introducing faults at + // systematically applied locations + uint32_t count_test = 0; + int last_percentage = -1; + for (enum f_type test = prf; test < f_type_count; test = next_test(test)) { + if (level == loud) { + const char* testname; + switch(test) { + case prf: testname = "PRF"; break; + case f: testname = "F"; break; + case thash_f: testname = "THASH"; break; + default: testname = "???"; break; + } + printf( " Testing whether faults during %s are detected\n", + testname ); + } + uint32_t attempts = 0; + uint32_t error_caught = 0; + + for (uint32_t pos = 0; pos < count[test]; pos += incr) { + if (level != quiet) { + int this_percentage = 100.0 * count_test / total_tests; + if (this_percentage != last_percentage) { + printf( " %d%%\r", this_percentage ); + fflush(stdout); + last_percentage = this_percentage; + } + } + count_test++; + attempts++; + k.reset_count(); + k.set_error(test, pos); // Cause the pos'th evaluation of the + // function indicated by test to be wrong + try { + auto sig2 = k.sign( msg, msg_len, 0 ); + // We generated a signature withot the fault detection logic + // triggering (which can happen if the function was used in + // the top level Merkle tree, or we tweaked a track that + // wasn't actually used); make sure the parts we care about + // have not changed + if (0 != memcmp( sig.get(), sig2.get(), len_crit )) { + printf( "*** UNDETECTED FAULT: test = %d pos = %u\n", + (int)test, (unsigned)pos ); + return false; + } + } catch(std::exception& e) { + // We detected the error + error_caught++; + } + } + + if (2*error_caught < attempts) { + printf( "*** SOMETHING'S WRONG WITH THE TEST: DETECTED FAULT %u out of %u trials\n", error_caught, attempts ); + return false; + } + } + + return true; +} diff --git a/test_sphincs.cpp b/test_sphincs.cpp index b9d50d6..35df845 100644 --- a/test_sphincs.cpp +++ b/test_sphincs.cpp @@ -24,6 +24,7 @@ static struct { { "sign", test_sign, "signature generation test", false, 0 }, { "verify", test_verify, "signature verification test", true, 0 }, { "thread", test_thread, "threading test", false, 0 }, + { "fault", test_fault, "fault detection test", false, 0 }, /* Add more here */ }; diff --git a/test_sphincs.h b/test_sphincs.h index 429bd6c..2d68b67 100644 --- a/test_sphincs.h +++ b/test_sphincs.h @@ -8,5 +8,6 @@ extern bool test_keygen(bool fast_flag, enum noise_level level); extern bool test_sign(bool fast_flag, enum noise_level level); extern bool test_verify(bool fast_flag, enum noise_level level); extern bool test_thread(bool fast_flag, enum noise_level level); +extern bool test_fault(bool fast_flag, enum noise_level level); #endif /* TEST_SPHINCS_H_ */