mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-07-19 00:48:09 -07:00
update mfkey to 4.1 [ci skip]
This commit is contained in:
@@ -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,5 +1,4 @@
|
||||
#pragma GCC optimize("O3")
|
||||
#pragma GCC optimize("-funroll-all-loops")
|
||||
|
||||
#include <inttypes.h>
|
||||
#include "crypto1.h"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 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;) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user