This commit is contained in:
dela
2026-02-26 06:14:36 +08:00
commit 06b67f60d6
20 changed files with 1440 additions and 0 deletions

74
src/gift256/encrypt.rs Normal file
View File

@@ -0,0 +1,74 @@
//! GIFT-256 fixsliced encryption (13 rounds)
//! Corresponds to gift256_encrypt_fixsliced @ 0x80048469
//!
//! Round structure: 4 cycles x {SBox+P1+KeyXOR, SBox+DA+KeyXOR, SBox+P2+KeyXOR, SBox+DB+KeyXOR}
//! Last cycle only executes the first type (SBox+P1+KeyXOR)
use super::sbox::sbox_bitsliced;
use super::linear::{linear_p1, linear_p2, diffusion_a, diffusion_b};
use super::interleave::{pack_input, nibble_deinterleave, unpack_output};
/// GIFT-256 encryption.
/// input: 256-bit plaintext (8 u32, little-endian)
/// rk: 120 u32 round keys (from key_schedule)
/// output: 256-bit ciphertext (8 u32)
pub fn encrypt(input: &[u32; 8], rk: &[u32; 120]) -> [u32; 8] {
// === Input packing (bit-interleave + initial round key XOR) ===
let initial_rk: [u32; 8] = rk[0..8].try_into().unwrap();
let mut s = pack_input(input, &initial_rk);
// === 13 rounds main loop (4 cycles) ===
// Round key indexing:
// Initial: rk[0..7]
// Type A: rk[base+8 .. base+15]
// Type DA: rk[base+16 .. base+23]
// Type P2: rk[base+24 .. base+31]
// Type DB: rk[base+32 .. base+39]
// where base = cycle * 32
for cycle in 0..4 {
let base = cycle * 32;
// --- Round Type A: SBox + P1 + KeyXOR ---
sbox_bitsliced(&mut s);
linear_p1(&mut s);
let rk_a: [u32; 8] = rk[base + 8..base + 16].try_into().unwrap();
for j in 0..8 {
s[j] ^= rk_a[j];
}
if cycle == 3 {
break; // Last cycle only has Type A
}
// --- Round Type DA: SBox + nibble-ROT diffusion + KeyXOR ---
sbox_bitsliced(&mut s);
let rk_da: [u32; 8] = rk[base + 16..base + 24].try_into().unwrap();
diffusion_a(&mut s, &rk_da);
// --- Round Type P2: SBox + P2 + KeyXOR ---
sbox_bitsliced(&mut s);
linear_p2(&mut s);
let rk_p2: [u32; 8] = rk[base + 24..base + 32].try_into().unwrap();
for j in 0..8 {
s[j] ^= rk_p2[j];
}
// --- Round Type DB: SBox + byte-ROT8 diffusion + KeyXOR ---
sbox_bitsliced(&mut s);
let rk_db: [u32; 8] = rk[base + 32..base + 40].try_into().unwrap();
diffusion_b(&mut s, &rk_db);
}
// === Output deinterleave ===
for j in 0..8 {
s[j] = nibble_deinterleave(s[j]);
}
// Final SBox
sbox_bitsliced(&mut s);
// Final round key XOR + deinterleave (rk[112..119])
let final_rk: [u32; 8] = rk[112..120].try_into().unwrap();
unpack_output(&s, &final_rk)
}

190
src/gift256/interleave.rs Normal file
View File

