Commit 5e00a0c9b5

Frank Denis <github@pureftpd.org>
2025-11-29 19:25:07
std.crypto.aes: expose the inverse MixColumns operation
The inverse MixColumns operation is already used internally for AES decryption, but it wasn’t exposed in the public API because it didn’t seem necessary at the time. Since then, several new AES-based block ciphers and permutations (such as Vistrutah and Areion) have been developed, and they require this operation to be implementable in Zig. Since then, new interesting AES-based block ciphers and permutations (Vistrutah, Areion, etc). have been invented, and require that operation to be implementable in Zig.
1 parent 7d9ad99
Changed files (4)
lib/std/crypto/aes/aesni.zig
@@ -96,6 +96,17 @@ pub const Block = struct {
         return Block{ .repr = block1.repr | block2.repr };
     }
 
+    /// Apply the inverse MixColumns operation to a block.
+    pub fn invMixColumns(block: Block) Block {
+        return Block{
+            .repr = asm (
+                \\ vaesimc %[in], %[out]
+                : [out] "=x" (-> Repr),
+                : [in] "x" (block.repr),
+            ),
+        };
+    }
+
     /// Perform operations on multiple blocks in parallel.
     pub const parallel = struct {
         const cpu = std.Target.x86.cpu;
@@ -308,6 +319,17 @@ pub fn BlockVec(comptime blocks_count: comptime_int) type {
             }
             return out;
         }
+
+        /// Apply the inverse MixColumns operation to each block in the vector.
+        pub fn invMixColumns(block_vec: Self) Self {
+            var out_bytes: [blocks_count * 16]u8 = undefined;
+            const in_bytes = block_vec.toBytes();
+            inline for (0..blocks_count) |i| {
+                const block = Block.fromBytes(in_bytes[i * 16 ..][0..16]);
+                out_bytes[i * 16 ..][0..16].* = block.invMixColumns().toBytes();
+            }
+            return fromBytes(&out_bytes);
+        }
     };
 }
 
lib/std/crypto/aes/armcrypto.zig
@@ -99,6 +99,17 @@ pub const Block = struct {
         return Block{ .repr = block1.repr | block2.repr };
     }
 
+    /// Apply the inverse MixColumns operation to a block.
+    pub fn invMixColumns(block: Block) Block {
+        return Block{
+            .repr = asm (
+                \\ aesimc %[out].16b, %[in].16b
+                : [out] "=x" (-> Repr),
+                : [in] "x" (block.repr),
+            ),
+        };
+    }
+
     /// Perform operations on multiple blocks in parallel.
     pub const parallel = struct {
         /// The recommended number of AES encryption/decryption to perform in parallel for the chosen implementation.
@@ -275,6 +286,15 @@ pub fn BlockVec(comptime blocks_count: comptime_int) type {
             }
             return out;
         }
+
+        /// Apply the inverse MixColumns operation to each block in the vector.
+        pub fn invMixColumns(block_vec: Self) Self {
+            var out: Self = undefined;
+            inline for (0..native_words) |i| {
+                out.repr[i] = block_vec.repr[i].invMixColumns();
+            }
+            return out;
+        }
     };
 }
 
lib/std/crypto/aes/soft.zig
@@ -265,6 +265,26 @@ pub const Block = struct {
         return Block{ .repr = x };
     }
 
+    /// Apply the inverse MixColumns operation to a block.
+    pub fn invMixColumns(block: Block) Block {
+        var out: Repr = undefined;
+        inline for (0..4) |i| {
+            const col = block.repr[i];
+            const b0: u8 = @truncate(col);
+            const b1: u8 = @truncate(col >> 8);
+            const b2: u8 = @truncate(col >> 16);
+            const b3: u8 = @truncate(col >> 24);
+
+            const r0 = mul(0x0e, b0) ^ mul(0x0b, b1) ^ mul(0x0d, b2) ^ mul(0x09, b3);
+            const r1 = mul(0x09, b0) ^ mul(0x0e, b1) ^ mul(0x0b, b2) ^ mul(0x0d, b3);
+            const r2 = mul(0x0d, b0) ^ mul(0x09, b1) ^ mul(0x0e, b2) ^ mul(0x0b, b3);
+            const r3 = mul(0x0b, b0) ^ mul(0x0d, b1) ^ mul(0x09, b2) ^ mul(0x0e, b3);
+
+            out[i] = @as(u32, r0) | (@as(u32, r1) << 8) | (@as(u32, r2) << 16) | (@as(u32, r3) << 24);
+        }
+        return Block{ .repr = out };
+    }
+
     /// Perform operations on multiple blocks in parallel.
     pub const parallel = struct {
         /// The recommended number of AES encryption/decryption to perform in parallel for the chosen implementation.
@@ -441,6 +461,15 @@ pub fn BlockVec(comptime blocks_count: comptime_int) type {
             }
             return out;
         }
+
+        /// Apply the inverse MixColumns operation to each block in the vector.
+        pub fn invMixColumns(block_vec: Self) Self {
+            var out: Self = undefined;
+            for (0..native_words) |i| {
+                out.repr[i] = block_vec.repr[i].invMixColumns();
+            }
+            return out;
+        }
     };
 }
 
lib/std/crypto/aes.zig
@@ -108,6 +108,36 @@ test "expand 128-bit key" {
     }
 }
 
+test "invMixColumns" {
+    const key = [_]u8{ 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c };
+    const enc_ctx = Aes128.initEnc(key);
+    const dec_ctx = Aes128.initDec(key);
+
+    for (1..10) |i| {
+        const enc_rk = enc_ctx.key_schedule.round_keys[10 - i];
+        const dec_rk = dec_ctx.key_schedule.round_keys[i];
+        const computed = enc_rk.invMixColumns();
+        try testing.expectEqualSlices(u8, &dec_rk.toBytes(), &computed.toBytes());
+    }
+}
+
+test "BlockVec invMixColumns" {
+    const input = [_]u8{
+        0x5f, 0x57, 0xf7, 0x1d, 0x72, 0xf5, 0xbe, 0xb9, 0x64, 0xbc, 0x3b, 0xf9, 0x15, 0x92, 0x29, 0x1a,
+        0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c,
+    };
+
+    const vec2 = BlockVec(2).fromBytes(&input);
+    const result_vec = vec2.invMixColumns();
+    const result_bytes = result_vec.toBytes();
+
+    for (0..2) |i| {
+        const block = Block.fromBytes(input[i * 16 ..][0..16]);
+        const expected = block.invMixColumns().toBytes();
+        try testing.expectEqualSlices(u8, &expected, result_bytes[i * 16 ..][0..16]);
+    }
+}
+
 test "expand 256-bit key" {
     const key = [_]u8{
         0x60, 0x3d, 0xeb, 0x10,