Commit ca6951ff79

Frank Denis <124872+jedisct1@users.noreply.github.com>
2021-06-06 16:37:58
std.crypto: timing-safe functions to add&subtract serialized integers (#8977)
This is useful to increment nonces and for scalar reduction. It will avoid code duplication in crypto/25519 and crypto/pcurves/*
1 parent fbd9931
Changed files (1)
lib
std
crypto
lib/std/crypto/utils.zig
@@ -1,6 +1,7 @@
 const std = @import("../std.zig");
 const debug = std.debug;
 const mem = std.mem;
+const random = std.crypto.random;
 const testing = std.testing;
 
 const Endian = std.builtin.Endian;
@@ -77,6 +78,52 @@ pub fn timingSafeCompare(comptime T: type, a: []const T, b: []const T, endian: E
     return Order.lt;
 }
 
+/// Add two integers serialized as arrays of the same size, in constant time.
+/// The result is stored into `result`, and `true` is returned if an overflow occurred.
+pub fn timingSafeAdd(comptime T: type, a: []const T, b: []const T, result: []T, endian: Endian) bool {
+    const len = a.len;
+    debug.assert(len == b.len and len == result.len);
+    var carry: u1 = 0;
+    if (endian == .Little) {
+        var i: usize = 0;
+        while (i < len) : (i += 1) {
+            const tmp = @boolToInt(@addWithOverflow(u8, a[i], b[i], &result[i]));
+            carry = tmp | @boolToInt(@addWithOverflow(u8, result[i], carry, &result[i]));
+        }
+    } else {
+        var i: usize = len;
+        while (i != 0) {
+            i -= 1;
+            const tmp = @boolToInt(@addWithOverflow(u8, a[i], b[i], &result[i]));
+            carry = tmp | @boolToInt(@addWithOverflow(u8, result[i], carry, &result[i]));
+        }
+    }
+    return @bitCast(bool, carry);
+}
+
+/// Subtract two integers serialized as arrays of the same size, in constant time.
+/// The result is stored into `result`, and `true` is returned if an underflow occurred.
+pub fn timingSafeSub(comptime T: type, a: []const T, b: []const T, result: []T, endian: Endian) bool {
+    const len = a.len;
+    debug.assert(len == b.len and len == result.len);
+    var borrow: u1 = 0;
+    if (endian == .Little) {
+        var i: usize = 0;
+        while (i < len) : (i += 1) {
+            const tmp = @boolToInt(@subWithOverflow(u8, a[i], b[i], &result[i]));
+            borrow = tmp | @boolToInt(@subWithOverflow(u8, result[i], borrow, &result[i]));
+        }
+    } else {
+        var i: usize = len;
+        while (i != 0) {
+            i -= 1;
+            const tmp = @boolToInt(@subWithOverflow(u8, a[i], b[i], &result[i]));
+            borrow = tmp | @boolToInt(@subWithOverflow(u8, result[i], borrow, &result[i]));
+        }
+    }
+    return @bitCast(bool, borrow);
+}
+
 /// Sets a slice to zeroes.
 /// Prevents the store from being optimized out.
 pub fn secureZero(comptime T: type, s: []T) void {
@@ -90,8 +137,8 @@ pub fn secureZero(comptime T: type, s: []T) void {
 test "crypto.utils.timingSafeEql" {
     var a: [100]u8 = undefined;
     var b: [100]u8 = undefined;
-    std.crypto.random.bytes(a[0..]);
-    std.crypto.random.bytes(b[0..]);
+    random.bytes(a[0..]);
+    random.bytes(b[0..]);
     try testing.expect(!timingSafeEql([100]u8, a, b));
     mem.copy(u8, a[0..], b[0..]);
     try testing.expect(timingSafeEql([100]u8, a, b));
@@ -100,8 +147,8 @@ test "crypto.utils.timingSafeEql" {
 test "crypto.utils.timingSafeEql (vectors)" {
     var a: [100]u8 = undefined;
     var b: [100]u8 = undefined;
-    std.crypto.random.bytes(a[0..]);
-    std.crypto.random.bytes(b[0..]);
+    random.bytes(a[0..]);
+    random.bytes(b[0..]);
     const v1: std.meta.Vector(100, u8) = a;
     const v2: std.meta.Vector(100, u8) = b;
     try testing.expect(!timingSafeEql(std.meta.Vector(100, u8), v1, v2));
@@ -122,6 +169,26 @@ test "crypto.utils.timingSafeCompare" {
     try testing.expectEqual(timingSafeCompare(u8, &a, &b, .Little), .lt);
 }
 
+test "crypto.utils.timingSafe{Add,Sub}" {
+    const len = 32;
+    var a: [len]u8 = undefined;
+    var b: [len]u8 = undefined;
+    var c: [len]u8 = undefined;
+    const zero = [_]u8{0} ** len;
+    var iterations: usize = 100;
+    while (iterations != 0) : (iterations -= 1) {
+        random.bytes(&a);
+        random.bytes(&b);
+        const endian = if (iterations % 2 == 0) Endian.Big else Endian.Little;
+        _ = timingSafeSub(u8, &a, &b, &c, endian); // a-b
+        _ = timingSafeAdd(u8, &c, &b, &c, endian); // (a-b)+b
+        try testing.expectEqualSlices(u8, &c, &a);
+        const borrow = timingSafeSub(u8, &c, &a, &c, endian); // ((a-b)+b)-a
+        try testing.expectEqualSlices(u8, &c, &zero);
+        try testing.expectEqual(borrow, false);
+    }
+}
+
 test "crypto.utils.secureZero" {
     var a = [_]u8{0xfe} ** 8;
     var b = [_]u8{0xfe} ** 8;