@@ -0,0 +1,190 @@
//! GIFT-256 fixsliced bit interleave/deinterleave
//! Used to convert between standard and fixsliced representations
//! Corresponds to gift256_encrypt_fixsliced State 0 (input) and State 3 (output)
/// Pack 8 u32 (standard) into 8 u32 (fixsliced) with initial round key XOR.
/// Corresponds to gift256_encrypt_fixsliced State 0.
///
/// Input read order (from decompiled offsets):
/// input[7]=@0x1c, input[3]=@0x0c, input[6]=@0x18, input[2]=@0x08
/// input[5]=@0x14, input[1]=@0x04, input[4]=@0x10, input[0]=@0x00
pub fn pack_input(input: &[u32; 8], initial_rk: &[u32; 8]) -> [u32; 8] {
let (mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h) =
(input[7], input[3], input[6], input[2], input[5], input[1], input[4], input[0]);
// Phase 1: 0x55555555 interleave (odd/even bit separation)
let t0 = (a ^ (b >> 1)) & 0x55555555;
a ^= t0;
b ^= t0 << 1;
let t1 = (c ^ (d >> 1)) & 0x55555555;
c ^= t1;
d ^= t1 << 1;
let t2 = (e ^ (f >> 1)) & 0x55555555;
e ^= t2;
f ^= t2 << 1;
let t3 = (g ^ (h >> 1)) & 0x55555555;
g ^= t3;
h ^= t3 << 1;
// Phase 2: 0x33333333 interleave (2-bit group separation)
let t4 = (a ^ (c >> 2)) & 0x33333333;
a ^= t4;
c ^= t4 << 2;
let t5 = (e ^ (g >> 2)) & 0x33333333;
e ^= t5;
g ^= t5 << 2;
let t6 = (b ^ (d >> 2)) & 0x33333333;
b ^= t6;
d ^= t6 << 2;
let t7 = (f ^ (h >> 2)) & 0x33333333;
f ^= t7;
h ^= t7 << 2;
// Phase 3: 0x0F0F0F0F interleave (nibble separation) + initial round key XOR
let t8 = (a ^ (e >> 4)) & 0x0F0F0F0F;
let out7 = initial_rk[7] ^ (a ^ t8);
let out3 = initial_rk[3] ^ ((t8 << 4) ^ e);
let t9 = (c ^ (g >> 4)) & 0x0F0F0F0F;
let out5 = initial_rk[5] ^ (c ^ t9);
let out1 = initial_rk[1] ^ ((t9 << 4) ^ g);
let t10 = (b ^ (f >> 4)) & 0x0F0F0F0F;
let out6 = initial_rk[6] ^ (b ^ t10);
let out2 = initial_rk[2] ^ ((t10 << 4) ^ f);
let t11 = (d ^ (h >> 4)) & 0x0F0F0F0F;
let out4 = initial_rk[4] ^ (d ^ t11);
let out0 = initial_rk[0] ^ ((t11 << 4) ^ h);
[out0, out1, out2, out3, out4, out5, out6, out7]
}
/// Nibble deinterleave.
/// Decompiled: ((uVar4 >> 4 ^ uVar4) & 0xf000f00) * 0x11 ^ uVar4
pub fn nibble_deinterleave(x: u32) -> u32 {
(((x >> 4) ^ x) & 0x0F000F00).wrapping_mul(0x11) ^ x
}
/// Key schedule deinterleave type A (offsets 0x20..0x3C, 0x60..0x7C, 0x1A0..0x1BC)
/// x = ((x ^ x>>4) & 0x030F0C00) * 0x11 ^ x;
/// x = ((x>>2 ^ x) & 0x33003300) * 5 ^ x;
pub fn key_deinterleave_a(x: u32) -> u32 {
let x = (((x ^ (x >> 4)) & 0x030F0C00).wrapping_mul(0x11)) ^ x;
((((x >> 2) ^ x) & 0x33003300).wrapping_mul(5)) ^ x
}
/// Key schedule deinterleave type B (offsets 0x40..0x5C)
/// x = ((x>>4 ^ x) & 0x0F000F00) * 0x11 ^ x;
pub fn key_deinterleave_b(x: u32) -> u32 {
((((x >> 4) ^ x) & 0x0F000F00).wrapping_mul(0x11)) ^ x
}
/// Key schedule deinterleave type C (offsets 0x60..0x7C second group)
/// x = ((x ^ x>>4) & 0x0C0F0300) * 0x11 ^ x;
/// x = ((x>>2 ^ x) & 0x33003300) * 5 ^ x;
pub fn key_deinterleave_c(x: u32) -> u32 {
let x = (((x ^ (x >> 4)) & 0x0C0F0300).wrapping_mul(0x11)) ^ x;
((((x >> 2) ^ x) & 0x33003300).wrapping_mul(5)) ^ x
}
/// Interleave a half-key (4 u32 input -> 8 u32 output).
/// Same 3-stage butterfly as pack_input but on 4 words.
/// Used in key_schedule to interleave the two key halves.
pub fn interleave_key_half(input: &[u32], output: &mut [u32]) {
let (mut a, mut b) = (input[3], input[1]);
let (mut c, mut d) = (input[2], input[0]);
// Phase 1: 0x55555555
let t0 = (a ^ (b >> 1)) & 0x55555555;
a ^= t0;
b ^= t0 << 1;
let t1 = (c ^ (d >> 1)) & 0x55555555;
c ^= t1;
d ^= t1 << 1;
// Phase 2: 0x33333333
let t2 = (a ^ (c >> 2)) & 0x33333333;
a ^= t2;
c ^= t2 << 2;
let t3 = (b ^ (d >> 2)) & 0x33333333;
b ^= t3;
d ^= t3 << 2;
// Phase 3: 0x0F0F0F0F
let t4 = (a ^ (b >> 4)) & 0x0F0F0F0F;
output[7] = a ^ t4;
output[3] = (t4 << 4) ^ b;
let t5 = (c ^ (d >> 4)) & 0x0F0F0F0F;
output[5] = c ^ t5;
output[1] = (t5 << 4) ^ d;
// For the remaining outputs, use the already-interleaved values
let t6 = (a ^ (c >> 4)) & 0x0F0F0F0F;
output[6] = a ^ t6;
output[2] = (t6 << 4) ^ c;
let t7 = (b ^ (d >> 4)) & 0x0F0F0F0F;
output[4] = b ^ t7;
output[0] = (t7 << 4) ^ d;
}
/// Unpack output: reverse bit-interleave + final round key XOR.
/// Reverse of pack_input: apply masks in reverse order (0x0F -> 0x33 -> 0x55).
pub fn unpack_output(s: &[u32; 8], final_rk: &[u32; 8]) -> [u32; 8] {
// XOR with final round keys first
let mut a = s[7] ^ final_rk[7];
let mut b = s[6] ^ final_rk[6];
let mut c = s[5] ^ final_rk[5];
let mut d = s[3] ^ final_rk[3];
let mut e = s[4] ^ final_rk[4];
let mut f = s[2] ^ final_rk[2];
let mut g = s[1] ^ final_rk[1];
let mut h = s[0] ^ final_rk[0];
// Reverse Phase 3: 0x0F0F0F0F
let t8 = (a ^ (e >> 4)) & 0x0F0F0F0F;
a ^= t8;
e ^= t8 << 4;
let t9 = (c ^ (g >> 4)) & 0x0F0F0F0F;
c ^= t9;
g ^= t9 << 4;
let t10 = (b ^ (f >> 4)) & 0x0F0F0F0F;
b ^= t10;
f ^= t10 << 4;
let t11 = (d ^ (h >> 4)) & 0x0F0F0F0F;
d ^= t11;
h ^= t11 << 4;
// Reverse Phase 2: 0x33333333
let t4 = (a ^ (c >> 2)) & 0x33333333;
a ^= t4;
c ^= t4 << 2;
let t5 = (e ^ (g >> 2)) & 0x33333333;
e ^= t5;
g ^= t5 << 2;
let t6 = (b ^ (d >> 2)) & 0x33333333;
b ^= t6;
d ^= t6 << 2;
let t7 = (f ^ (h >> 2)) & 0x33333333;
f ^= t7;
h ^= t7 << 2;
// Reverse Phase 1: 0x55555555
let t0 = (a ^ (b >> 1)) & 0x55555555;
a ^= t0;
b ^= t0 << 1;
let t1 = (c ^ (d >> 1)) & 0x55555555;
c ^= t1;
d ^= t1 << 1;
let t2 = (e ^ (f >> 1)) & 0x55555555;
e ^= t2;
f ^= t2 << 1;
let t3 = (g ^ (h >> 1)) & 0x55555555;
g ^= t3;
h ^= t3 << 1;
// Output in original order (reverse of input mapping)
[h, f, d, b, g, e, c, a]
}

