Commit 21ae64852a

Frank Denis <124872+jedisct1@users.noreply.github.com>
2023-12-22 16:57:16
std.crypto.kem.kyber: mitigate KyberSlash (#18316)
On some architectures, including AMD Zen CPUs, dividing a secret by a constant denominator may not be a constant-time operation. And most Kyber implementations, including ours, could leak the hamming weight of the shared secret because of this. See: https://kyberslash.cr.yp.to Multiplications aren't guaranteed to be constant-time either, but at least on the CPUs we currently support, it is.
1 parent 42ddf59
Changed files (1)
lib
std
lib/std/crypto/kyber_d00.zig
@@ -1020,8 +1020,15 @@ const Poly = struct {
                 //                  = ⌊(2ᵈ/q)x+½⌋ mod⁺ 2ᵈ
                 //                  = ⌊((x << d) + q/2) / q⌋ mod⁺ 2ᵈ
                 //                  = DIV((x << d) + q/2, q) & ((1<<d) - 1)
-                const t = @as(u32, @intCast(p.cs[in_off + i])) << d;
-                in[i] = @as(u16, @intCast(@divFloor(t + q_over_2, Q) & two_d_min_1));
+                const t = @as(u24, @intCast(p.cs[in_off + i])) << d;
+                // Division by invariant multiplication, equivalent to DIV(t + q/2, q).
+                // A division may not be a constant-time operation, even with a constant denominator.
+                // Here, side channels would leak information about the shared secret, see https://kyberslash.cr.yp.to
+                // Multiplication, on the other hand, is a constant-time operation on the CPUs we currently support.
+                comptime assert(d <= 11);
+                comptime assert(((20642679 * @as(u64, Q)) >> 36) == 1);
+                const u: u32 = @intCast((@as(u64, t + q_over_2) * 20642679) >> 36);
+                in[i] = @intCast(u & two_d_min_1);
             }
 
             // Now we pack the d-bit integers from `in' into out as bytes.