diff --git a/applications/system/mfkey/application.fam b/applications/system/mfkey/application.fam index 5ece9e777..ec5b730b2 100644 --- a/applications/system/mfkey/application.fam +++ b/applications/system/mfkey/application.fam @@ -8,14 +8,13 @@ App( "gui", "storage", ], - stack_size=1 * 1024, + stack_size=8 * 1024, fap_icon="mfkey.png", fap_category="NFC", fap_author="@noproto", fap_icon_assets="images", - fap_weburl="https://github.com/noproto/FlipperMfkey", fap_description="MIFARE Classic key recovery tool", - fap_version="4.0", + fap_version="4.1", ) App( diff --git a/applications/system/mfkey/crypto1.c b/applications/system/mfkey/crypto1.c index e862b14d1..78a400a96 100644 --- a/applications/system/mfkey/crypto1.c +++ b/applications/system/mfkey/crypto1.c @@ -1,5 +1,4 @@ #pragma GCC optimize("O3") -#pragma GCC optimize("-funroll-all-loops") #include #include "crypto1.h" diff --git a/applications/system/mfkey/crypto1.h b/applications/system/mfkey/crypto1.h index 25205ed70..405a37f9b 100644 --- a/applications/system/mfkey/crypto1.h +++ b/applications/system/mfkey/crypto1.h @@ -2,14 +2,22 @@ #define CRYPTO1_H #include + #include "mfkey.h" #include #include #define LF_POLY_ODD (0x29CE5C) #define LF_POLY_EVEN (0x870804) -#define BIT(x, n) ((x) >> (n) & 1) -#define BEBIT(x, n) BIT(x, (n) ^ 24) + +// Precomputed mask constants for extend_table +#define CONST_M1_1 (LF_POLY_EVEN << 1 | 1) +#define CONST_M2_1 (LF_POLY_ODD << 1) +#define CONST_M1_2 (LF_POLY_ODD) +#define CONST_M2_2 (LF_POLY_EVEN << 1 | 1) + +#define BIT(x, n) ((x) >> (n) & 1) +#define BEBIT(x, n) BIT(x, (n) ^ 24) #define SWAPENDIAN(x) \ ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) @@ -31,7 +39,7 @@ static inline void rollback_word_noret(struct Crypto1State* s, uint32_t in, int static inline uint8_t napi_lfsr_rollback_bit(struct Crypto1State* s, uint32_t in, int fb); static inline uint32_t napi_lfsr_rollback_word(struct Crypto1State* s, uint32_t in, int fb); -static const uint8_t lookup1[256] = { +static const uint8_t __attribute__((aligned(4))) lookup1[256] = { 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, @@ -43,7 +51,7 @@ static const uint8_t lookup1[256] = { 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24}; -static const uint8_t lookup2[256] = { +static const uint8_t __attribute__((aligned(4))) lookup2[256] = { 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, @@ -54,29 +62,104 @@ static const uint8_t lookup2[256] = { 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6}; -static inline int filter(uint32_t const x) { +static inline __attribute__((always_inline)) int filter(uint32_t const x) { uint32_t f; f = lookup1[x & 0xff] | lookup2[(x >> 8) & 0xff]; f |= 0x0d938 >> (x >> 16 & 0xf) & 1; return BIT(0xEC57E80A, f); } -#ifdef __ARM_ARCH_7EM__ -static inline uint8_t evenparity32(uint32_t x) { - // fold 32 bits -> 16 -> 8 -> 4 +// Optimized: compute filter(v) and filter(v|1) with shared work +// Returns packed (f1 << 1 | f0) to avoid pointer overhead +static inline __attribute__((always_inline)) uint32_t filter_pair(uint32_t v) { + // Shared indices (v|1 only changes bit 0) + uint32_t idx_hi = (v >> 8) & 0xff; + uint32_t idx_nib = (v >> 16) & 0xf; + + // Shared lookups and computation + uint8_t l2 = lookup2[idx_hi]; + uint32_t nib_bit = (0x0d938 >> idx_nib) & 1; + uint32_t shared = l2 | nib_bit; + + // lookup1 differs only in bit 0 + uint32_t idx0_lo = v & 0xff; + uint8_t l1_0 = lookup1[idx0_lo]; + uint8_t l1_1 = lookup1[idx0_lo | 1]; + + // Compute both filters + int f0 = BIT(0xEC57E80A, l1_0 | shared); + int f1 = BIT(0xEC57E80A, l1_1 | shared); + + // Pack: bit 0 = f0, bit 1 = f1 + return (uint32_t)f0 | ((uint32_t)f1 << 1); +} + +// Compute filter pair using pre-XOR'd filter constant +// adj_filter = 0xEC57E80A ^ (-(uint32_t)BIT(xks, round)) +// Returns f0/f1 already XOR'd with xks_bit, eliminating xks_bit as a variable +static inline __attribute__((always_inline)) uint32_t + filter_pair_xor(uint32_t v, uint32_t adj_filter) { + uint32_t idx_hi = (v >> 8) & 0xff; + uint32_t idx_nib = (v >> 16) & 0xf; + uint8_t l2 = lookup2[idx_hi]; + uint32_t nib_bit = (0x0d938 >> idx_nib) & 1; + uint32_t shared = l2 | nib_bit; + uint32_t idx0_lo = v & 0xff; + uint8_t l1_0 = lookup1[idx0_lo]; + uint8_t l1_1 = lookup1[idx0_lo | 1]; + int f0 = BIT(adj_filter, l1_0 | shared); + int f1 = BIT(adj_filter, l1_1 | shared); + return (uint32_t)f0 | ((uint32_t)f1 << 1); +} + +// Unpack macros for filter_pair result +#define FILTER_F0(fp) ((fp) & 1) +#define FILTER_F1(fp) (((fp) >> 1) & 1) + +// Optimized filter_pair with precomputed nibble bit +// nib_bit must be 0 or 1 (the result of (0x0d938 >> nibble) & 1) +static inline __attribute__((always_inline)) uint32_t + filter_pair_with_nib(uint32_t v, uint32_t nib_bit) { + uint32_t idx_hi = (v >> 8) & 0xff; + + uint8_t l2 = lookup2[idx_hi]; + uint32_t shared = l2 | nib_bit; // nib_bit precomputed, no shift/mask needed + + uint32_t idx0_lo = v & 0xff; + uint8_t l1_0 = lookup1[idx0_lo]; + uint8_t l1_1 = lookup1[idx0_lo | 1]; + + int f0 = BIT(0xEC57E80A, l1_0 | shared); + int f1 = BIT(0xEC57E80A, l1_1 | shared); + + return (uint32_t)f0 | ((uint32_t)f1 << 1); +} + +// Register-based parity update (avoids array access overhead) +// Calculates new state with parity bits without array/pointer access +static inline __attribute__((always_inline)) uint32_t + update_contribution_reg(uint32_t v, int mask1, int mask2) { + uint32_t p = v >> 25; + p = (p << 1) | evenparity32(v & mask1); + p = (p << 1) | evenparity32(v & mask2); + return (p << 24) | (v & 0xffffff); +} + +static inline __attribute__((always_inline)) uint8_t evenparity32(uint32_t x) { x ^= x >> 16; x ^= x >> 8; x ^= x >> 4; - // magic 0x6996: bit i tells you parity of i (0 ≤ i < 16) - return (uint8_t)((0x6996u >> (x & 0xF)) & 1); + x ^= x >> 2; + x ^= x >> 1; + return (uint8_t)(x & 1); } -#endif -static inline void update_contribution(unsigned int data[], int item, int mask1, int mask2) { - int p = data[item] >> 25; +static inline __attribute__((always_inline)) void + update_contribution(unsigned int data[], int item, int mask1, int mask2) { + unsigned int p = data[item] >> 25; // Use unsigned to avoid UB on left shift p = p << 1 | evenparity32(data[item] & mask1); p = p << 1 | evenparity32(data[item] & mask2); - data[item] = p << 24 | (data[item] & 0xffffff); + data[item] = (p << 24) | (data[item] & 0xffffff); } static inline uint32_t crypt_word(struct Crypto1State* s) { @@ -187,29 +270,6 @@ static inline void rollback_word_noret(struct Crypto1State* s, uint32_t in, int return; } -// TODO: -/* -uint32_t rollback_word(struct Crypto1State *s, uint32_t in, int x) { - uint32_t res_ret = 0; - uint8_t ret; - uint32_t feedin, t, next_in; - for (int i = 31; i >= 0; i--) { - next_in = BEBIT(in, i); - s->odd &= 0xffffff; - t = s->odd, s->odd = s->even, s->even = t; - ret = filter(s->odd); - feedin = ret & (!!x); - feedin ^= s->even & 1; - feedin ^= LF_POLY_EVEN & (s->even >>= 1); - feedin ^= LF_POLY_ODD & s->odd; - feedin ^= !!next_in; - s->even |= (evenparity32(feedin)) << 23; - res_ret |= (ret << (24 ^ i)); - } - return res_ret; -} -*/ - uint8_t napi_lfsr_rollback_bit(struct Crypto1State* s, uint32_t in, int fb) { int out; uint8_t ret; @@ -231,7 +291,7 @@ uint32_t napi_lfsr_rollback_word(struct Crypto1State* s, uint32_t in, int fb) { int i; uint32_t ret = 0; for(i = 31; i >= 0; --i) - ret |= napi_lfsr_rollback_bit(s, BEBIT(in, i), fb) << (i ^ 24); + ret |= (uint32_t)napi_lfsr_rollback_bit(s, BEBIT(in, i), fb) << (i ^ 24); return ret; } diff --git a/applications/system/mfkey/mfkey.c b/applications/system/mfkey/mfkey.c index db700be32..97ccbb8c8 100644 --- a/applications/system/mfkey/mfkey.c +++ b/applications/system/mfkey/mfkey.c @@ -1,5 +1,4 @@ #pragma GCC optimize("O3") -#pragma GCC optimize("-funroll-all-loops") // TODO: More efficient dictionary bruteforce by scanning through hardcoded very common keys and previously found dictionary keys first? // (a cache for key_already_found_for_nonce_in_dict) @@ -18,14 +17,17 @@ #include #include "mfkey_icons.h" #include +#include #include #include #include #include #include +#include #include #include "mfkey.h" #include "crypto1.h" +#include "mfkey_attack.h" #include "plugin_interface.h" #include #include @@ -42,12 +44,13 @@ #define LF_POLY_ODD (0x29CE5C) #define LF_POLY_EVEN (0x870804) -#define CONST_M1_1 (LF_POLY_EVEN << 1 | 1) -#define CONST_M2_1 (LF_POLY_ODD << 1) -#define CONST_M1_2 (LF_POLY_ODD) -#define CONST_M2_2 (LF_POLY_EVEN << 1 | 1) -#define BIT(x, n) ((x) >> (n) & 1) -#define BEBIT(x, n) BIT(x, (n) ^ 24) + +#define CONST_M1_1 (LF_POLY_EVEN << 1 | 1) +#define CONST_M2_1 (LF_POLY_ODD << 1) +#define CONST_M1_2 (LF_POLY_ODD) +#define CONST_M2_2 (LF_POLY_EVEN << 1 | 1) +#define BIT(x, n) ((x) >> (n) & 1) +#define BEBIT(x, n) BIT(x, (n) ^ 24) #define SWAP(a, b) \ do { \ unsigned int t = a; \ @@ -59,18 +62,18 @@ // #define SIZEOF(arr) sizeof(arr) / sizeof(*arr) // Reduced to 16-bit as these values are small and don't need 32-bit -static int16_t eta_round_time = 44; -static int16_t eta_total_time = 705; +static int16_t eta_round_time = 30; +static int16_t eta_total_time = 481; // MSB_LIMIT: Chunk size (out of 256) - can be 8-bit as it's a small value -static uint8_t MSB_LIMIT = 16; +// Not static - referenced by mfkey_attack.c +uint8_t MSB_LIMIT = 16; -static inline void flush_key_buffer(ProgramState* program_state) { +// Not static - referenced by mfkey_attack.c for static_encrypted attacks +void flush_key_buffer(ProgramState* program_state) { if(program_state->key_buffer && program_state->key_buffer_count > 0 && program_state->cuid_dict) { // Pre-allocate exact size needed: 2 hex chars (key_idx) + 12 hex chars (key) + 1 newline per key size_t total_size = program_state->key_buffer_count * 15; - //FURI_LOG_I(TAG, "Flushing key buffer: %d keys", program_state->key_buffer_count); - //FURI_LOG_I(TAG, "Total size: %d bytes", total_size); char* batch_buffer = malloc(total_size + 1); // +1 for null terminator char* ptr = batch_buffer; @@ -109,323 +112,8 @@ static inline void flush_key_buffer(ProgramState* program_state) { } } -static inline int - check_state(struct Crypto1State* t, MfClassicNonce* n, ProgramState* program_state) { - if(!(t->odd | t->even)) return 0; - if(n->attack == mfkey32) { - uint32_t rb = (napi_lfsr_rollback_word(t, 0, 0) ^ n->p64); - if(rb != n->ar0_enc) { - return 0; - } - rollback_word_noret(t, n->nr0_enc, 1); - rollback_word_noret(t, n->uid_xor_nt0, 0); - struct Crypto1State temp = {t->odd, t->even}; - crypt_word_noret(t, n->uid_xor_nt1, 0); - crypt_word_noret(t, n->nr1_enc, 1); - if(n->ar1_enc == (crypt_word(t) ^ n->p64b)) { - crypto1_get_lfsr(&temp, &(n->key)); - return 1; - } - } else if(n->attack == static_nested) { - struct Crypto1State temp = {t->odd, t->even}; - rollback_word_noret(t, n->uid_xor_nt1, 0); - if(n->ks1_1_enc == crypt_word_ret(t, n->uid_xor_nt0, 0)) { - rollback_word_noret(&temp, n->uid_xor_nt1, 0); - crypto1_get_lfsr(&temp, &(n->key)); - return 1; - } - } else if(n->attack == static_encrypted) { - // TODO: Parity bits from rollback_word? - if(n->ks1_1_enc == napi_lfsr_rollback_word(t, n->uid_xor_nt0, 0)) { - // Reduce with parity - uint8_t local_parity_keystream_bits; - struct Crypto1State temp = {t->odd, t->even}; - if((crypt_word_par(&temp, n->uid_xor_nt0, 0, n->nt0, &local_parity_keystream_bits) == - n->ks1_1_enc) && - (local_parity_keystream_bits == n->par_1)) { - // Found key candidate - crypto1_get_lfsr(t, &(n->key)); - program_state->num_candidates++; - - // Use key buffer - buffer is guaranteed to be available for static_encrypted - program_state->key_buffer[program_state->key_buffer_count] = n->key; - program_state->key_idx_buffer[program_state->key_buffer_count] = n->key_idx; - program_state->key_buffer_count++; - - // Flush buffer when full - if(program_state->key_buffer_count >= program_state->key_buffer_size) { - flush_key_buffer(program_state); - } - } - } - } - return 0; -} - -static inline __attribute__((hot)) int - state_loop(unsigned int* states_buffer, int xks, int m1, int m2, unsigned int in, int and_val) { - int states_tail = 0; - int xks_bit = 0, round_in = 0; - - // Unroll first 4 rounds (no round_in calculations needed) - // Hoist the filter() calls to just one iteration and reuse the results - // This avoids redundant calculations and improves performance and gives us 2000b of extra ram (11496b free on run) - // V.28/04. Aprox 3s speedup per round. Total 3 keys 7mins 17s!! - for(int round = 1; round <= 4; ++round) { - xks_bit = BIT(xks, round); - for(int s = 0; s <= states_tail; ++s) { - unsigned int v = states_buffer[s] << 1; - states_buffer[s] = v; - int f0 = filter(v); - int f1 = filter(v | 1); - - if(__builtin_expect((f0 ^ f1) != 0, 0)) { - states_buffer[s] |= f0 ^ xks_bit; - } else if(__builtin_expect(f0 == xks_bit, 1)) { - states_buffer[++states_tail] = states_buffer[++s]; - states_buffer[s] = states_buffer[s - 1] | 1; - } else { - states_buffer[s--] = states_buffer[states_tail--]; - } - } - } - - // Round 5 (unrolled) - { - xks_bit = BIT(xks, 5); - int r5_in = ((in >> 2) & and_val) << 24; // 2*(5-4)=2 - for(int s = 0; s <= states_tail; ++s) { - unsigned int v = states_buffer[s] << 1; - states_buffer[s] = v; - int f0 = filter(v), f1 = filter(v | 1); - if(__builtin_expect((f0 ^ f1) != 0, 0)) { - states_buffer[s] |= f0 ^ xks_bit; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s] ^= r5_in; - } else if(__builtin_expect(f0 == xks_bit, 1)) { - states_buffer[++states_tail] = states_buffer[s + 1]; - states_buffer[s + 1] = v | 1; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s++] ^= r5_in; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s] ^= r5_in; - } else { - states_buffer[s--] = states_buffer[states_tail--]; - } - } - } - - // Round 6 (unrolled) - { - xks_bit = BIT(xks, 6); - int r6_in = ((in >> 4) & and_val) << 24; // 2*(6-4)=4 - for(int s = 0; s <= states_tail; ++s) { - unsigned int v = states_buffer[s] << 1; - states_buffer[s] = v; - int f0 = filter(v), f1 = filter(v | 1); - if(__builtin_expect((f0 ^ f1) != 0, 0)) { - states_buffer[s] |= f0 ^ xks_bit; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s] ^= r6_in; - } else if(__builtin_expect(f0 == xks_bit, 1)) { - states_buffer[++states_tail] = states_buffer[s + 1]; - states_buffer[s + 1] = v | 1; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s++] ^= r6_in; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s] ^= r6_in; - } else { - states_buffer[s--] = states_buffer[states_tail--]; - } - } - } - - // Loop rounds 7–12 - for(int round = 7; round <= 12; ++round) { - xks_bit = BIT(xks, round); - round_in = ((in >> (2 * (round - 4))) & and_val) << 24; - for(int s = 0; s <= states_tail; ++s) { - unsigned int v = states_buffer[s] << 1; - states_buffer[s] = v; - int f0 = filter(v), f1 = filter(v | 1); - if(__builtin_expect((f0 ^ f1) != 0, 0)) { - states_buffer[s] |= f0 ^ xks_bit; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s] ^= round_in; - } else if(__builtin_expect(f0 == xks_bit, 1)) { - states_buffer[++states_tail] = states_buffer[s + 1]; - states_buffer[s + 1] = v | 1; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s++] ^= round_in; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s] ^= round_in; - } else { - states_buffer[s--] = states_buffer[states_tail--]; - } - } - } - - return states_tail; -} - -int binsearch(unsigned int data[], int start, int stop) { - int mid, val = data[stop] & 0xff000000; - while(start != stop) { - mid = (stop - start) >> 1; - if((data[start + mid] ^ 0x80000000) > (val ^ 0x80000000)) - stop = start + mid; - else - start += mid + 1; - } - return start; -} - -void quicksort(unsigned int array[], int low, int high) { - // Use insertion sort for small arrays (threshold determined by testing) - if(high - low < 16) { - // Insertion sort - for(int i = low + 1; i <= high; i++) { - unsigned int key = array[i]; - int j = i - 1; - while(j >= low && array[j] > key) { - array[j + 1] = array[j]; - j--; - } - array[j + 1] = key; - } - return; - } - - if(low >= high) return; - - // Median-of-three pivot selection - int middle = low + (high - low) / 2; - if(array[middle] < array[low]) SWAP(array[middle], array[low]); - if(array[high] < array[low]) SWAP(array[high], array[low]); - if(array[high] < array[middle]) SWAP(array[high], array[middle]); - - unsigned int pivot = array[middle]; - - // Rest of quicksort with improved partitioning - int i = low, j = high; - while(i <= j) { - while(array[i] < pivot) - i++; - while(array[j] > pivot) - j--; - if(i <= j) { - // swap - unsigned int temp = array[i]; - array[i] = array[j]; - array[j] = temp; - i++; - j--; - } - } - - if(low < j) quicksort(array, low, j); - if(high > i) quicksort(array, i, high); -} - -int extend_table(unsigned int data[], int tbl, int end, int bit, int m1, int m2, unsigned int in) { - in <<= 24; - for(data[tbl] <<= 1; tbl <= end; data[++tbl] <<= 1) { - if((filter(data[tbl]) ^ filter(data[tbl] | 1)) != 0) { - data[tbl] |= filter(data[tbl]) ^ bit; - update_contribution(data, tbl, m1, m2); - data[tbl] ^= in; - } else if(filter(data[tbl]) == bit) { - data[++end] = data[tbl + 1]; - data[tbl + 1] = data[tbl] | 1; - update_contribution(data, tbl, m1, m2); - data[tbl++] ^= in; - update_contribution(data, tbl, m1, m2); - data[tbl] ^= in; - } else { - data[tbl--] = data[end--]; - } - } - return end; -} - -int old_recover( - unsigned int odd[], - int o_head, - int o_tail, - int oks, - unsigned int even[], - int e_head, - int e_tail, - int eks, - int rem, - int s, - MfClassicNonce* n, - unsigned int in, - int first_run, - ProgramState* program_state) { - int o, e, i; - if(rem == -1) { - for(e = e_head; e <= e_tail; ++e) { - even[e] = (even[e] << 1) ^ evenparity32(even[e] & LF_POLY_EVEN) ^ (!!(in & 4)); - for(o = o_head; o <= o_tail; ++o, ++s) { - struct Crypto1State temp = {0, 0}; - temp.even = odd[o]; - temp.odd = even[e] ^ evenparity32(odd[o] & LF_POLY_ODD); - if(check_state(&temp, n, program_state)) { - return -1; - } - } - } - return s; - } - if(first_run == 0) { - for(i = 0; (i < 4) && (rem-- != 0); i++) { - oks >>= 1; - eks >>= 1; - in >>= 2; - o_tail = extend_table( - odd, o_head, o_tail, oks & 1, LF_POLY_EVEN << 1 | 1, LF_POLY_ODD << 1, 0); - if(o_head > o_tail) return s; - e_tail = extend_table( - even, e_head, e_tail, eks & 1, LF_POLY_ODD, LF_POLY_EVEN << 1 | 1, in & 3); - if(e_head > e_tail) return s; - } - } - first_run = 0; - quicksort(odd, o_head, o_tail); - quicksort(even, e_head, e_tail); - while(o_tail >= o_head && e_tail >= e_head) { - if(((odd[o_tail] ^ even[e_tail]) >> 24) == 0) { - o_tail = binsearch(odd, o_head, o = o_tail); - e_tail = binsearch(even, e_head, e = e_tail); - s = old_recover( - odd, - o_tail--, - o, - oks, - even, - e_tail--, - e, - eks, - rem, - s, - n, - in, - first_run, - program_state); - if(s == -1) { - break; - } - } else if((odd[o_tail] ^ 0x80000000) > (even[e_tail] ^ 0x80000000)) { - o_tail = binsearch(odd, o_head, o_tail) - 1; - } else { - e_tail = binsearch(even, e_head, e_tail) - 1; - } - } - return s; -} - -static inline int sync_state(ProgramState* program_state) { +// Not static - referenced by mfkey_attack.c +int sync_state(ProgramState* program_state) { int ts = furi_hal_rtc_get_timestamp(); int elapsed_time = ts - program_state->eta_timestamp; if(elapsed_time < program_state->eta_round) { @@ -445,148 +133,6 @@ static inline int sync_state(ProgramState* program_state) { return 0; } -int calculate_msb_tables( - int oks, - int eks, - int msb_round, - MfClassicNonce* n, - unsigned int* states_buffer, - struct Msb* odd_msbs, - struct Msb* even_msbs, - unsigned int* temp_states_odd, - unsigned int* temp_states_even, - unsigned int in, - ProgramState* program_state) { - unsigned int msb_head = (MSB_LIMIT * msb_round); - unsigned int msb_tail = (MSB_LIMIT * (msb_round + 1)); - int states_tail = 0; - int semi_state = 0; - unsigned int msb = 0; - - // Preprocessed in value - in = ((in >> 16 & 0xff) | (in << 16) | (in & 0xff00)) << 1; - - // Clear MSB arrays once before loop instead of inside loop - memset(odd_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); - memset(even_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); - - // Bit values to check - calculate once outside the loop - int oks_bit = oks & 1; - int eks_bit = eks & 1; - - // Check for stop request less frequently - int sync_check_interval = 32768 * 2; // Doubled the interval - - for(semi_state = 1 << 20; semi_state >= 0; semi_state--) { - if(semi_state % sync_check_interval == 0) { - if(sync_state(program_state) == 1) { - return 0; - } - } - - // Process both filter conditions in one pass when possible - int filter_semi_state = filter(semi_state); - - // Check oks condition - if(filter_semi_state == oks_bit) { - states_buffer[0] = semi_state; - states_tail = state_loop(states_buffer, oks, CONST_M1_1, CONST_M2_1, 0, 0); - - for(int i = states_tail; i >= 0; i--) { - msb = states_buffer[i] >> 24; - if((msb >= msb_head) && (msb < msb_tail)) { - // Calculate index once - int msb_idx = msb - msb_head; - - // Avoid sequential scan by using a direct flag - int found = 0; - for(int j = 0; j < odd_msbs[msb_idx].tail; j++) { - if(odd_msbs[msb_idx].states[j] == states_buffer[i]) { - found = 1; - break; - } - } - - if(!found) { - int tail = odd_msbs[msb_idx].tail++; - odd_msbs[msb_idx].states[tail] = states_buffer[i]; - } - } - } - } - - // Check eks condition - if(filter_semi_state == eks_bit) { - states_buffer[0] = semi_state; - states_tail = state_loop(states_buffer, eks, CONST_M1_2, CONST_M2_2, in, 3); - - for(int i = 0; i <= states_tail; i++) { - msb = states_buffer[i] >> 24; - if((msb >= msb_head) && (msb < msb_tail)) { - // Calculate index once - int msb_idx = msb - msb_head; - - // Avoid sequential scan - int found = 0; - for(int j = 0; j < even_msbs[msb_idx].tail; j++) { - if(even_msbs[msb_idx].states[j] == states_buffer[i]) { - found = 1; - break; - } - } - - if(!found) { - int tail = even_msbs[msb_idx].tail++; - even_msbs[msb_idx].states[tail] = states_buffer[i]; - } - } - } - } - } - - // Shift once outside the loop - oks >>= 12; - eks >>= 12; - - // Process results - for(int i = 0; i < MSB_LIMIT; i++) { - if((i % 4) == 0 && sync_state(program_state) == 1) { - return 0; - } - - // Only clear buffers if they're going to be used - if(odd_msbs[i].tail > 0 || even_msbs[i].tail > 0) { - memset(temp_states_even, 0, sizeof(unsigned int) * (1280)); - memset(temp_states_odd, 0, sizeof(unsigned int) * (1280)); - memcpy(temp_states_odd, odd_msbs[i].states, odd_msbs[i].tail * sizeof(unsigned int)); - memcpy( - temp_states_even, even_msbs[i].states, even_msbs[i].tail * sizeof(unsigned int)); - - int res = old_recover( - temp_states_odd, - 0, - odd_msbs[i].tail, - oks, - temp_states_even, - 0, - even_msbs[i].tail, - eks, - 3, - 0, - n, - in >> 16, - 1, - program_state); - - if(res == -1) { - return 1; - } - } - } - - return 0; -} - void** allocate_blocks(const size_t* block_sizes, int num_blocks) { void** block_pointers = malloc(num_blocks * sizeof(void*)); if(!block_pointers) { @@ -619,12 +165,15 @@ void** allocate_blocks(const size_t* block_sizes, int num_blocks) { bool recover(MfClassicNonce* n, int ks2, unsigned int in, ProgramState* program_state) { bool found = false; - const size_t block_sizes[] = {49216, 49216, 5120, 5120, 4096}; - const size_t reduced_block_sizes[] = {24608, 24608, 5120, 5120, 4096}; + // Packed 24-bit Msb format: 16 buckets × 2312 bytes each = 36992 + // states_buffer needs 1024 elements × 4 bytes = 4096 + const size_t block_sizes[] = {36992, 36992, 5120, 5120, 4096}; + // Reduced: 8 buckets × 2312 bytes each = 18496 + const size_t reduced_block_sizes[] = {18496, 18496, 5120, 5120, 4096}; const int num_blocks = sizeof(block_sizes) / sizeof(block_sizes[0]); // Reset globals each nonce - eta_round_time = 44; - eta_total_time = 705; + eta_round_time = 30; + eta_total_time = 481; MSB_LIMIT = 16; // Use half speed (reduced block sizes) for static encrypted nonces so we can buffer keys @@ -720,7 +269,7 @@ bool recover(MfClassicNonce* n, int ks2, unsigned int in, ProgramState* program_ program_state->search = msb; program_state->eta_round = eta_round_time; program_state->eta_total = eta_total_time - (eta_round_time * msb); - if(calculate_msb_tables( + if(calculate_msb_tables_optimized( oks, eks, msb, @@ -732,8 +281,6 @@ bool recover(MfClassicNonce* n, int ks2, unsigned int in, ProgramState* program_ temp_states_even, in, program_state)) { - // int bench_stop = furi_hal_rtc_get_timestamp(); - // FURI_LOG_I(TAG, "Cracked in %i seconds", bench_stop - bench_start); found = true; break; } @@ -810,7 +357,6 @@ void mfkey(ProgramState* program_state) { } uint32_t i = 0, j = 0; - // FURI_LOG_I(TAG, "Free heap before alloc(): %zub", memmgr_get_free_heap()); Storage* storage = furi_record_open(RECORD_STORAGE); FlipperApplication* app = flipper_application_alloc(storage, firmware_api_interface); flipper_application_preload(app, APP_ASSETS_PATH("plugins/mfkey_init_plugin.fal")); @@ -873,7 +419,6 @@ void mfkey(ProgramState* program_state) { // TODO: Already closed? buffered_file_stream_close(nonce_arr->stream); stream_free(nonce_arr->stream); - // FURI_LOG_I(TAG, "Free heap after free(): %zub", memmgr_get_free_heap()); program_state->mfkey_state = MFKeyAttack; // TODO: Work backwards on this array and free memory for(i = 0; i < nonce_arr->total_nonces; i++) { @@ -884,7 +429,6 @@ void mfkey(ProgramState* program_state) { (program_state->num_completed)++; continue; } - // FURI_LOG_I(TAG, "Beginning recovery for %8lx", next_nonce.uid); FuriString* cuid_dict_path; switch(next_nonce.attack) { case mfkey32: @@ -961,9 +505,7 @@ void mfkey(ProgramState* program_state) { } // TODO: Update display to show all keys were found // TODO: Prepend found key(s) to user dictionary file - // FURI_LOG_I(TAG, "Unique keys found:"); for(i = 0; i < keyarray_size; i++) { - // FURI_LOG_I(TAG, "%012" PRIx64, keyarray[i]); keys_dict_add_key(user_dict, keyarray[i].data, sizeof(MfClassicKey)); } if(keyarray_size > 0) { @@ -975,7 +517,6 @@ void mfkey(ProgramState* program_state) { if(program_state->mfkey_state == Error) { return; } - // FURI_LOG_I(TAG, "mfkey function completed normally"); // DEBUG program_state->mfkey_state = Complete; // No need to alert the user if they asked it to stop if(!(program_state->close_thread_please)) { @@ -1124,7 +665,7 @@ int32_t mfkey_main() { gui_add_view_port(gui, view_port, GuiLayerFullscreen); program_state->mfkeythread = - furi_thread_alloc_ex("MFKeyWorker", 2048, mfkey_worker_thread, program_state); + furi_thread_alloc_ex("MFKeyWorker", 4096, mfkey_worker_thread, program_state); InputEvent input_event; for(bool main_loop = true; main_loop;) { diff --git a/applications/system/mfkey/mfkey.h b/applications/system/mfkey/mfkey.h index 0e5ff01dd..c01d3a989 100644 --- a/applications/system/mfkey/mfkey.h +++ b/applications/system/mfkey/mfkey.h @@ -12,9 +12,14 @@ struct Crypto1State { uint32_t odd, even; }; + +#define MSB_BUCKET_CAPACITY 768 + struct Msb { int tail; - uint32_t states[768]; + // Store 24-bit states packed into bytes (MSB is implicit from bucket index). + // CAPACITY * 3 bytes for data + 4 bytes padding for safe unaligned 32-bit write. + uint8_t states[MSB_BUCKET_CAPACITY * 3 + 4]; }; typedef enum { diff --git a/applications/system/mfkey/mfkey_attack.c b/applications/system/mfkey/mfkey_attack.c new file mode 100644 index 000000000..2fd1257f3 --- /dev/null +++ b/applications/system/mfkey/mfkey_attack.c @@ -0,0 +1,287 @@ +#pragma GCC optimize("O3") + +#include +#include + +#include +#include "mfkey_attack.h" + +#include "crypto1.h" +#include "mfkey_bs_verify.h" +#include "mfkey_dedup.h" +#include "mfkey_state_expansion.h" +#include "mfkey_recovery.h" +#include "mfkey_batch_prelude.h" + +volatile bool g_abort_attack = false; +ProgramState* g_program_state = NULL; // For static_encrypted key buffering + +extern int sync_state(ProgramState* program_state); +extern void flush_key_buffer(ProgramState* program_state); +extern uint8_t MSB_LIMIT; + +#define SWAP(a, b) \ + do { \ + unsigned int t = a; \ + a = b; \ + b = t; \ + } while(0) + +// Forces x into a register, preventing the compiler from hoisting BIT(x, n) +// extractions out of loops and spilling all 16 keystream bits to stack. +#define OPT_BARRIER(x) __asm__ volatile("" : "+r"(x)) + +// Precomputed Round 4 lane survival masks, indexed by [shared_value][target_bit]. +// Each 16-bit half covers one half-batch (lo/hi). + +static const uint16_t R4_LANE_MASK[8][2] = { + /* shared=0 */ {0xFFFF, 0x26C7}, + /* shared=1 */ {0xD938, 0x26C7}, + /* shared=2 */ {0xFFFF, 0xFFFF}, + /* shared=3 */ {0x26C7, 0xFFFF}, + /* shared=4 */ {0xFFFF, 0x26C7}, + /* shared=5 */ {0x26C7, 0xD938}, + /* shared=6 */ {0x26C7, 0xFFFF}, + /* shared=7 */ {0x26C7, 0xD938}, +}; + +int calculate_msb_tables_optimized( + int oks, + int eks, + int msb_round, + MfClassicNonce* n, + unsigned int* states_buffer, + struct Msb* odd_msbs, + struct Msb* even_msbs, + unsigned int* temp_states_odd, + unsigned int* temp_states_even, + unsigned int in, + ProgramState* program_state) { + // Set global state for hot-path functions (avoids passing through recursion) + g_program_state = program_state; + g_abort_attack = false; + + unsigned int msb_head = (MSB_LIMIT * msb_round); + unsigned int msb_tail = (MSB_LIMIT * (msb_round + 1)); + + int states_tail = 0; + unsigned int msb = 0; + + // Preprocessed in value + in = ((in >> 16 & 0xff) | (in << 16) | (in & 0xff00)) << 1; + + // Clear MSB arrays + memset(odd_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); + memset(even_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); + +// Identity mask deduplication +#define DISABLE_IDENTITY_FILTER 0 +#if !DISABLE_IDENTITY_FILTER + // Use the idle temp_states buffers as scratch space for bitmask filters. + // Each filter needs (MSB_LIMIT * 64) = 1024 uint32_t entries = 4096 bytes + // temp_states_odd/even are each 1280 elements, so we split them: + uint32_t* odd_msb_filters = (uint32_t*)temp_states_odd; + uint32_t* even_msb_filters = (uint32_t*)temp_states_even; + memset(temp_states_odd, 0, 1024 * sizeof(unsigned int)); + memset(temp_states_even, 0, 1024 * sizeof(unsigned int)); +#endif + + // Iterate in batches of 32 (batch_base has bits 0-4 = 0) + for(int batch_base = (1 << 20) & ~31; batch_base >= 0; batch_base -= 32) { + // Prevent compiler from hoisting BIT(oks/eks, N) extractions out of loop + OPT_BARRIER(oks); + OPT_BARRIER(eks); + + // Periodic sync check (every 2048 batches = 65536 semi-states) + if((batch_base & 0xFFE0) == 0) { + if(sync_state(program_state) == 1) { + return 0; + } + } + + // R4 Lane Mask: precompute full 32-bit masks for both streams + // shared_lo/hi, BIT(oks,4), BIT(eks,4) are all constant per batch, + // so the entire R4 mask is batch-invariant. Precompute once here to + // avoid 2 Flash table reads + address arithmetic per inner-loop hit. + uint32_t nib_bit_r4 = (0x0d938 >> ((batch_base >> 12) & 0xF)) & 1; + uint32_t l2_base_r4 = (batch_base >> 4) & 0xFE; + uint32_t shared_lo_r4 = lookup2[l2_base_r4] | nib_bit_r4; + uint32_t shared_hi_r4 = lookup2[l2_base_r4 | 1] | nib_bit_r4; + uint32_t r4_mask_oks = (uint32_t)R4_LANE_MASK[shared_lo_r4][BIT(oks, 4)] | + ((uint32_t)R4_LANE_MASK[shared_hi_r4][BIT(oks, 4)] << 16); + uint32_t r4_mask_eks = (uint32_t)R4_LANE_MASK[shared_lo_r4][BIT(eks, 4)] | + ((uint32_t)R4_LANE_MASK[shared_hi_r4][BIT(eks, 4)] << 16); + + // OKS processing + uint32_t oks_leaf_masks[8]; + uint32_t valid_oks = batch_prelude_unified(batch_base, oks, r4_mask_oks, oks_leaf_masks); + + if(valid_oks) { + uint32_t node_base = (batch_base << 3); + uint32_t active = valid_oks; + while(active) { + int lane = __builtin_ctz(active); + active &= active - 1; + + uint32_t lane_bit = 1u << lane; + uint32_t base_state = node_base | (lane << 3); + int count = 0; + for(int c = 0; c < 8; c++) + if(oks_leaf_masks[c] & lane_bit) states_buffer[count++] = base_state | c; + + if(count > 0) { + states_tail = + state_loop_r4(states_buffer, count, oks, CONST_M1_1, CONST_M2_1, 0, 0); + + // Bucket Insertion + for(int i = states_tail; i >= 0; i--) { + msb = states_buffer[i] >> 24; + if((msb >= msb_head) && (msb < msb_tail)) { + int msb_idx = msb - msb_head; + uint32_t state = states_buffer[i]; + +#if DISABLE_IDENTITY_FILTER + if(odd_msbs[msb_idx].tail < MSB_BUCKET_CAPACITY) { + int tail = odd_msbs[msb_idx].tail++; + memcpy(&odd_msbs[msb_idx].states[tail * 3], &state, 3); + } +#else + uint32_t fingerprint = FIB_HASH_20BIT(state); + uint32_t filter_idx = (msb_idx << 6) | (fingerprint >> 5); + uint32_t mask = 1U << (fingerprint & 31); + + bool already_exists = false; + if(odd_msb_filters[filter_idx] & mask) { + already_exists = scan_for_duplicate_8x( + odd_msbs[msb_idx].states, + odd_msbs[msb_idx].tail, + state & 0x00FFFFFF); + } + + if(!already_exists && odd_msbs[msb_idx].tail < MSB_BUCKET_CAPACITY) { + odd_msb_filters[filter_idx] |= mask; + int tail = odd_msbs[msb_idx].tail++; + memcpy(&odd_msbs[msb_idx].states[tail * 3], &state, 3); + } +#endif + } + } + } + } + } + + // EKS processing + uint32_t eks_leaf_masks[8]; + uint32_t valid_eks = batch_prelude_unified(batch_base, eks, r4_mask_eks, eks_leaf_masks); + + if(valid_eks) { + uint32_t node_base = (batch_base << 3); + uint32_t active = valid_eks; + while(active) { + int lane = __builtin_ctz(active); + active &= active - 1; + + uint32_t lane_bit = 1u << lane; + uint32_t base_state = node_base | (lane << 3); + int count = 0; + for(int c = 0; c < 8; c++) + if(eks_leaf_masks[c] & lane_bit) states_buffer[count++] = base_state | c; + + if(count > 0) { + states_tail = + state_loop_r4(states_buffer, count, eks, CONST_M1_2, CONST_M2_2, in, 3); + + // Bucket Insertion + for(int i = 0; i <= states_tail; i++) { + msb = states_buffer[i] >> 24; + if((msb >= msb_head) && (msb < msb_tail)) { + int msb_idx = msb - msb_head; + uint32_t state = states_buffer[i]; + +#if DISABLE_IDENTITY_FILTER + if(even_msbs[msb_idx].tail < MSB_BUCKET_CAPACITY) { + int tail = even_msbs[msb_idx].tail++; + memcpy(&even_msbs[msb_idx].states[tail * 3], &state, 3); + } +#else + uint32_t fingerprint = FIB_HASH_20BIT(state); + uint32_t filter_idx = (msb_idx << 6) | (fingerprint >> 5); + uint32_t mask = 1U << (fingerprint & 31); + + bool already_exists = false; + if(even_msb_filters[filter_idx] & mask) { + already_exists = scan_for_duplicate_8x( + even_msbs[msb_idx].states, + even_msbs[msb_idx].tail, + state & 0x00FFFFFF); + } + + if(!already_exists && even_msbs[msb_idx].tail < MSB_BUCKET_CAPACITY) { + even_msb_filters[filter_idx] |= mask; + int tail = even_msbs[msb_idx].tail++; + memcpy(&even_msbs[msb_idx].states[tail * 3], &state, 3); + } +#endif + } + } + } + } + } + } + + // Shift keystream for old_recover + oks >>= 12; + eks >>= 12; + + // Verification phase + for(int i = 0; i < MSB_LIMIT; i++) { + if((i % 4) == 0) { + if(sync_state(program_state) == 1) { + g_abort_attack = true; + return 0; + } + } + + // Only process MSB buckets with candidates on both sides + if(odd_msbs[i].tail > 0 && even_msbs[i].tail > 0) { + uint32_t current_msb_val = (uint32_t)(msb_head + i) << 24; + + for(int k = 0; k < odd_msbs[i].tail; k++) { + uint32_t raw = 0; + memcpy(&raw, &odd_msbs[i].states[k * 3], 3); + temp_states_odd[k] = raw | current_msb_val; + } + + for(int k = 0; k < even_msbs[i].tail; k++) { + uint32_t raw = 0; + memcpy(&raw, &even_msbs[i].states[k * 3], 3); + temp_states_even[k] = raw | current_msb_val; + } + + // Bitsliced verification for all attack types (mfkey32, static_nested, + // static_encrypted). Each type uses its own 32-way SWAR kernel. + int res = old_recover_bs( + temp_states_odd, + 0, + odd_msbs[i].tail - 1, + oks, + temp_states_even, + 0, + even_msbs[i].tail - 1, + eks, + 3, + 0, + n, + in >> 16, + 1); + + if(res == -1) { + return 1; // Key found + } else if(res == -2) { + return 0; // User aborted + } + } + } + + return 0; +} diff --git a/applications/system/mfkey/mfkey_attack.h b/applications/system/mfkey/mfkey_attack.h new file mode 100644 index 000000000..a6fbf5783 --- /dev/null +++ b/applications/system/mfkey/mfkey_attack.h @@ -0,0 +1,21 @@ +#ifndef MFKEY_ATTACK_H +#define MFKEY_ATTACK_H + +#include "mfkey.h" + +// Main MSB table calculation - runs attack for one MSB round +// Returns 1 if key found, 0 otherwise +int calculate_msb_tables_optimized( + int oks, + int eks, + int msb_round, + MfClassicNonce* n, + unsigned int* states_buffer, + struct Msb* odd_msbs, + struct Msb* even_msbs, + unsigned int* temp_states_odd, + unsigned int* temp_states_even, + unsigned int in, + ProgramState* program_state); + +#endif // MFKEY_ATTACK_H diff --git a/applications/system/mfkey/mfkey_batch_prelude.c b/applications/system/mfkey/mfkey_batch_prelude.c new file mode 100644 index 000000000..63b1290e4 --- /dev/null +++ b/applications/system/mfkey/mfkey_batch_prelude.c @@ -0,0 +1,707 @@ +// MFKey Batch Prelude - 32-lane parallel tree expansion through rounds 0-3 +// Extracted from mfkey_attack.c for better code organization + +#pragma GCC optimize("O3") + +#include "mfkey_batch_prelude.h" +#include "crypto1.h" +#include "mfkey_dedup.h" +#include + +// Precomputed filter LUTs for shared input values 0..7 +// Derived from 0xEC57E80A stride 8 (lookup2 outputs even values, nibble adds 0 or 1) +static const uint8_t FILTER_LUT_TABLE[8] = {0x4, 0x5, 0xC, 0xB, 0x4, 0xA, 0xE, 0xA}; + +// Super-LUT: Combined bitmasks indexed by full LUT value (0-15) +// Eliminates conditional branches - single indexed load per round +// Total: 2KB (8×16 + 4×2×16 + 2×4×16 + 8×16 = 512 uint32_t) + +// Round 0: 8 offsets × 16 LUT values (regenerated from filter() brute-force) +static const uint32_t R0_COMBINED[8][16] = { + { + 0x00000000, + 0x0DD30DD3, + 0x00000000, + 0x0DD30DD3, + 0xF22CF22C, + 0xFFFFFFFF, + 0xF22CF22C, + 0xFFFFFFFF, + 0x00000000, + 0x0DD30DD3, + 0x00000000, + 0x0DD30DD3, + 0xF22CF22C, + 0xFFFFFFFF, + 0xF22CF22C, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x00000DD3, + 0x0DD30000, + 0x0DD30DD3, + 0x0000F22C, + 0x0000FFFF, + 0x0DD3F22C, + 0x0DD3FFFF, + 0xF22C0000, + 0xF22C0DD3, + 0xFFFF0000, + 0xFFFF0DD3, + 0xF22CF22C, + 0xF22CFFFF, + 0xFFFFF22C, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x00000000, + 0x0DD30DD3, + 0x0DD30DD3, + 0x00000000, + 0x00000000, + 0x0DD30DD3, + 0x0DD30DD3, + 0xF22CF22C, + 0xF22CF22C, + 0xFFFFFFFF, + 0xFFFFFFFF, + 0xF22CF22C, + 0xF22CF22C, + 0xFFFFFFFF, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x0DD30DD3, + 0x00000000, + 0x0DD30DD3, + 0xF22CF22C, + 0xFFFFFFFF, + 0xF22CF22C, + 0xFFFFFFFF, + 0x00000000, + 0x0DD30DD3, + 0x00000000, + 0x0DD30DD3, + 0xF22CF22C, + 0xFFFFFFFF, + 0xF22CF22C, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x0DD30000, + 0x00000DD3, + 0x0DD30DD3, + 0xF22C0000, + 0xFFFF0000, + 0xF22C0DD3, + 0xFFFF0DD3, + 0x0000F22C, + 0x0DD3F22C, + 0x0000FFFF, + 0x0DD3FFFF, + 0xF22CF22C, + 0xFFFFF22C, + 0xF22CFFFF, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x00000DD3, + 0x0DD30000, + 0x0DD30DD3, + 0x0000F22C, + 0x0000FFFF, + 0x0DD3F22C, + 0x0DD3FFFF, + 0xF22C0000, + 0xF22C0DD3, + 0xFFFF0000, + 0xFFFF0DD3, + 0xF22CF22C, + 0xF22CFFFF, + 0xFFFFF22C, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x0DD30000, + 0x00000DD3, + 0x0DD30DD3, + 0xF22C0000, + 0xFFFF0000, + 0xF22C0DD3, + 0xFFFF0DD3, + 0x0000F22C, + 0x0DD3F22C, + 0x0000FFFF, + 0x0DD3FFFF, + 0xF22CF22C, + 0xFFFFF22C, + 0xF22CFFFF, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x00000000, + 0x0DD30DD3, + 0x0DD30DD3, + 0x00000000, + 0x00000000, + 0x0DD30DD3, + 0x0DD30DD3, + 0xF22CF22C, + 0xF22CF22C, + 0xFFFFFFFF, + 0xFFFFFFFF, + 0xF22CF22C, + 0xF22CF22C, + 0xFFFFFFFF, + 0xFFFFFFFF, + }, +}; + +// Round 1: 4 offsets × 2 children × 16 LUT values (regenerated from filter() brute-force) +static const uint32_t R1_COMBINED[4][2][16] = { + { + { + 0x00000000, + 0x003D3D3D, + 0x3D000000, + 0x3D3D3D3D, + 0x00C2C2C2, + 0x00FFFFFF, + 0x3DC2C2C2, + 0x3DFFFFFF, + 0xC2000000, + 0xC23D3D3D, + 0xFF000000, + 0xFF3D3D3D, + 0xC2C2C2C2, + 0xC2FFFFFF, + 0xFFC2C2C2, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x00292929, + 0x29000000, + 0x29292929, + 0x00D6D6D6, + 0x00FFFFFF, + 0x29D6D6D6, + 0x29FFFFFF, + 0xD6000000, + 0xD6292929, + 0xFF000000, + 0xFF292929, + 0xD6D6D6D6, + 0xD6FFFFFF, + 0xFFD6D6D6, + 0xFFFFFFFF, + }, + }, + { + { + 0x00000000, + 0x3D3D0000, + 0x00003D3D, + 0x3D3D3D3D, + 0xC2C20000, + 0xFFFF0000, + 0xC2C23D3D, + 0xFFFF3D3D, + 0x0000C2C2, + 0x3D3DC2C2, + 0x0000FFFF, + 0x3D3DFFFF, + 0xC2C2C2C2, + 0xFFFFC2C2, + 0xC2C2FFFF, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x29290000, + 0x00002929, + 0x29292929, + 0xD6D60000, + 0xFFFF0000, + 0xD6D62929, + 0xFFFF2929, + 0x0000D6D6, + 0x2929D6D6, + 0x0000FFFF, + 0x2929FFFF, + 0xD6D6D6D6, + 0xFFFFD6D6, + 0xD6D6FFFF, + 0xFFFFFFFF, + }, + }, + { + { + 0x00000000, + 0x003D3D00, + 0x3D00003D, + 0x3D3D3D3D, + 0x00C2C200, + 0x00FFFF00, + 0x3DC2C23D, + 0x3DFFFF3D, + 0xC20000C2, + 0xC23D3DC2, + 0xFF0000FF, + 0xFF3D3DFF, + 0xC2C2C2C2, + 0xC2FFFFC2, + 0xFFC2C2FF, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x00292900, + 0x29000029, + 0x29292929, + 0x00D6D600, + 0x00FFFF00, + 0x29D6D629, + 0x29FFFF29, + 0xD60000D6, + 0xD62929D6, + 0xFF0000FF, + 0xFF2929FF, + 0xD6D6D6D6, + 0xD6FFFFD6, + 0xFFD6D6FF, + 0xFFFFFFFF, + }, + }, + { + { + 0x00000000, + 0x00003D00, + 0x3D3D003D, + 0x3D3D3D3D, + 0x0000C200, + 0x0000FF00, + 0x3D3DC23D, + 0x3D3DFF3D, + 0xC2C200C2, + 0xC2C23DC2, + 0xFFFF00FF, + 0xFFFF3DFF, + 0xC2C2C2C2, + 0xC2C2FFC2, + 0xFFFFC2FF, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x00002900, + 0x29290029, + 0x29292929, + 0x0000D600, + 0x0000FF00, + 0x2929D629, + 0x2929FF29, + 0xD6D600D6, + 0xD6D629D6, + 0xFFFF00FF, + 0xFFFF29FF, + 0xD6D6D6D6, + 0xD6D6FFD6, + 0xFFFFD6FF, + 0xFFFFFFFF, + }, + }, +}; + +// Round 2: 2 offsets × 4 children × 16 LUT values (regenerated from filter() brute-force) +static const uint32_t R2_COMBINED[2][4][16] = { + { + { + 0x00000000, + 0x77000777, + 0x00777000, + 0x77777777, + 0x88000888, + 0xFF000FFF, + 0x88777888, + 0xFF777FFF, + 0x00888000, + 0x77888777, + 0x00FFF000, + 0x77FFF777, + 0x88888888, + 0xFF888FFF, + 0x88FFF888, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x11000111, + 0x00111000, + 0x11111111, + 0xEE000EEE, + 0xFF000FFF, + 0xEE111EEE, + 0xFF111FFF, + 0x00EEE000, + 0x11EEE111, + 0x00FFF000, + 0x11FFF111, + 0xEEEEEEEE, + 0xFFEEEFFF, + 0xEEFFFEEE, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x66000666, + 0x00666000, + 0x66666666, + 0x99000999, + 0xFF000FFF, + 0x99666999, + 0xFF666FFF, + 0x00999000, + 0x66999666, + 0x00FFF000, + 0x66FFF666, + 0x99999999, + 0xFF999FFF, + 0x99FFF999, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x66000666, + 0x00666000, + 0x66666666, + 0x99000999, + 0xFF000FFF, + 0x99666999, + 0xFF666FFF, + 0x00999000, + 0x66999666, + 0x00FFF000, + 0x66FFF666, + 0x99999999, + 0xFF999FFF, + 0x99FFF999, + 0xFFFFFFFF, + }, + }, + { + { + 0x00000000, + 0x00700770, + 0x77077007, + 0x77777777, + 0x00800880, + 0x00F00FF0, + 0x77877887, + 0x77F77FF7, + 0x88088008, + 0x88788778, + 0xFF0FF00F, + 0xFF7FF77F, + 0x88888888, + 0x88F88FF8, + 0xFF8FF88F, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x00100110, + 0x11011001, + 0x11111111, + 0x00E00EE0, + 0x00F00FF0, + 0x11E11EE1, + 0x11F11FF1, + 0xEE0EE00E, + 0xEE1EE11E, + 0xFF0FF00F, + 0xFF1FF11F, + 0xEEEEEEEE, + 0xEEFEEFFE, + 0xFFEFFEEF, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x00600660, + 0x66066006, + 0x66666666, + 0x00900990, + 0x00F00FF0, + 0x66966996, + 0x66F66FF6, + 0x99099009, + 0x99699669, + 0xFF0FF00F, + 0xFF6FF66F, + 0x99999999, + 0x99F99FF9, + 0xFF9FF99F, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x00600660, + 0x66066006, + 0x66666666, + 0x00900990, + 0x00F00FF0, + 0x66966996, + 0x66F66FF6, + 0x99099009, + 0x99699669, + 0xFF0FF00F, + 0xFF6FF66F, + 0x99999999, + 0x99F99FF9, + 0xFF9FF99F, + 0xFFFFFFFF, + }, + }, +}; + +// Round 3: Individual child masks for target=1 — [child_idx][lut] +// child_idx c encodes R1-R2-R3 binary choices (b0*4 + b1*2 + b2) +// For target=0: use ~mask at runtime (complement) +// Replaces R3_PAIRED: same total size (512 bytes) +// Verification: R3_PAIRED[1][i][lut] == R3_INDIVIDUAL[2*i][lut] | R3_INDIVIDUAL[2*i+1][lut] +static const uint32_t R3_INDIVIDUAL[8][16] = { + { + 0x00000000, + 0x0C3CF03F, + 0xF3C30FC0, + 0xFFFFFFFF, + 0x00000000, + 0x0C3CF03F, + 0xF3C30FC0, + 0xFFFFFFFF, + 0x00000000, + 0x0C3CF03F, + 0xF3C30FC0, + 0xFFFFFFFF, + 0x00000000, + 0x0C3CF03F, + 0xF3C30FC0, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x04145015, + 0x51410540, + 0x55555555, + 0x0828A02A, + 0x0C3CF03F, + 0x5969A56A, + 0x5D7DF57F, + 0xA2820A80, + 0xA6965A95, + 0xF3C30FC0, + 0xF7D75FD5, + 0xAAAAAAAA, + 0xAEBEFABF, + 0xFBEBAFEA, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x0828A02A, + 0xA2820A80, + 0xAAAAAAAA, + 0x04145015, + 0x0C3CF03F, + 0xA6965A95, + 0xAEBEFABF, + 0x51410540, + 0x5969A56A, + 0xF3C30FC0, + 0xFBEBAFEA, + 0x55555555, + 0x5D7DF57F, + 0xF7D75FD5, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x0828A02A, + 0xA2820A80, + 0xAAAAAAAA, + 0x04145015, + 0x0C3CF03F, + 0xA6965A95, + 0xAEBEFABF, + 0x51410540, + 0x5969A56A, + 0xF3C30FC0, + 0xFBEBAFEA, + 0x55555555, + 0x5D7DF57F, + 0xF7D75FD5, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x04145015, + 0x51410540, + 0x55555555, + 0x0828A02A, + 0x0C3CF03F, + 0x5969A56A, + 0x5D7DF57F, + 0xA2820A80, + 0xA6965A95, + 0xF3C30FC0, + 0xF7D75FD5, + 0xAAAAAAAA, + 0xAEBEFABF, + 0xFBEBAFEA, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x0C3CF03F, + 0x0C3CF03F, + 0x0C3CF03F, + 0x0C3CF03F, + 0xF3C30FC0, + 0xF3C30FC0, + 0xF3C30FC0, + 0xF3C30FC0, + 0xFFFFFFFF, + 0xFFFFFFFF, + 0xFFFFFFFF, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x04145015, + 0x51410540, + 0x55555555, + 0x0828A02A, + 0x0C3CF03F, + 0x5969A56A, + 0x5D7DF57F, + 0xA2820A80, + 0xA6965A95, + 0xF3C30FC0, + 0xF7D75FD5, + 0xAAAAAAAA, + 0xAEBEFABF, + 0xFBEBAFEA, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x04145015, + 0x51410540, + 0x55555555, + 0x0828A02A, + 0x0C3CF03F, + 0x5969A56A, + 0x5D7DF57F, + 0xA2820A80, + 0xA6965A95, + 0xF3C30FC0, + 0xF7D75FD5, + 0xAAAAAAAA, + 0xAEBEFABF, + 0xFBEBAFEA, + 0xFFFFFFFF, + }, +}; + +// Combined prefilter + reconstruction in one pass. +// leaf_masks[c] (c=0..7) gives lanes where child c survives R0-R3+R4. +// Return value is OR of all 8 leaf_masks (= lane survival mask). +uint32_t + batch_prelude_unified(uint32_t batch_base, int oks, uint32_t r4_mask, uint32_t leaf_masks[8]) { + OPT_BARRIER(oks); + + // --- ROUND 0 --- + uint32_t idx_hi = (batch_base >> 8) & 0xFF; + uint32_t idx_nib = (batch_base >> 16) & 0xF; + uint32_t shared = lookup2[idx_hi] | ((0x0d938 >> idx_nib) & 1); + int lut = FILTER_LUT_TABLE[shared]; + uint32_t off0 = (batch_base >> 5) & 7; + + uint32_t valid = R0_COMBINED[off0][lut]; + if((oks & 1) == 0) valid = ~valid; + if(!valid) return 0; + + // --- ROUND 1 --- + int target = BIT(oks, 1); + uint32_t node_base = (batch_base << 1); + shared = lookup2[(node_base >> 8) & 0xFF] | ((0x0d938 >> ((node_base >> 16) & 0xF)) & 1); + lut = FILTER_LUT_TABLE[shared]; + uint32_t off1 = (batch_base >> 5) & 3; + + uint32_t p0 = R1_COMBINED[off1][0][lut]; + uint32_t p1 = R1_COMBINED[off1][1][lut]; + if(target == 0) { + p0 = ~p0; + p1 = ~p1; + } + p0 &= valid; + p1 &= valid; + if(!(p0 | p1)) return 0; + + // --- ROUND 2 --- + target = BIT(oks, 2); + node_base = (batch_base << 2); + shared = lookup2[(node_base >> 8) & 0xFF] | ((0x0d938 >> ((node_base >> 16) & 0xF)) & 1); + lut = FILTER_LUT_TABLE[shared]; + uint32_t off2 = (batch_base >> 5) & 1; + + uint32_t p00 = R2_COMBINED[off2][0][lut]; + uint32_t p01 = R2_COMBINED[off2][1][lut]; + uint32_t p10 = R2_COMBINED[off2][2][lut]; + uint32_t p11 = R2_COMBINED[off2][3][lut]; + if(target == 0) { + p00 = ~p00; + p01 = ~p01; + p10 = ~p10; + p11 = ~p11; + } + p00 &= p0; + p01 &= p0; + p10 &= p1; + p11 &= p1; + if(!(p00 | p01 | p10 | p11)) return 0; + + // --- ROUND 3 (individual child masks) --- + node_base = (batch_base << 3); + shared = lookup2[(node_base >> 8) & 0xFF] | ((0x0d938 >> ((node_base >> 16) & 0xF)) & 1); + lut = FILTER_LUT_TABLE[shared]; + + int target3 = BIT(oks, 3); + + // Load 8 individual child masks, apply target complement, AND with parent + r4_mask + // Child c: parent is p[c>>2][c&2 ? 1 : 0] → parent index from R1 bit (c>>2) and R2 bit ((c>>1)&1) + // Parent mapping: c=0,1→p00 c=2,3→p01 c=4,5→p10 c=6,7→p11 + const uint32_t parents[4] = {p00, p01, p10, p11}; + uint32_t all = 0; + for(int c = 0; c < 8; c++) { + uint32_t m = R3_INDIVIDUAL[c][lut]; + if(target3 == 0) m = ~m; + m &= parents[c >> 1]; + m &= r4_mask; + leaf_masks[c] = m; + all |= m; + } + + return all; +} diff --git a/applications/system/mfkey/mfkey_batch_prelude.h b/applications/system/mfkey/mfkey_batch_prelude.h new file mode 100644 index 000000000..7e113bc04 --- /dev/null +++ b/applications/system/mfkey/mfkey_batch_prelude.h @@ -0,0 +1,12 @@ +#ifndef MFKEY_BATCH_PRELUDE_H +#define MFKEY_BATCH_PRELUDE_H + +#include +#include "mfkey.h" + +// Returns lane survival mask AND per-child leaf masks. +// leaf_masks[c] (c=0..7) gives lanes where child c survives R0-R3+R4. +uint32_t + batch_prelude_unified(uint32_t batch_base, int oks, uint32_t r4_mask, uint32_t leaf_masks[8]); + +#endif // MFKEY_BATCH_PRELUDE_H diff --git a/applications/system/mfkey/mfkey_bs_verify.c b/applications/system/mfkey/mfkey_bs_verify.c new file mode 100644 index 000000000..6f312ba0f --- /dev/null +++ b/applications/system/mfkey/mfkey_bs_verify.c @@ -0,0 +1,530 @@ +// 32-way SWAR (SIMD Within A Register) verification of candidate LFSR states. +// Each bit position in a uint32_t represents one of 32 parallel lanes. + +#pragma GCC optimize("O3") + +#include "mfkey_bs_verify.h" +#include "crypto1.h" +#include + +// VFP register parking: use the M4F's 32 FPU registers (s0-s31) to stash +// slow-changing values during filter execution, freeing GP registers. + +#if defined(__arm__) && defined(__ARM_FP) +#define VFP_PARK(var, slot) \ + float slot; \ + __asm__ volatile("vmov %0, %1" : "=t"(slot) : "r"(var)) +#define VFP_UNPARK(var, slot) __asm__ volatile("vmov %0, %1" : "=r"(var) : "t"(slot)) +#else +#define VFP_PARK(var, slot) uint32_t slot = (var) +#define VFP_UNPARK(var, slot) (var) = (slot) +#endif + +// Bit-sliced filter function (minimized sum-of-products form) + +static inline __attribute__((always_inline)) uint32_t + crypto1_lut_a(uint32_t d, uint32_t c, uint32_t b, uint32_t a) { + return (c & d) | (a & c & ~b) | (a & d & ~b) | (b & ~c & ~d); +} + +static inline __attribute__((always_inline)) uint32_t + crypto1_lut_b(uint32_t d, uint32_t c, uint32_t b, uint32_t a) { + return (b & c & d) | (a & b & ~c) | (c & ~b & ~d) | (d & ~a & ~b); +} + +static inline __attribute__((always_inline)) uint32_t + crypto1_bs_filter(const uint32_t* odd, uint32_t head) { + const uint32_t* p = odd + head; + uint32_t f4 = crypto1_lut_a(p[3], p[2], p[1], p[0]); + uint32_t f3 = crypto1_lut_b(p[7], p[6], p[5], p[4]); + uint32_t f2 = crypto1_lut_a(p[11], p[10], p[9], p[8]); + uint32_t f1 = crypto1_lut_a(p[15], p[14], p[13], p[12]); + uint32_t f0 = crypto1_lut_b(p[19], p[18], p[17], p[16]); + + uint32_t f32 = f3 & f2; + uint32_t res = (f32 & (f0 | f1)); + res |= (f4 & ((f1 & f3) | ~(f0 | f3))); + res |= (f0 & ~f2 & ((f1 & ~f4) | ~(f1 | f3))); + return res; +} + +// LFSR polynomial tap XOR functions + +static inline __attribute__((always_inline)) uint32_t + crypto1_bs_xor_taps_odd(const uint32_t* reg, uint32_t head) { + const uint32_t* p = reg + head; + uint32_t acc0 = p[2] ^ p[3] ^ p[4]; + uint32_t acc1 = p[6] ^ p[9] ^ p[10]; + uint32_t acc2 = p[11] ^ p[14] ^ p[15]; + uint32_t acc3 = p[16] ^ p[19] ^ p[21]; + return acc0 ^ acc1 ^ acc2 ^ acc3; +} + +static inline __attribute__((always_inline)) uint32_t + crypto1_bs_xor_taps_even(const uint32_t* reg, uint32_t head) { + const uint32_t* p = reg + head; + uint32_t acc0 = p[2] ^ p[11] ^ p[16]; + uint32_t acc1 = p[17] ^ p[18] ^ p[23]; + return acc0 ^ acc1; +} + +static inline __attribute__((always_inline)) uint32_t + poly_even_rollback_xor(const uint32_t* even, uint32_t head) { + const uint32_t* p = even + head; + return p[2] ^ p[11] ^ p[16] ^ p[17] ^ p[18]; +} + +// 32x32 SWAR butterfly transpose + +static void transpose_32x32(uint32_t* d) { + uint32_t t, r0, r1, r2, r3, r4, r5, r6, r7; + + // Step 1: 16x16 blocks + for(int i = 0; i < 16; i++) { + t = (d[i] >> 16 ^ d[i + 16]) & 0x0000FFFF; + d[i] ^= t << 16; + d[i + 16] ^= t; + } + + // Step 2: 8x8 blocks + for(int i = 0; i < 32; i += 16) { + for(int j = 0; j < 8; j++) { + t = (d[i + j] >> 8 ^ d[i + j + 8]) & 0x00FF00FF; + d[i + j] ^= t << 8; + d[i + j + 8] ^= t; + } + } + + // Steps 3-5: Process in 8-row blocks entirely in registers + for(int b = 0; b < 32; b += 8) { + r0 = d[b + 0]; + r1 = d[b + 1]; + r2 = d[b + 2]; + r3 = d[b + 3]; + r4 = d[b + 4]; + r5 = d[b + 5]; + r6 = d[b + 6]; + r7 = d[b + 7]; + + // Step 3: 4x4 blocks + t = (r0 >> 4 ^ r4) & 0x0F0F0F0F; + r0 ^= t << 4; + r4 ^= t; + t = (r1 >> 4 ^ r5) & 0x0F0F0F0F; + r1 ^= t << 4; + r5 ^= t; + t = (r2 >> 4 ^ r6) & 0x0F0F0F0F; + r2 ^= t << 4; + r6 ^= t; + t = (r3 >> 4 ^ r7) & 0x0F0F0F0F; + r3 ^= t << 4; + r7 ^= t; + + // Step 4: 2x2 blocks + t = (r0 >> 2 ^ r2) & 0x33333333; + r0 ^= t << 2; + r2 ^= t; + t = (r1 >> 2 ^ r3) & 0x33333333; + r1 ^= t << 2; + r3 ^= t; + t = (r4 >> 2 ^ r6) & 0x33333333; + r4 ^= t << 2; + r6 ^= t; + t = (r5 >> 2 ^ r7) & 0x33333333; + r5 ^= t << 2; + r7 ^= t; + + // Step 5: 1x1 blocks + t = (r0 >> 1 ^ r1) & 0x55555555; + r0 ^= t << 1; + r1 ^= t; + t = (r2 >> 1 ^ r3) & 0x55555555; + r2 ^= t << 1; + r3 ^= t; + t = (r4 >> 1 ^ r5) & 0x55555555; + r4 ^= t << 1; + r5 ^= t; + t = (r6 >> 1 ^ r7) & 0x55555555; + r6 ^= t << 1; + r7 ^= t; + + d[b + 0] = r0; + d[b + 1] = r1; + d[b + 2] = r2; + d[b + 3] = r3; + d[b + 4] = r4; + d[b + 5] = r5; + d[b + 6] = r6; + d[b + 7] = r7; + } +} + +void bs_init_from_candidates(Crypto1BitSlice* bs, const BsCandidateBatch* batch) { + bs->odd_head = 0; + bs->even_head = 0; + + uint32_t temp_odd[32]; + uint32_t temp_even[32]; + + int count = batch->count; + if(count >= 32) { + memcpy(temp_odd, batch->odd, 32 * sizeof(uint32_t)); + memcpy(temp_even, batch->even, 32 * sizeof(uint32_t)); + } else { + memcpy(temp_odd, batch->odd, count * sizeof(uint32_t)); + memcpy(temp_even, batch->even, count * sizeof(uint32_t)); + memset(temp_odd + count, 0, (32 - count) * sizeof(uint32_t)); + memset(temp_even + count, 0, (32 - count) * sizeof(uint32_t)); + } + + transpose_32x32(temp_odd); + transpose_32x32(temp_even); + + for(int i = 0; i < 24; i++) { + bs->odd[i] = temp_odd[i]; + bs->odd[i + 24] = temp_odd[i]; + bs->even[i] = temp_even[i]; + bs->even[i + 24] = temp_even[i]; + } +} + +// Rollback without keystream collection +static inline __attribute__((always_inline)) void + bs_rollback_word_noret(Crypto1BitSlice* bs, uint32_t in, uint32_t fb_mask) { + uint32_t* odd_ptr = bs->odd; + uint32_t* even_ptr = bs->even; + uint32_t oh = bs->odd_head; + uint32_t eh = bs->even_head; + + // Process first 16 bits (i = 31..16) + for(int i = 31; i >= 16; i--) { + uint32_t* tmp_ptr = odd_ptr; + odd_ptr = even_ptr; + even_ptr = tmp_ptr; + + uint32_t tmp_head = oh; + oh = eh; + eh = tmp_head; + + int bit_pos = 24 ^ i; // Crypto1 big-endian bit ordering + uint32_t in_bits = ((in >> bit_pos) & 1) ? 0xFFFFFFFF : 0; // Broadcast bit to all 32 lanes + + uint32_t extracted = even_ptr[eh]; + VFP_PARK(extracted, _vfp_extracted); + + uint32_t ks = crypto1_bs_filter(odd_ptr, oh); + VFP_UNPARK(extracted, _vfp_extracted); + uint32_t new_eh = eh + 1; + + uint32_t recovered_msb = extracted; + recovered_msb ^= poly_even_rollback_xor(even_ptr, new_eh); + recovered_msb ^= crypto1_bs_xor_taps_odd(odd_ptr, oh); + recovered_msb ^= in_bits; + recovered_msb ^= (ks & fb_mask); + + even_ptr[new_eh + 23] = recovered_msb; + if(new_eh + 23 >= 24) even_ptr[new_eh + 23 - 24] = recovered_msb; + + eh = new_eh; + } + + // Intermediate normalization + if(oh >= 24) oh -= 24; + if(eh >= 24) eh -= 24; + + // Process remaining 16 bits (i = 15..0) + for(int i = 15; i >= 0; i--) { + uint32_t* tmp_ptr = odd_ptr; + odd_ptr = even_ptr; + even_ptr = tmp_ptr; + + uint32_t tmp_head = oh; + oh = eh; + eh = tmp_head; + + int bit_pos = 24 ^ i; + uint32_t in_bits = ((in >> bit_pos) & 1) ? 0xFFFFFFFF : 0; // Sign-extend bit without UB + + uint32_t extracted = even_ptr[eh]; + VFP_PARK(extracted, _vfp_extracted); + + uint32_t ks = crypto1_bs_filter(odd_ptr, oh); + VFP_UNPARK(extracted, _vfp_extracted); + uint32_t new_eh = eh + 1; + + uint32_t recovered_msb = extracted; + recovered_msb ^= poly_even_rollback_xor(even_ptr, new_eh); + recovered_msb ^= crypto1_bs_xor_taps_odd(odd_ptr, oh); + recovered_msb ^= in_bits; + recovered_msb ^= (ks & fb_mask); + + even_ptr[new_eh + 23] = recovered_msb; + if(new_eh + 23 >= 24) even_ptr[new_eh + 23 - 24] = recovered_msb; + + eh = new_eh; + } + + if(oh >= 24) oh -= 24; + if(eh >= 24) eh -= 24; + + bs->odd_head = oh; + bs->even_head = eh; +} + +static inline __attribute__((always_inline)) void + bs_crypt_word_noret(Crypto1BitSlice* bs, uint32_t in, uint32_t enc_mask) { + uint32_t* odd_ptr = bs->odd; + uint32_t* even_ptr = bs->even; + uint32_t oh = bs->odd_head; + uint32_t eh = bs->even_head; + + for(int i = 0; i < 32; i++) { + int bit_pos = 24 ^ i; + uint32_t in_bits = ((in >> bit_pos) & 1) ? 0xFFFFFFFF : 0; // Sign-extend bit without UB + + VFP_PARK(in_bits, _vfp_in_bits); + uint32_t ks = crypto1_bs_filter(odd_ptr, oh); + VFP_UNPARK(in_bits, _vfp_in_bits); + uint32_t feed = (ks & enc_mask) ^ in_bits; + feed ^= crypto1_bs_xor_taps_odd(odd_ptr, oh); + feed ^= crypto1_bs_xor_taps_even(even_ptr, eh); + + uint32_t new_eh = (eh == 0) ? 23 : (eh - 1); + + even_ptr[new_eh] = feed; + even_ptr[new_eh + 24] = feed; + eh = new_eh; + + uint32_t* tmp_ptr = odd_ptr; + odd_ptr = even_ptr; + even_ptr = tmp_ptr; + + uint32_t tmp_head = oh; + oh = eh; + eh = tmp_head; + } + + bs->odd_head = oh; + bs->even_head = eh; +} + +// Fused rollback + keystream comparison with byte-boundary early exit. +// fb_mask: 0 when keystream does not feed back into LFSR. + +static inline __attribute__((always_inline)) uint32_t bs_rollback_word_check_ks( + Crypto1BitSlice* bs, + uint32_t in, + uint32_t fb_mask, + uint32_t expected, + uint32_t alive) { + uint32_t* odd_ptr = bs->odd; + uint32_t* even_ptr = bs->even; + uint32_t oh = bs->odd_head; + uint32_t eh = bs->even_head; + + for(int i = 31; i >= 0; i--) { + uint32_t* tmp_ptr = odd_ptr; + odd_ptr = even_ptr; + even_ptr = tmp_ptr; + + uint32_t tmp_head = oh; + oh = eh; + eh = tmp_head; + + int bit_pos = 24 ^ i; + uint32_t in_bits = ((in >> bit_pos) & 1) ? 0xFFFFFFFF : 0; + + uint32_t extracted = even_ptr[eh]; + uint32_t new_eh = eh + 1; + + // Park values not needed during filter in VFP registers + VFP_PARK(alive, _vfp_alive); + VFP_PARK(extracted, _vfp_extracted); + VFP_PARK(new_eh, _vfp_new_eh); + + uint32_t ks = crypto1_bs_filter(odd_ptr, oh); + + // Restore from VFP + VFP_UNPARK(alive, _vfp_alive); + VFP_UNPARK(extracted, _vfp_extracted); + VFP_UNPARK(new_eh, _vfp_new_eh); + + // Compare keystream bit against expected + uint32_t exp_broadcast = ((expected >> bit_pos) & 1) ? 0xFFFFFFFF : 0; + alive &= ~(ks ^ exp_broadcast); + + // Rollback LFSR step + uint32_t recovered_msb = extracted; + recovered_msb ^= poly_even_rollback_xor(even_ptr, new_eh); + recovered_msb ^= crypto1_bs_xor_taps_odd(odd_ptr, oh); + recovered_msb ^= in_bits; + recovered_msb ^= (ks & fb_mask); + + even_ptr[new_eh + 23] = recovered_msb; + if(new_eh + 23 >= 24) even_ptr[new_eh + 23 - 24] = recovered_msb; + + eh = new_eh; + + // Early exit at byte boundaries + if((i & 7) == 0 && !alive) { + if(oh >= 24) oh -= 24; + if(eh >= 24) eh -= 24; + bs->odd_head = oh; + bs->even_head = eh; + return 0; + } + } + + if(oh >= 24) oh -= 24; + if(eh >= 24) eh -= 24; + bs->odd_head = oh; + bs->even_head = eh; + return alive; +} + +// Fused forward crypt + keystream comparison with byte-boundary early exit. +// enc_mask: 0 when keystream does not feed back into LFSR. + +static inline __attribute__((always_inline)) uint32_t bs_crypt_word_check_ks( + Crypto1BitSlice* bs, + uint32_t in, + uint32_t enc_mask, + uint32_t expected, + uint32_t alive) { + uint32_t* odd_ptr = bs->odd; + uint32_t* even_ptr = bs->even; + uint32_t oh = bs->odd_head; + uint32_t eh = bs->even_head; + + for(int i = 0; i < 32; i++) { + int bit_pos = 24 ^ i; + uint32_t in_bits = ((in >> bit_pos) & 1) ? 0xFFFFFFFF : 0; + + // Park alive in VFP during heavy filter computation + VFP_PARK(alive, _vfp_alive); + + uint32_t ks = crypto1_bs_filter(odd_ptr, oh); + + // Restore alive from VFP + VFP_UNPARK(alive, _vfp_alive); + + // Compare keystream bit against expected + uint32_t exp_broadcast = ((expected >> bit_pos) & 1) ? 0xFFFFFFFF : 0; + alive &= ~(ks ^ exp_broadcast); + + // LFSR advance + uint32_t feed = (ks & enc_mask) ^ in_bits; + feed ^= crypto1_bs_xor_taps_odd(odd_ptr, oh); + feed ^= crypto1_bs_xor_taps_even(even_ptr, eh); + + uint32_t new_eh = (eh == 0) ? 23 : (eh - 1); + even_ptr[new_eh] = feed; + even_ptr[new_eh + 24] = feed; + eh = new_eh; + + uint32_t* tmp_ptr = odd_ptr; + odd_ptr = even_ptr; + even_ptr = tmp_ptr; + uint32_t tmp_head = oh; + oh = eh; + eh = tmp_head; + + // Early exit at byte boundaries + if((i & 7) == 7 && !alive) { + bs->odd_head = oh; + bs->even_head = eh; + return 0; + } + } + + bs->odd_head = oh; + bs->even_head = eh; + return alive; +} + +// mfkey32 verification kernel +uint32_t bs_verify_batch_32(Crypto1BitSlice* bs, MfClassicNonce* nonce, uint32_t alive) { + // Checkpoint 1: rollback with keystream check + uint32_t expected1 = nonce->ar0_enc ^ nonce->p64; + alive = bs_rollback_word_check_ks(bs, 0, 0, expected1, alive); + if(!alive) return 0; + + // Intermediate rollback/crypt (no keystream check) + bs_rollback_word_noret(bs, nonce->nr0_enc, 0xFFFFFFFF); + bs_rollback_word_noret(bs, nonce->uid_xor_nt0, 0); + bs_crypt_word_noret(bs, nonce->uid_xor_nt1, 0); + bs_crypt_word_noret(bs, nonce->nr1_enc, 0xFFFFFFFF); + + // Checkpoint 2: forward crypt with keystream check + uint32_t expected2 = nonce->ar1_enc ^ nonce->p64b; + alive = bs_crypt_word_check_ks(bs, 0, 0, expected2, alive); + + return alive; +} + +void bs_extract_key(const BsCandidateBatch* batch, int lane, MfClassicNonce* nonce) { + struct Crypto1State t; + t.odd = batch->odd[lane]; + t.even = batch->even[lane]; + + if(nonce->attack == mfkey32) { + napi_lfsr_rollback_word(&t, 0, 0); + rollback_word_noret(&t, nonce->nr0_enc, 1); + rollback_word_noret(&t, nonce->uid_xor_nt0, 0); + } else if(nonce->attack == static_nested) { + rollback_word_noret(&t, nonce->uid_xor_nt1, 0); + } else { + napi_lfsr_rollback_word(&t, nonce->uid_xor_nt0, 0); + } + + crypto1_get_lfsr(&t, &nonce->key); +} + +// static_nested verification kernel +uint32_t bs_verify_batch_32_nested(Crypto1BitSlice* bs, MfClassicNonce* nonce, uint32_t alive) { + // Step 1: Rollback uid_xor_nt1 (fb=0) — no keystream check + bs_rollback_word_noret(bs, nonce->uid_xor_nt1, 0); + + // Step 2: Forward crypt uid_xor_nt0 (enc_mask=0) with keystream check + alive = bs_crypt_word_check_ks(bs, nonce->uid_xor_nt0, 0, nonce->ks1_1_enc, alive); + + return alive; +} + +// Scalar parity validation (cold path, typically 0-2 survivors) +static uint32_t validate_survivors_parity( + const BsCandidateBatch* batch, + MfClassicNonce* nonce, + uint32_t alive) { + uint32_t parity_valid = 0; + uint32_t remaining = alive; + while(remaining) { + int lane = __builtin_ctz(remaining); + remaining &= remaining - 1; + + struct Crypto1State t; + t.odd = batch->odd[lane]; + t.even = batch->even[lane]; + napi_lfsr_rollback_word(&t, nonce->uid_xor_nt0, 0); + + uint8_t pk; + struct Crypto1State temp = {t.odd, t.even}; + if((crypt_word_par(&temp, nonce->uid_xor_nt0, 0, nonce->nt0, &pk) == nonce->ks1_1_enc) && + (pk == nonce->par_1)) { + parity_valid |= (1U << lane); + } + } + return parity_valid; +} + +// static_encrypted verification kernel +// Hybrid: bitsliced keystream pruning + scalar parity on survivors. +uint32_t bs_verify_batch_32_encrypted( + Crypto1BitSlice* bs, + const BsCandidateBatch* batch, + MfClassicNonce* nonce, + uint32_t alive) { + alive = bs_rollback_word_check_ks(bs, nonce->uid_xor_nt0, 0, nonce->ks1_1_enc, alive); + if(!alive) return 0; + return validate_survivors_parity(batch, nonce, alive); +} diff --git a/applications/system/mfkey/mfkey_bs_verify.h b/applications/system/mfkey/mfkey_bs_verify.h new file mode 100644 index 000000000..2555626f0 --- /dev/null +++ b/applications/system/mfkey/mfkey_bs_verify.h @@ -0,0 +1,71 @@ +#ifndef MFKEY_BS_VERIFY_H +#define MFKEY_BS_VERIFY_H + +// Bit-sliced verification for all attack types (mfkey32, static_nested, +// static_encrypted). Processes 32 candidate LFSR states in parallel using +// SWAR (SIMD Within A Register) techniques. + +#include "mfkey.h" +#include +#include + +#define BS_BATCH_SIZE 32 + +typedef struct { + uint32_t odd[BS_BATCH_SIZE]; // Odd half-states (24-bit, lower bits used) + uint32_t even[BS_BATCH_SIZE]; // Even half-states (24-bit) + int count; // Current fill level (0-32) +} BsCandidateBatch; + +// Mirrored runway buffer: bits 0-23 mirrored to 24-47. +// Max read index: head (0-23) + max tap offset (23) = 46 < 48. +typedef struct { + uint32_t odd[48]; // 24 LFSR bits mirrored 2 times to avoid bounds checks + uint32_t even[48]; // Each element holds 32 bits (one per lane) + uint32_t odd_head; // Current head position (0-23 normalized) + uint32_t even_head; +} Crypto1BitSlice; + +// Initialize empty batch +static inline void bs_batch_init(BsCandidateBatch* batch) { + batch->count = 0; +} + +// Add candidate to batch +// Returns true if batch is now full (caller should verify and reset) +static inline bool bs_batch_add(BsCandidateBatch* batch, uint32_t odd, uint32_t even) { + if(batch->count < BS_BATCH_SIZE) { + batch->odd[batch->count] = odd; + batch->even[batch->count] = even; + batch->count++; + } + return batch->count >= BS_BATCH_SIZE; +} + +// Initialize bitsliced state from candidate batch (transpose scalar → SWAR) +// Call once before dispatching to any verification kernel +void bs_init_from_candidates(Crypto1BitSlice* bs, const BsCandidateBatch* batch); + +// Verify batch of up to 32 candidates against nonce data +// bs: pre-initialized bitsliced state (from bs_init_from_candidates) +// alive: lane mask with zero-states already rejected +// Returns bitmask where bit i is set if candidate i is valid +uint32_t bs_verify_batch_32(Crypto1BitSlice* bs, MfClassicNonce* nonce, uint32_t alive); + +// Extract key from valid lane (unified for all attack types) +// lane = __builtin_ctz(valid_mask) to get first valid lane +void bs_extract_key(const BsCandidateBatch* batch, int lane, MfClassicNonce* nonce); + +// Static nested verification: rollback uid_xor_nt1 → forward crypt uid_xor_nt0 +// Returns bitmask of valid lanes (first-match) +uint32_t bs_verify_batch_32_nested(Crypto1BitSlice* bs, MfClassicNonce* nonce, uint32_t alive); + +// Static encrypted verification: rollback uid_xor_nt0 + parity check +// Returns bitmask of ALL valid lanes (multi-result) +uint32_t bs_verify_batch_32_encrypted( + Crypto1BitSlice* bs, + const BsCandidateBatch* batch, + MfClassicNonce* nonce, + uint32_t alive); + +#endif // MFKEY_BS_VERIFY_H diff --git a/applications/system/mfkey/mfkey_dedup.h b/applications/system/mfkey/mfkey_dedup.h new file mode 100644 index 000000000..b6c193bd1 --- /dev/null +++ b/applications/system/mfkey/mfkey_dedup.h @@ -0,0 +1,141 @@ +#ifndef MFKEY_DEDUP_H +#define MFKEY_DEDUP_H + +#include "mfkey.h" +#include "crypto1.h" +#include +#include + +#define OPT_BARRIER(x) __asm__ volatile("" : "+r"(x)) + +// Golden ratio multiply-shift hash for 20-bit semi-states → 11-bit output +#define FIB_HASH_20BIT(x) (((x) * 2654435769u) >> 21) + +// Eliminates candidates before tree expansion by checking rounds 1-3 in advance. +// Returns 1 if any path survives to round 4, else 0. +static inline __attribute__((always_inline)) int + prefilter_rounds_1_3(uint32_t semi_state, int oks) { + OPT_BARRIER(oks); // Prevent hoisting of BIT(oks, round) extractions + // Precompute nibble bits for rounds 1-3 + // Round 1: nibble = (semi_state >> 15) & 0xF + // Round 2: nibble = (semi_state >> 14) & 0xF + // Round 3: nibble = (semi_state >> 13) & 0xF + uint32_t nib1 = ((0x0d938 >> ((semi_state >> 15) & 0xF)) & 1); + uint32_t nib2 = ((0x0d938 >> ((semi_state >> 14) & 0xF)) & 1); + uint32_t nib3 = ((0x0d938 >> ((semi_state >> 13) & 0xF)) & 1); + + // Round 1: 2 potential children -> 2-bit mask + uint32_t v = semi_state << 1; + int target = BIT(oks, 1); + uint32_t fp = filter_pair_with_nib(v, nib1); + int f0 = FILTER_F0(fp); + int f1 = FILTER_F1(fp); + + // r1_valid: bit 0 = v survives, bit 1 = v|1 survives + int r1_valid = ((f0 == target) << 0) | ((f1 == target) << 1); + if(!r1_valid) return 0; + + // Round 2: up to 4 grandchildren -> 4-bit mask + // Tracks exactly which grandchildren survive + target = BIT(oks, 2); + int r2_valid = 0; + + if(r1_valid & 1) { // v survived R1 + fp = filter_pair_with_nib(v << 1, nib2); + f0 = FILTER_F0(fp); + f1 = FILTER_F1(fp); + if(f0 == target) r2_valid |= 1; // v<<1 survives + if(f1 == target) r2_valid |= 2; // (v<<1)|1 survives + } + if(r1_valid & 2) { // v|1 survived R1 + fp = filter_pair_with_nib((v | 1) << 1, nib2); + f0 = FILTER_F0(fp); + f1 = FILTER_F1(fp); + if(f0 == target) r2_valid |= 4; // (v|1)<<1 survives + if(f1 == target) r2_valid |= 8; // ((v|1)<<1)|1 survives + } + + if(!r2_valid) return 0; + + // Round 3: only check children of actual R2 survivors + target = BIT(oks, 3); + + if(r2_valid & 1) { // v<<1 survived R2 + fp = filter_pair_with_nib(v << 2, nib3); + f0 = FILTER_F0(fp); + f1 = FILTER_F1(fp); + if(f0 == target || f1 == target) return 1; + } + if(r2_valid & 2) { // (v<<1)|1 survived R2 + fp = filter_pair_with_nib((v << 2) | 2, nib3); + f0 = FILTER_F0(fp); + f1 = FILTER_F1(fp); + if(f0 == target || f1 == target) return 1; + } + if(r2_valid & 4) { // (v|1)<<1 survived R2 + fp = filter_pair_with_nib((v | 1) << 2, nib3); + f0 = FILTER_F0(fp); + f1 = FILTER_F1(fp); + if(f0 == target || f1 == target) return 1; + } + if(r2_valid & 8) { // ((v|1)<<1)|1 survived R2 + fp = filter_pair_with_nib(((v | 1) << 2) | 2, nib3); + f0 = FILTER_F0(fp); + f1 = FILTER_F1(fp); + if(f0 == target || f1 == target) return 1; + } + + return 0; +} + +// 8x unrolled Duff's device for duplicate detection in packed 24-bit states +static inline __attribute__((always_inline)) bool scan_for_duplicate_8x( + const uint8_t* states, // Packed 24-bit state array + int count, // Number of entries to scan + uint32_t state_masked) // Target state with MSB masked off (& 0x00FFFFFF) +{ + if(count <= 0) return false; + + const uint8_t* p = states; + int n = (count + 7) / 8; // Number of 8-iteration chunks (rounded up) + + // Duff's Device: jump into unrolled loop based on remainder + switch(count % 8) { + case 0: + do { + if((*(uint32_t*)p & 0x00FFFFFF) == state_masked) return true; + p += 3; + __attribute__((fallthrough)); + case 7: + if((*(uint32_t*)p & 0x00FFFFFF) == state_masked) return true; + p += 3; + __attribute__((fallthrough)); + case 6: + if((*(uint32_t*)p & 0x00FFFFFF) == state_masked) return true; + p += 3; + __attribute__((fallthrough)); + case 5: + if((*(uint32_t*)p & 0x00FFFFFF) == state_masked) return true; + p += 3; + __attribute__((fallthrough)); + case 4: + if((*(uint32_t*)p & 0x00FFFFFF) == state_masked) return true; + p += 3; + __attribute__((fallthrough)); + case 3: + if((*(uint32_t*)p & 0x00FFFFFF) == state_masked) return true; + p += 3; + __attribute__((fallthrough)); + case 2: + if((*(uint32_t*)p & 0x00FFFFFF) == state_masked) return true; + p += 3; + __attribute__((fallthrough)); + case 1: + if((*(uint32_t*)p & 0x00FFFFFF) == state_masked) return true; + p += 3; + } while(--n > 0); + } + return false; +} + +#endif // MFKEY_DEDUP_H diff --git a/applications/system/mfkey/mfkey_recovery.c b/applications/system/mfkey/mfkey_recovery.c new file mode 100644 index 000000000..8ce52cffd --- /dev/null +++ b/applications/system/mfkey/mfkey_recovery.c @@ -0,0 +1,288 @@ +// MFKey Recovery - Sorting and LFSR state verification +// Extracted from mfkey_attack.c for better code organization + +#pragma GCC optimize("O3") + +#include "mfkey_recovery.h" +#include "crypto1.h" +#include "mfkey_bs_verify.h" +#include + +// External dependencies from mfkey.c +extern int sync_state(ProgramState* program_state); +extern void flush_key_buffer(ProgramState* program_state); +extern uint8_t MSB_LIMIT; + +// External from mfkey_attack.c +extern volatile bool g_abort_attack; +extern ProgramState* g_program_state; + +static inline __attribute__((always_inline)) int + binsearch(unsigned int data[], int start, int stop) { + unsigned int msb_val = data[stop] & 0xff000000; + + while(start != stop) { + int mid = start + ((stop - start) >> 1); + if(data[mid] >= msb_val) { + stop = mid; + } else { + start = mid + 1; + } + } + return start; +} + +// Radix sort: O(n) non-recursive replacement for quicksort +#define RADIX_BITS 8 +#define RADIX_SIZE (1 << RADIX_BITS) +#define RADIX_MASK (RADIX_SIZE - 1) + +unsigned int g_radix_temp[1280]; + +void radix_sort_32(unsigned int* arr, int low, int high, unsigned int* temp) { + int count = high - low + 1; + if(count <= 1) return; + + // Small arrays use insertion sort + if(count < 32) { + for(int i = low + 1; i <= high; i++) { + unsigned int key = arr[i]; + int j = i - 1; + while(j >= low && arr[j] > key) { + arr[j + 1] = arr[j]; + j--; + } + arr[j + 1] = key; + } + return; + } + + if(count > 1280) { + count = 1280; + high = low + count - 1; + } + + unsigned int hist[RADIX_SIZE]; + unsigned int* src = arr + low; + unsigned int* dst = temp; + + for(int pass = 0; pass < 4; pass++) { + int shift = pass * RADIX_BITS; + memset(hist, 0, sizeof(hist)); + for(int i = 0; i < count; i++) { + hist[(src[i] >> shift) & RADIX_MASK]++; + } + unsigned int total = 0; + for(int i = 0; i < RADIX_SIZE; i++) { + unsigned int c = hist[i]; + hist[i] = total; + total += c; + } + for(int i = 0; i < count; i++) { + int bucket = (src[i] >> shift) & RADIX_MASK; + dst[hist[bucket]++] = src[i]; + } + unsigned int* t = src; + src = dst; + dst = t; + } +} + +static inline __attribute__((always_inline)) void + update_contribution_odd(unsigned int data[], int item) { + unsigned int val = data[item]; + unsigned int p = val >> 25; + p = p << 1 | evenparity32(val & CONST_M1_1); + p = p << 1 | evenparity32(val & CONST_M2_1); + data[item] = (p << 24) | (val & 0xffffff); +} + +static inline __attribute__((always_inline)) void + update_contribution_even(unsigned int data[], int item) { + unsigned int val = data[item]; + unsigned int p = val >> 25; + p = p << 1 | evenparity32(val & CONST_M1_2); + p = p << 1 | evenparity32(val & CONST_M2_2); + data[item] = (p << 24) | (val & 0xffffff); +} + +static int __attribute__((hot)) extend_table_odd(unsigned int data[], int tbl, int end, int bit) { + for(data[tbl] <<= 1; tbl <= end; data[++tbl] <<= 1) { + int f0 = filter(data[tbl]); + int f1 = filter(data[tbl] | 1); + if((f0 ^ f1) != 0) { + data[tbl] |= f0 ^ bit; + update_contribution_odd(data, tbl); + } else if(f0 == bit) { + data[++end] = data[tbl + 1]; + data[tbl + 1] = data[tbl] | 1; + update_contribution_odd(data, tbl); + tbl++; + update_contribution_odd(data, tbl); + } else { + data[tbl--] = data[end--]; + } + } + return end; +} + +static int __attribute__((hot)) +extend_table_even(unsigned int data[], int tbl, int end, int bit, unsigned int in) { + in <<= 24; + for(data[tbl] <<= 1; tbl <= end; data[++tbl] <<= 1) { + int f0 = filter(data[tbl]); + int f1 = filter(data[tbl] | 1); + if((f0 ^ f1) != 0) { + data[tbl] |= f0 ^ bit; + update_contribution_even(data, tbl); + data[tbl] ^= in; + } else if(f0 == bit) { + data[++end] = data[tbl + 1]; + data[tbl + 1] = data[tbl] | 1; + update_contribution_even(data, tbl); + data[tbl++] ^= in; + update_contribution_even(data, tbl); + data[tbl] ^= in; + } else { + data[tbl--] = data[end--]; + } + } + return end; +} + +// Routes to correct bitsliced kernel by attack type. +// Returns: -1 = key found (mfkey32/static_nested), 0 = no match. +// For static_encrypted: buffers all valid keys, always returns 0. +static __attribute__((noinline)) int + bs_verify_dispatch(BsCandidateBatch* batch, MfClassicNonce* n) { + // Common: compute alive mask and reject zero states + uint32_t alive = (batch->count >= 32) ? 0xFFFFFFFF : ((1U << batch->count) - 1); + for(int i = 0; i < batch->count; i++) { + if(!(batch->odd[i] | batch->even[i])) { + alive &= ~(1U << i); + } + } + if(!alive) return 0; + + // Common: transpose scalar candidates → bitsliced SWAR state (once) + Crypto1BitSlice bs; + bs_init_from_candidates(&bs, batch); + + if(n->attack == mfkey32) { + uint32_t valid = bs_verify_batch_32(&bs, n, alive); + if(valid) { + int lane = __builtin_ctz(valid); + bs_extract_key(batch, lane, n); + return -1; + } + } else if(n->attack == static_nested) { + uint32_t valid = bs_verify_batch_32_nested(&bs, n, alive); + if(valid) { + int lane = __builtin_ctz(valid); + bs_extract_key(batch, lane, n); + return -1; + } + } else { + // static_encrypted: collect ALL valid keys + uint32_t valid = bs_verify_batch_32_encrypted(&bs, batch, n, alive); + while(valid) { + int lane = __builtin_ctz(valid); + valid &= valid - 1; + bs_extract_key(batch, lane, n); + // Buffer all valid keys for static_encrypted + g_program_state->num_candidates++; + g_program_state->key_buffer[g_program_state->key_buffer_count] = n->key; + g_program_state->key_idx_buffer[g_program_state->key_buffer_count] = n->key_idx; + g_program_state->key_buffer_count++; + if(g_program_state->key_buffer_count >= g_program_state->key_buffer_size) { + flush_key_buffer(g_program_state); + } + } + } + return 0; +} + +// 32-way parallel verification with recursive MSB-walk +int old_recover_bs( + unsigned int odd[], + int o_head, + int o_tail, + int oks, + unsigned int even[], + int e_head, + int e_tail, + int eks, + int rem, + int s, + MfClassicNonce* n, + unsigned int in, + int first_run) { + int o, e, i; + + if(rem == -1) { + BsCandidateBatch batch; + bs_batch_init(&batch); + + for(e = e_head; e <= e_tail; ++e) { + uint32_t e_val = (even[e] << 1) ^ evenparity32(even[e] & LF_POLY_EVEN) ^ (!!(in & 4)); + + for(o = o_head; o <= o_tail; ++o, ++s) { + uint32_t o_val = odd[o]; + uint32_t final_even = o_val; + uint32_t final_odd = e_val ^ evenparity32(o_val & LF_POLY_ODD); + + if(bs_batch_add(&batch, final_odd, final_even)) { + int result = bs_verify_dispatch(&batch, n); + if(result == -1) return -1; + if(g_abort_attack) return -2; + bs_batch_init(&batch); + } + } + } + + if(batch.count > 0) { + int result = bs_verify_dispatch(&batch, n); + if(result == -1) return -1; + } + + return s; + } + + if(first_run) { + // All elements share the same top byte (constructed as raw | msb_val), + // so sorting is a no-op and the MSB-walk finds exactly one group. + // Skip directly to recursion with first_run=0. + return old_recover_bs( + odd, o_head, o_tail, oks, even, e_head, e_tail, eks, rem, s, n, in, 0); + } + + for(i = 0; (i < 4) && (rem-- != 0); i++) { + oks >>= 1; + eks >>= 1; + in >>= 2; + o_tail = extend_table_odd(odd, o_head, o_tail, oks & 1); + if(o_head > o_tail) return s; + e_tail = extend_table_even(even, e_head, e_tail, eks & 1, in & 3); + if(e_head > e_tail) return s; + } + + // Sort by MSB for grouped cross-product intersection + radix_sort_32(odd, o_head, o_tail, g_radix_temp); + radix_sort_32(even, e_head, e_tail, g_radix_temp); + + while(o_tail >= o_head && e_tail >= e_head) { + if(((odd[o_tail] ^ even[e_tail]) >> 24) == 0) { + o_tail = binsearch(odd, o_head, o = o_tail); + e_tail = binsearch(even, e_head, e = e_tail); + s = old_recover_bs(odd, o_tail--, o, oks, even, e_tail--, e, eks, rem, s, n, in, 0); + if(s < 0) { + break; + } + } else if((odd[o_tail] ^ 0x80000000) > (even[e_tail] ^ 0x80000000)) { + o_tail = binsearch(odd, o_head, o_tail) - 1; + } else { + e_tail = binsearch(even, e_head, e_tail) - 1; + } + } + return s; +} diff --git a/applications/system/mfkey/mfkey_recovery.h b/applications/system/mfkey/mfkey_recovery.h new file mode 100644 index 000000000..257eac476 --- /dev/null +++ b/applications/system/mfkey/mfkey_recovery.h @@ -0,0 +1,31 @@ +#ifndef MFKEY_RECOVERY_H +#define MFKEY_RECOVERY_H + +#include "mfkey.h" +#include "crypto1.h" + +// Scratch buffer for radix sort. Also reused as ping-pong buffer by +// state_loop_r4 during expansion (phases don't overlap). +extern unsigned int g_radix_temp[1280]; + +// Radix sort for 24-bit packed states (O(n) non-recursive sorting) +void radix_sort_32(unsigned int* arr, int low, int high, unsigned int* temp); + +// Bitsliced recovery: 32-way parallel verification +// Returns: >0 = states checked, -1 = key found, -2 = aborted +int old_recover_bs( + unsigned int odd[], + int o_head, + int o_tail, + int oks, + unsigned int even[], + int e_head, + int e_tail, + int eks, + int rem, + int s, + MfClassicNonce* n, + unsigned int in, + int first_run); + +#endif // MFKEY_RECOVERY_H diff --git a/applications/system/mfkey/mfkey_state_expansion.c b/applications/system/mfkey/mfkey_state_expansion.c new file mode 100644 index 000000000..a3071886e --- /dev/null +++ b/applications/system/mfkey/mfkey_state_expansion.c @@ -0,0 +1,152 @@ +// MFKey State Expansion - LFSR tree search through rounds 1-12 +// Extracted from mfkey_attack.c for better code organization + +#pragma GCC optimize("O3") + +#include "mfkey_state_expansion.h" +#include "mfkey_recovery.h" +#include "crypto1.h" +#include + +#define OPT_BARRIER(x) __asm__ volatile("" : "+r"(x)) + +// Pre-fold xks_bit into the filter constant so BIT(adj, idx) == BIT(0xEC57E80A, idx) ^ xks_bit +#define ADJ_FILTER(xks, round) (0xEC57E80Au ^ (-(uint32_t)BIT(xks, round))) + +// In-place LFSR expansion for rounds 4-12, called after batch prelude completes rounds 0-3. +// fork_delta deferred past round 4 early-exit (~95% of calls exit at round 4). +__attribute__((hot)) int state_loop_r4( + unsigned int* states_buffer, + int count, // Number of active states in buffer + int xks, + int m1, + int m2, + unsigned int in, + int and_val) { + OPT_BARRIER(xks); // Prevent hoisting of BIT(xks, round) extractions + if(count == 0) return -1; + + // Round 4 (in-place, no parity update) + int states_tail = count - 1; + { + uint32_t adj = ADJ_FILTER(xks, 4); + for(int s = 0; s <= states_tail; ++s) { + unsigned int raw = states_buffer[s]; + OPT_BARRIER(raw); + unsigned int v = raw << 1; + uint32_t fp = filter_pair_xor(v, adj); + int f0_x = FILTER_F0(fp); + int f1_x = FILTER_F1(fp); + + if(__builtin_expect((f0_x ^ f1_x) != 0, 0)) { + // Single child survives + states_buffer[s] = v | f0_x; + } else if(__builtin_expect(f0_x == 0, 1)) { + // Both children survive — fork + states_buffer[++states_tail] = states_buffer[s + 1]; + states_buffer[s] = v; + s++; + states_buffer[s] = v | 1; + } else { + // Neither survives — eliminate + states_buffer[s--] = states_buffer[states_tail--]; + } + } + if(__builtin_expect(states_tail < 0, 1)) return -1; + } + + // Fork delta deferred past round 4 early-exit (~95% of calls don't reach here) + uint32_t fork_delta = ((m1 & 1) << 25) | ((m2 & 1) << 24) | 1; + + // Round 5 (unrolled, in-place) + { + uint32_t adj = ADJ_FILTER(xks, 5); + unsigned int r5_in = ((in >> 2) & and_val) << 24; + for(int s = 0; s <= states_tail; ++s) { + unsigned int raw = states_buffer[s]; + OPT_BARRIER(raw); + unsigned int v = raw << 1; + uint32_t fp = filter_pair_xor(v, adj); + int f0_x = FILTER_F0(fp); + int f1_x = FILTER_F1(fp); + + if(__builtin_expect((f0_x ^ f1_x) != 0, 0)) { + v |= f0_x; + OPT_BARRIER(v); + states_buffer[s] = update_contribution_reg(v, m1, m2) ^ r5_in; + } else if(__builtin_expect(f0_x == 0, 1)) { + states_buffer[++states_tail] = states_buffer[s + 1]; + OPT_BARRIER(v); + uint32_t p0 = update_contribution_reg(v, m1, m2) ^ r5_in; + states_buffer[s] = p0; + s++; + states_buffer[s] = p0 ^ fork_delta; + } else { + states_buffer[s--] = states_buffer[states_tail--]; + } + } + if(__builtin_expect(states_tail < 0, 0)) return -1; + } + + // Round 6 (unrolled, in-place) + { + uint32_t adj = ADJ_FILTER(xks, 6); + unsigned int r6_in = ((in >> 4) & and_val) << 24; + for(int s = 0; s <= states_tail; ++s) { + unsigned int raw = states_buffer[s]; + OPT_BARRIER(raw); + unsigned int v = raw << 1; + uint32_t fp = filter_pair_xor(v, adj); + int f0_x = FILTER_F0(fp); + int f1_x = FILTER_F1(fp); + + if(__builtin_expect((f0_x ^ f1_x) != 0, 0)) { + v |= f0_x; + OPT_BARRIER(v); + states_buffer[s] = update_contribution_reg(v, m1, m2) ^ r6_in; + } else if(__builtin_expect(f0_x == 0, 1)) { + states_buffer[++states_tail] = states_buffer[s + 1]; + OPT_BARRIER(v); + uint32_t p0 = update_contribution_reg(v, m1, m2) ^ r6_in; + states_buffer[s] = p0; + s++; + states_buffer[s] = p0 ^ fork_delta; + } else { + states_buffer[s--] = states_buffer[states_tail--]; + } + } + if(__builtin_expect(states_tail < 0, 0)) return -1; + } + + // Rounds 7-12 (loop, in-place) + for(int round = 7; round <= 12; ++round) { + uint32_t adj = ADJ_FILTER(xks, round); + unsigned int round_in = ((in >> (2 * (round - 4))) & and_val) << 24; + for(int s = 0; s <= states_tail; ++s) { + unsigned int raw = states_buffer[s]; + OPT_BARRIER(raw); + unsigned int v = raw << 1; + uint32_t fp = filter_pair_xor(v, adj); + int f0_x = FILTER_F0(fp); + int f1_x = FILTER_F1(fp); + + if(__builtin_expect((f0_x ^ f1_x) != 0, 0)) { + v |= f0_x; + OPT_BARRIER(v); + states_buffer[s] = update_contribution_reg(v, m1, m2) ^ round_in; + } else if(__builtin_expect(f0_x == 0, 1)) { + states_buffer[++states_tail] = states_buffer[s + 1]; + OPT_BARRIER(v); + uint32_t p0 = update_contribution_reg(v, m1, m2) ^ round_in; + states_buffer[s] = p0; + s++; + states_buffer[s] = p0 ^ fork_delta; + } else { + states_buffer[s--] = states_buffer[states_tail--]; + } + } + if(__builtin_expect(states_tail < 0, 0)) return -1; + } + + return states_tail; +} diff --git a/applications/system/mfkey/mfkey_state_expansion.h b/applications/system/mfkey/mfkey_state_expansion.h new file mode 100644 index 000000000..77dd82da4 --- /dev/null +++ b/applications/system/mfkey/mfkey_state_expansion.h @@ -0,0 +1,17 @@ +#ifndef MFKEY_STATE_EXPANSION_H +#define MFKEY_STATE_EXPANSION_H + +#include + +// LFSR state expansion for rounds 4-12 (rounds 0-3 handled by batch prelude). +// Returns final state count (states_tail), or -1 if all eliminated. +int state_loop_r4( + unsigned int* states_buffer, + int count, + int xks, + int m1, + int m2, + unsigned int in, + int and_val); + +#endif // MFKEY_STATE_EXPANSION_H