162
src/gift256/key_schedule.rs Normal file
View File

@@ -0,0 +1,162 @@
//! GIFT-256 fixsliced key schedule
//! Corresponds to gift256_key_schedule @ 0x80057559
//! Expands a 256-bit key into 120 u32 (480 bytes) of round keys
use super::sbox::sbox_bitsliced;
use super::interleave::{key_deinterleave_a, key_deinterleave_b, key_deinterleave_c, interleave_key_half};
/// NOT compensation table: offsets (u32 indices) that must be inverted at the end.
/// Corresponds to the mass of ks[offset] ^= 0xFFFFFFFF at the end of the decompilation.
const NOT_OFFSETS: [usize; 56] = [
0x08, 0x09, 0x0D, 0x0E,
0x10, 0x11, 0x15, 0x16,
0x18, 0x19, 0x1D, 0x1E,
0x20, 0x21, 0x25, 0x26,
0x28, 0x29, 0x2D, 0x2E,
0x30, 0x31, 0x35, 0x36,
0x38, 0x39, 0x3D, 0x3E,
0x40, 0x41, 0x45, 0x46,
0x48, 0x49, 0x4D, 0x4E,
0x50, 0x51, 0x55, 0x56,
0x58, 0x59, 0x5D, 0x5E,
0x60, 0x61, 0x65, 0x66,
0x68, 0x69, 0x6D, 0x6E,
0x70, 0x71, 0x75, 0x76,
];
/// GF extension function.
/// Decompiled:
/// uVar4 = (src >> param3 | src << (32-param3)) & 0x03030303 ^ dst;
/// result = uVar4 ^ (uVar4 & 0x03030303) << 6
/// ^ (uVar4 & 0x0F0F0F0F) << 4
/// ^ (uVar4 & 0x3F3F3F3F) << 2;
fn gf_expand(src: u32, dst: u32, rot: u32) -> u32 {
let x = (src.rotate_right(rot) & 0x03030303) ^ dst;
x ^ ((x & 0x03030303) << 6) ^ ((x & 0x0F0F0F0F) << 4) ^ ((x & 0x3F3F3F3F) << 2)
}
/// Key rotate: copy ks[base+0..7] forward to ks[base+8..15] with rotation.
/// Corresponds to gift256_key_rotate @ 0x80060a8f
///
/// After tracing the CFF state machine:
/// ks[base+15] = ks[base+7]
/// ks[base+14] = ks[base+6]
/// ...
/// ks[base+9] = ks[base+1]
/// ks[base+8] = ks[base+0]
fn key_rotate(ks: &mut [u32], base: usize) {
for i in (0..8).rev() {
ks[base + 8 + i] = ks[base + i];
}
}
/// LFSR expansion: perform GF expansion on 8 key words.
/// Corresponds to gift256_key_lfsr_expand @ 0x8005a060
/// rot = 14 or 6 (rotation amount)
fn key_lfsr_expand(ks: &mut [u32], base: usize, rot: u32) {
for i in 0..8 {
let src_idx = base + i;
let dst_idx = base + i - 16; // base is always >= 16
ks[src_idx] = gf_expand(ks[src_idx], ks[dst_idx], rot);
}
}
/// Full key schedule: expand 256-bit key into 120 u32 round keys.
pub fn key_schedule(key: &[u8; 32]) -> [u32; 120] {
let mut ks = [0u32; 120];
// === Step 1: Bit-interleave packing (State 3) ===
// Read key as 8 u32 (little-endian)
let mut kw = [0u32; 8];
for i in 0..8 {
kw[i] = u32::from_le_bytes([key[i * 4], key[i * 4 + 1], key[i * 4 + 2], key[i * 4 + 3]]);
}
// Interleave: two halves of 4 words each
interleave_key_half(&kw[0..4], &mut ks[0..8]);
interleave_key_half(&kw[4..8], &mut ks[8..16]);
// === Step 2: 12-step round key expansion (State 4+5, 6 iterations) ===
let mut round = 8usize;
let mut rk_const_offset = 0x10usize; // u32 index
for _step in 0..6 {
// --- Sub-step A (State 4) ---
key_rotate(&mut ks, round);
round += 8;
// S-Box on the newly written 8 words
let mut sbox_buf: [u32; 8] = ks[round - 8..round].try_into().unwrap();
sbox_bitsliced(&mut sbox_buf);
ks[round - 8..round].copy_from_slice(&sbox_buf);
// NOT compensation (4 specific offsets within this block)
ks[round - 8] ^= 0xFFFFFFFF;
ks[round - 7] ^= 0xFFFFFFFF;
ks[round - 3] ^= 0xFFFFFFFF;
ks[round - 2] ^= 0xFFFFFFFF;
// Round constant injection
ks[rk_const_offset] ^= 0xC000;
// LFSR expand with rot=14
key_lfsr_expand(&mut ks, round, 14);
// --- Sub-step B (State 5) ---
key_rotate(&mut ks, round);
round += 8;
let mut sbox_buf: [u32; 8] = ks[round - 8..round].try_into().unwrap();
sbox_bitsliced(&mut sbox_buf);
ks[round - 8..round].copy_from_slice(&sbox_buf);
ks[round - 8] ^= 0xFFFFFFFF;
ks[round - 7] ^= 0xFFFFFFFF;
ks[round - 3] ^= 0xFFFFFFFF;
ks[round - 2] ^= 0xFFFFFFFF;
// LFSR expand with rot=6
key_lfsr_expand(&mut ks, round, 6);
rk_const_offset += 17; // iVar24 += 0x44 bytes = 17 u32 indices
}
// === Step 3: Post-processing (State 2 + State 1) ===
// Deinterleave with 3 different mask patterns, each block covers 32 u32
for block in 0..3 {
let base = block * 32;
// Type A: offsets +8..+15
for j in 0..8 {
ks[base + 8 + j] = key_deinterleave_a(ks[base + 8 + j]);
}
// Type B: offset +16
ks[base + 16] = key_deinterleave_b(ks[base + 16]);
// Type B: offsets +17..+23
for j in 1..8 {
ks[base + 16 + j] = key_deinterleave_b(ks[base + 16 + j]);
}
// Type C: offsets +24..+31
for j in 0..8 {
ks[base + 24 + j] = key_deinterleave_c(ks[base + 24 + j]);
}
}
// Final round key deinterleave (ks[104..111] = offsets 0x1A0..0x1BC)
for j in 0..8 {
ks[104 + j] = key_deinterleave_a(ks[104 + j]);
}
// Initial NOT
ks[8] ^= 0xFFFFFFFF;
// Final NOT compensation
for &off in &NOT_OFFSETS {
ks[off] ^= 0xFFFFFFFF;
}
ks
}

