Skip to content

Commit ca6951f

Browse files
authored
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 commit ca6951f

File tree

1 file changed

+71
-4
lines changed

1 file changed

+71
-4
lines changed

lib/std/crypto/utils.zig

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const std = @import("../std.zig");
22
const debug = std.debug;
33
const mem = std.mem;
4+
const random = std.crypto.random;
45
const testing = std.testing;
56

67
const Endian = std.builtin.Endian;
@@ -77,6 +78,52 @@ pub fn timingSafeCompare(comptime T: type, a: []const T, b: []const T, endian: E
7778
return Order.lt;
7879
}
7980

81+
/// Add two integers serialized as arrays of the same size, in constant time.
82+
/// The result is stored into `result`, and `true` is returned if an overflow occurred.
83+
pub fn timingSafeAdd(comptime T: type, a: []const T, b: []const T, result: []T, endian: Endian) bool {
84+
const len = a.len;
85+
debug.assert(len == b.len and len == result.len);
86+
var carry: u1 = 0;
87+
if (endian == .Little) {
88+
var i: usize = 0;
89+
while (i < len) : (i += 1) {
90+
const tmp = @boolToInt(@addWithOverflow(u8, a[i], b[i], &result[i]));
91+
carry = tmp | @boolToInt(@addWithOverflow(u8, result[i], carry, &result[i]));
92+
}
93+
} else {
94+
var i: usize = len;
95+
while (i != 0) {
96+
i -= 1;
97+
const tmp = @boolToInt(@addWithOverflow(u8, a[i], b[i], &result[i]));
98+
carry = tmp | @boolToInt(@addWithOverflow(u8, result[i], carry, &result[i]));
99+
}
100+
}
101+
return @bitCast(bool, carry);
102+
}
103+
104+
/// Subtract two integers serialized as arrays of the same size, in constant time.
105+
/// The result is stored into `result`, and `true` is returned if an underflow occurred.
106+
pub fn timingSafeSub(comptime T: type, a: []const T, b: []const T, result: []T, endian: Endian) bool {
107+
const len = a.len;
108+
debug.assert(len == b.len and len == result.len);
109+
var borrow: u1 = 0;
110+
if (endian == .Little) {
111+
var i: usize = 0;
112+
while (i < len) : (i += 1) {
113+
const tmp = @boolToInt(@subWithOverflow(u8, a[i], b[i], &result[i]));
114+
borrow = tmp | @boolToInt(@subWithOverflow(u8, result[i], borrow, &result[i]));
115+
}
116+
} else {
117+
var i: usize = len;
118+
while (i != 0) {
119+
i -= 1;
120+
const tmp = @boolToInt(@subWithOverflow(u8, a[i], b[i], &result[i]));
121+
borrow = tmp | @boolToInt(@subWithOverflow(u8, result[i], borrow, &result[i]));
122+
}
123+
}
124+
return @bitCast(bool, borrow);
125+
}
126+
80127
/// Sets a slice to zeroes.
81128
/// Prevents the store from being optimized out.
82129
pub fn secureZero(comptime T: type, s: []T) void {
@@ -90,8 +137,8 @@ pub fn secureZero(comptime T: type, s: []T) void {
90137
test "crypto.utils.timingSafeEql" {
91138
var a: [100]u8 = undefined;
92139
var b: [100]u8 = undefined;
93-
std.crypto.random.bytes(a[0..]);
94-
std.crypto.random.bytes(b[0..]);
140+
random.bytes(a[0..]);
141+
random.bytes(b[0..]);
95142
try testing.expect(!timingSafeEql([100]u8, a, b));
96143
mem.copy(u8, a[0..], b[0..]);
97144
try testing.expect(timingSafeEql([100]u8, a, b));
@@ -100,8 +147,8 @@ test "crypto.utils.timingSafeEql" {
100147
test "crypto.utils.timingSafeEql (vectors)" {
101148
var a: [100]u8 = undefined;
102149
var b: [100]u8 = undefined;
103-
std.crypto.random.bytes(a[0..]);
104-
std.crypto.random.bytes(b[0..]);
150+
random.bytes(a[0..]);
151+
random.bytes(b[0..]);
105152
const v1: std.meta.Vector(100, u8) = a;
106153
const v2: std.meta.Vector(100, u8) = b;
107154
try testing.expect(!timingSafeEql(std.meta.Vector(100, u8), v1, v2));
@@ -122,6 +169,26 @@ test "crypto.utils.timingSafeCompare" {
122169
try testing.expectEqual(timingSafeCompare(u8, &a, &b, .Little), .lt);
123170
}
124171

172+
test "crypto.utils.timingSafe{Add,Sub}" {
173+
const len = 32;
174+
var a: [len]u8 = undefined;
175+
var b: [len]u8 = undefined;
176+
var c: [len]u8 = undefined;
177+
const zero = [_]u8{0} ** len;
178+
var iterations: usize = 100;
179+
while (iterations != 0) : (iterations -= 1) {
180+
random.bytes(&a);
181+
random.bytes(&b);
182+
const endian = if (iterations % 2 == 0) Endian.Big else Endian.Little;
183+
_ = timingSafeSub(u8, &a, &b, &c, endian); // a-b
184+
_ = timingSafeAdd(u8, &c, &b, &c, endian); // (a-b)+b
185+
try testing.expectEqualSlices(u8, &c, &a);
186+
const borrow = timingSafeSub(u8, &c, &a, &c, endian); // ((a-b)+b)-a
187+
try testing.expectEqualSlices(u8, &c, &zero);
188+
try testing.expectEqual(borrow, false);
189+
}
190+
}
191+
125192
test "crypto.utils.secureZero" {
126193
var a = [_]u8{0xfe} ** 8;
127194
var b = [_]u8{0xfe} ** 8;

0 commit comments

Comments
 (0)