authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2026-09-07 21:02:45+02:00
committergravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2026-09-22 13:32:43+02:00
log8a353860179a1663987dae856ba08ccbbd037400
tree33f2558861a083608f0520a657b616805ed4c78e
parentb844605c524b9d7fe7327d7b95463ce70ed6fbf4

crypto.ff: fix primitive conversion overflow checks

Reject negative inputs and values that don't fit the destination type. And check for overflow before each limb shift, including when the destination is smaller than a limb.

1 files changed, 28 insertions(+), 7 deletions(-)

lib/std/crypto/ff.zig+28-7
......@@ -102,14 +102,15 @@ pub fn Uint(comptime max_bits: comptime_int) type {
102102 /// Creates a new big integer from a primitive type.
103103 /// This function may not run in constant time.
104104 pub fn fromPrimitive(comptime T: type, init_value: T) OverflowError!Self {
105 var x = init_value;
105 const U = @Int(.unsigned, @bitSizeOf(T));
106 var x = math.cast(U, init_value) orelse return error.Overflow;
106107 var out: Self = .{
107108 .limbs_buffer = undefined,
108109 .limbs_len = max_limbs_count,
109110 };
110111 for (&out.limbs_buffer) |*limb| {
111112 limb.* = if (@bitSizeOf(T) > t_bits) @as(TLimb, @truncate(x)) else x;
112 x = math.shr(T, x, t_bits);
113 x = math.shr(U, x, t_bits);
113114 }
114115 if (x != 0) {
115116 return error.Overflow;
......@@ -120,18 +121,21 @@ pub fn Uint(comptime max_bits: comptime_int) type {
120121 /// Converts a big integer to a primitive type.
121122 /// This function may not run in constant time.
122123 pub fn toPrimitive(self: Self, comptime T: type) OverflowError!T {
123 var x: T = 0;
124 const U = @Int(.unsigned, @bitSizeOf(T));
125 var x: U = 0;
124126 var i = self.limbs_len - 1;
125127 while (true) : (i -= 1) {
126 if (@bitSizeOf(T) >= t_bits and math.shr(T, x, @bitSizeOf(T) - t_bits) != 0) {
128 // Check for overflow before shifting, even when the destination is narrower than a limb.
129 const discarded = if (@bitSizeOf(U) >= t_bits) math.shr(U, x, @bitSizeOf(U) - t_bits) else x;
130 if (discarded != 0) {
127131 return error.Overflow;
128132 }
129 x = math.shl(T, x, t_bits);
130 const v = math.cast(T, self.limbsConst()[i]) orelse return error.Overflow;
133 x = math.shl(U, x, t_bits);
134 const v = math.cast(U, self.limbsConst()[i]) orelse return error.Overflow;
131135 x |= v;
132136 if (i == 0) break;
133137 }
134 return x;
138 return math.cast(T, x) orelse error.Overflow;
135139 }
136140
137141 /// Encodes a big integer into a byte array.
......@@ -1085,3 +1089,20 @@ test ct {
10851089 try testCt(ct_protected);
10861090 try testCt(ct_unprotected);
10871091}
1092
1093test "Uint serialization and predicates" {
1094 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
1095
1096 const U = Uint(256);
1097 const x = try U.fromPrimitive(u128, (1 << t_bits) + 5);
1098 try testing.expectError(error.Overflow, Uint(64).fromPrimitive(u256, 1 << 200));
1099 try testing.expectError(error.Overflow, x.toPrimitive(u8));
1100 try testing.expectError(error.Overflow, x.toPrimitive(@Int(.unsigned, t_bits)));
1101 try testing.expectEqual((1 << t_bits) + 5, try x.toPrimitive(@Int(.unsigned, t_bits + 1)));
1102 const max = try U.fromPrimitive(u128, (1 << t_bits) - 1);
1103 try testing.expectEqual((1 << t_bits) - 1, try max.toPrimitive(@Int(.unsigned, t_bits)));
1104 try testing.expectError(error.Overflow, max.toPrimitive(@Int(.unsigned, t_bits - 1)));
1105 const signed_max = try U.fromPrimitive(u128, math.maxInt(i128));
1106 try testing.expectEqual(math.maxInt(i128), try signed_max.toPrimitive(i128));
1107 try testing.expectError(error.Overflow, (try U.fromPrimitive(u128, 1 << 127)).toPrimitive(i128));
1108}