update mfkey to 4.1 [ci skip]

This commit is contained in:
MX
2026-02-16 23:15:07 +03:00
parent 75d4263822
commit 937e937d32
16 changed files with 2390 additions and 529 deletions
+2 -3
View File
@@ -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(
-1
View File
@@ -1,5 +1,4 @@
#pragma GCC optimize("O3")
#pragma GCC optimize("-funroll-all-loops")
#include <inttypes.h>
#include "crypto1.h"
+98 -38
View File
@@ -2,14 +2,22 @@
#define CRYPTO1_H
#include <inttypes.h>
#include "mfkey.h"
#include <nfc/helpers/nfc_util.h>
#include <nfc/protocols/mf_classic/mf_classic.h>
#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;
}
+27 -486
View File
@@ -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 <gui/elements.h>
#include "mfkey_icons.h"
#include <inttypes.h>
#include <string.h>
#include <toolbox/keys_dict.h>
#include <bit_lib/bit_lib.h>
#include <toolbox/stream/buffered_file_stream.h>
#include <dolphin/dolphin.h>
#include <notification/notification_messages.h>
#include <storage/storage.h>
#include <nfc/protocols/mf_classic/mf_classic.h>
#include "mfkey.h"
#include "crypto1.h"
#include "mfkey_attack.h"
#include "plugin_interface.h"
#include <flipper_application/flipper_application.h>
#include <loader/firmware_api/firmware_api.h>
@@ -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 712
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;) {
+6 -1
View File
@@ -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 {
+287
View File
@@ -0,0 +1,287 @@
#pragma GCC optimize("O3")
#include <stdlib.h>
#include <string.h>
#include <furi.h>
#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;
}
+21
View File
@@ -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
@@ -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 <string.h>
// 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;
}
@@ -0,0 +1,12 @@
#ifndef MFKEY_BATCH_PRELUDE_H
#define MFKEY_BATCH_PRELUDE_H
#include <stdint.h>
#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
+530
View File
@@ -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 <string.h>
// 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);
}
@@ -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 <stdint.h>
#include <stdbool.h>
#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
+141
View File
@@ -0,0 +1,141 @@
#ifndef MFKEY_DEDUP_H
#define MFKEY_DEDUP_H
#include "mfkey.h"
#include "crypto1.h"
#include <stdint.h>
#include <stdbool.h>
#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
+288
View File
@@ -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 <string.h>
// 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;
}
@@ -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
@@ -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 <stdint.h>
#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;
}
@@ -0,0 +1,17 @@
#ifndef MFKEY_STATE_EXPANSION_H
#define MFKEY_STATE_EXPANSION_H
#include <stdint.h>
// 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