145
src/gift256/linear.rs Normal file
View File

@@ -0,0 +1,145 @@
//! GIFT-256 linear diffusion layers
//! P1 and P2 are standalone functions, DA and DB are inlined in encrypt
use crate::util::nibble_half_swap;
/// P1: byte-ROT2 permutation (even rounds)
/// Corresponds to gift256_linear_P1_byteROT2 @ 0x800172b2
///
/// Core op: rot = ROR(x, 2) & 0xC0C0C0C0 | ROR(x, 10) & 0x3F3F3F3F
/// Processing order: s[6], s[7], s[5], s[4], s[1], s[2], s[0], s[3]
/// Extra: s[7]'s tmp value (t7) is XORed into s[0], s[4], s[3], s[1]
pub fn linear_p1(s: &mut [u32; 8]) {
let byte_rot2 = |x: u32| -> u32 {
(x.rotate_left(22) & 0x3F3F3F3F) | (x.rotate_left(30) & 0xC0C0C0C0)
};
let r6 = byte_rot2(s[6]);
let r7 = byte_rot2(s[7]);
let t6 = s[6] ^ r6;
let t7 = s[7] ^ r7;
s[7] = r7 ^ t6 ^ nibble_half_swap(t7);
let r5 = byte_rot2(s[5]);
let t5 = s[5] ^ r5;
s[6] = r6 ^ t5 ^ nibble_half_swap(t6);
let r4 = byte_rot2(s[4]);
let t4 = s[4] ^ r4;
s[5] = r5 ^ t4 ^ nibble_half_swap(t5);
let r1 = byte_rot2(s[1]);
let t1 = r1 ^ s[1];
let r2 = byte_rot2(s[2]);
let t2 = s[2] ^ r2;
s[2] = r2 ^ t1 ^ nibble_half_swap(t2);
let r0 = byte_rot2(s[0]);
let t0 = r0 ^ s[0];
s[0] = nibble_half_swap(t0) ^ r0 ^ t7; // NOTE: XOR with t7
let r3 = byte_rot2(s[3]);
let t3 = s[3] ^ r3;
s[4] = r4 ^ t3 ^ nibble_half_swap(t4) ^ t7;
s[3] = t2 ^ nibble_half_swap(t3) ^ r3 ^ t7;
s[1] = t0 ^ nibble_half_swap(t1) ^ r1 ^ t7;
}
/// P2: byte-ROT6 permutation (odd rounds)
/// Corresponds to gift256_linear_P2_byteROT6 @ 0x80006e1f
/// Identical structure to P1, only rotation amount changes:
/// rot = ROR(x, 6) & 0xFCFCFCFC | ROR(x, 14) & 0x03030303
pub fn linear_p2(s: &mut [u32; 8]) {
let byte_rot6 = |x: u32| -> u32 {
(x.rotate_left(18) & 0x03030303) | (x.rotate_left(26) & 0xFCFCFCFC)
};
let r6 = byte_rot6(s[6]);
let r7 = byte_rot6(s[7]);
let t6 = s[6] ^ r6;
let t7 = s[7] ^ r7;
s[7] = r7 ^ t6 ^ nibble_half_swap(t7);
let r5 = byte_rot6(s[5]);
let t5 = s[5] ^ r5;
s[6] = r6 ^ t5 ^ nibble_half_swap(t6);
let r4 = byte_rot6(s[4]);
let t4 = s[4] ^ r4;
s[5] = r5 ^ t4 ^ nibble_half_swap(t5);
let r1 = byte_rot6(s[1]);
let t1 = r1 ^ s[1];
let r2 = byte_rot6(s[2]);
let t2 = s[2] ^ r2;
s[2] = r2 ^ t1 ^ nibble_half_swap(t2);
let r0 = byte_rot6(s[0]);
let t0 = r0 ^ s[0];
s[0] = nibble_half_swap(t0) ^ r0 ^ t7;
let r3 = byte_rot6(s[3]);
let t3 = s[3] ^ r3;
s[4] = r4 ^ t3 ^ nibble_half_swap(t4) ^ t7;
s[3] = t2 ^ nibble_half_swap(t3) ^ r3 ^ t7;
s[1] = t0 ^ nibble_half_swap(t1) ^ r1 ^ t7;
}
/// DA: nibble-ROT4/12/20/28 diffusion (inlined in encrypt)
/// Corresponds to gift256_encrypt_fixsliced State 2 first part
///
/// Core op:
/// rotated = (x << 0x14 | x >> 0xc) & 0x0F0F0F0F | (x << 0x1c | x >> 4) & 0xF0F0F0F0
/// tmp = x ^ rotated
/// new = rotated ^ key ^ SWAP16(tmp) ^ cross_xor_pattern
pub fn diffusion_a(s: &mut [u32; 8], rk: &[u32; 8]) {
let nibble_rot = |x: u32| -> u32 {
(x.rotate_left(20) & 0x0F0F0F0F) | (x.rotate_left(28) & 0xF0F0F0F0)
};
let swap16 = |x: u32| -> u32 { x.rotate_left(16) };
let mut r = [0u32; 8];
let mut t = [0u32; 8];
for i in 0..8 {
r[i] = nibble_rot(s[i]);
t[i] = s[i] ^ r[i];
}
// Cross-XOR pattern (derived from decompilation)
s[0] = t[7] ^ r[0] ^ rk[0] ^ swap16(t[0]);
s[2] = r[2] ^ rk[2] ^ swap16(t[2]) ^ t[1];
s[5] = r[5] ^ rk[5] ^ swap16(t[5]) ^ t[4];
s[1] = rk[1] ^ swap16(t[1]) ^ t[0] ^ r[1] ^ t[7];
s[3] = r[3] ^ rk[3] ^ swap16(t[3]) ^ t[2] ^ t[7];
s[4] = rk[4] ^ swap16(t[4]) ^ t[3] ^ r[4] ^ t[7];
s[6] = r[6] ^ rk[6] ^ swap16(t[6]) ^ t[5];
s[7] = rk[7] ^ swap16(t[7]) ^ t[6] ^ r[7];
}
/// DB: byte-ROT8 diffusion (inlined in encrypt)
/// Corresponds to gift256_encrypt_fixsliced State 2 last part
///
/// Core op:
/// rotated = x << 0x18 | x >> 8 (= ROR(x, 8))
/// tmp = rotated ^ x
/// new = rotated ^ key ^ SWAP16(tmp) ^ cross_xor_pattern
pub fn diffusion_b(s: &mut [u32; 8], rk: &[u32; 8]) {
let swap16 = |x: u32| -> u32 { x.rotate_left(16) };
let mut r = [0u32; 8];
let mut t = [0u32; 8];
for i in 0..8 {
r[i] = s[i].rotate_right(8);
t[i] = r[i] ^ s[i];
}
// Cross-XOR pattern (same structure as DA but with byte-ROT8)
s[0] = r[0] ^ rk[0] ^ swap16(t[0]) ^ t[7];
s[2] = r[2] ^ rk[2] ^ swap16(t[2]) ^ t[1];
s[1] = rk[1] ^ swap16(t[1]) ^ t[0] ^ r[1] ^ t[7];
s[3] = r[3] ^ rk[3] ^ swap16(t[3]) ^ t[2] ^ t[7];
s[4] = rk[4] ^ swap16(t[4]) ^ t[3] ^ r[4] ^ t[7];
s[5] = r[5] ^ rk[5] ^ swap16(t[5]) ^ t[4];
s[6] = r[6] ^ rk[6] ^ swap16(t[6]) ^ t[5];
s[7] = rk[7] ^ swap16(t[7]) ^ t[6] ^ r[7];
}

5
src/gift256/mod.rs Normal file
View File

@@ -0,0 +1,5 @@
pub mod sbox;
pub mod encrypt;
pub mod key_schedule;
pub mod linear;
pub mod interleave;

90
src/gift256/sbox.rs Normal file
View File

@@ -0,0 +1,90 @@
//! GIFT-256 bitsliced S-Box
//! Corresponds to gift256_sbox_bitsliced @ 0x8000394c
//! Pure AND/XOR Boolean network, 32 S-Box instances in parallel
//!
//! Directly transcribed from Ghidra decompilation.
//! WARNING: Variables are reassigned multiple times. Do NOT optimize or merge.
/// Bitsliced S-Box: 8 u32 words processed in parallel.
/// Each bit position across s[0]..s[7] forms one 8-bit S-Box input/output.
///
/// Register mapping (encrypted_mem_read_u32 offsets):
/// s[0]=@0x00, s[1]=@0x04, s[2]=@0x08, s[3]=@0x0c
/// s[4]=@0x10, s[5]=@0x14, s[6]=@0x18, s[7]=@0x1c
pub fn sbox_bitsliced(s: &mut [u32; 8]) {
// Direct mapping from decompilation variable names:
// uVar1=@0x1c=s[7], uVar2=@0x04=s[1], uVar3=@0x10=s[4], uVar4=@0x08=s[2]
// uVar5=@0x0c=s[3], uVar6=@0x18=s[6], uVar7=@0x14=s[5], uVar(last)=@0x00=s[0]
let (a, b, c, d, e, f, g, h) = (s[7], s[1], s[2], s[3], s[4], s[5], s[6], s[0]);
let t1 = a ^ b; // uVar8 = uVar1 ^ uVar2
let t2 = e ^ c; // uVar14 = uVar3 ^ uVar4
let t3 = t1 ^ t2; // uVar9
let t4 = d ^ t3 ^ g; // uVar10
let t5 = e ^ a; // uVar15 = uVar3 ^ uVar1
let t6 = t4 ^ t5; // uVar11
let fg = f ^ g; // uVar7 = s[5] ^ s[6]
let t7 = t6 ^ fg; // uVar12
let ag = h ^ fg; // uVar25 = s[0] ^ (f^g)
let bx = b ^ ag; // uVar2 updated
let cd_mix = c ^ d ^ t3; // uVar26
let fg_cd = fg ^ cd_mix; // uVar7 subsequent
// Nonlinear core
let n1 = (t6 & t5) ^ (t2 & (fg_cd ^ t6)); // uVar16
let n2 = (t7 & t1) ^ t7 ^ (bx & ag) ^ t1 ^ n1; // uVar5
let ac = a ^ c; // uVar4 updated
let n1_ext = n1 ^ (cd_mix & t3) ^ t4 ^ ((h ^ cd_mix) & (ac ^ bx)); // uVar16 updated
let p = n2 & n1_ext; // uVar10
let q = (ac & fg_cd) ^ (t6 & t5); // uVar22
let r = (h & (e ^ ag)) ^ ac ^ fg_cd ^ (cd_mix & t3) ^ q; // uVar17
let qx = q ^ t7 ^ ((a ^ ag) & (h ^ t6)) ^ (t7 & t1) ^ a; // uVar22 updated
let m = (p ^ r) & (qx ^ n2); // uVar13
let n1x = r ^ n1_ext; // uVar16 = r ^ n1_ext
let rx = r ^ ((qx ^ p) & n1x); // uVar17 updated
let sx = (p ^ m) & qx; // uVar18
let n1x = (rx & (sx ^ qx ^ p)) ^ n1x; // uVar16 final
let sx = sx ^ n2; // uVar18 ^= uVar5
let qx = qx ^ m; // uVar22 ^= uVar13
let u = sx ^ qx; // uVar19
let v = n1x ^ u; // uVar5(final) = uVar16 ^ uVar19
let w = qx ^ rx; // uVar20
// Diffusion outputs
let o1 = ((v ^ w) & t2) ^ (w & t5); // uVar10
let o2 = o1 ^ (u & (ac ^ bx)); // uVar13
let o3 = o2 ^ (t3 & sx); // uVar9
let o4 = t7 & (rx ^ n1x); // uVar12
let o5 = o4 ^ (n1x & ag); // uVar23
let o6 = ((v ^ w) & (fg_cd ^ t6)) ^ o5; // uVar21
let o7 = o6 ^ (w & t6); // uVar14
// Output assignments (encrypted_mem_write_u32):
s[7] = o3 ^ o7; // @0x1c = uVar9 ^ uVar14
let o8 = (rx ^ n1x) & t1; // uVar8
let o9 = (qx & h) ^ (cd_mix & sx); // uVar24
let o6 = o8 ^ o9 ^ (fg_cd & v) ^ o6; // uVar21 updated
let o10 = o6 ^ (rx & (a ^ ag)); // uVar1
s[5] = (ac & v) ^ (w & t5) ^ o7 ^ o10; // @0x14
s[6] = o3 ^ o7; // @0x18 (same as s[7])
let o3x = (u & (h ^ cd_mix)) ^ (cd_mix & sx) ^ o5 ^ o3; // uVar9 updated
s[4] = o3x; // @0x10
s[2] = o2 ^ (qx & (e ^ ag)) ^ o10; // @0x08
let o4x = o9 ^ (rx & (h ^ t6)) ^ o4; // uVar12 updated
let o11 = o4x ^ o1 ^ (n1x & bx); // uVar1
s[1] = o11 ^ o6; // @0x04
s[0] = o11 ^ o8; // @0x00
s[3] = o4x ^ o3x; // @0x0c
}