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 {...@@ -102,14 +102,15 @@ pub fn Uint(comptime max_bits: comptime_int) type {
102 /// Creates a new big integer from a primitive type.102 /// Creates a new big integer from a primitive type.
103 /// This function may not run in constant time.103 /// This function may not run in constant time.
104 pub fn fromPrimitive(comptime T: type, init_value: T) OverflowError!Self {104 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;
106 var out: Self = .{107 var out: Self = .{
107 .limbs_buffer = undefined,108 .limbs_buffer = undefined,
108 .limbs_len = max_limbs_count,109 .limbs_len = max_limbs_count,
109 };110 };
110 for (&out.limbs_buffer) |*limb| {111 for (&out.limbs_buffer) |*limb| {
111 limb.* = if (@bitSizeOf(T) > t_bits) @as(TLimb, @truncate(x)) else x;112 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);
113 }114 }
114 if (x != 0) {115 if (x != 0) {
115 return error.Overflow;116 return error.Overflow;
...@@ -120,18 +121,21 @@ pub fn Uint(comptime max_bits: comptime_int) type {...@@ -120,18 +121,21 @@ pub fn Uint(comptime max_bits: comptime_int) type {
120 /// Converts a big integer to a primitive type.121 /// Converts a big integer to a primitive type.
121 /// This function may not run in constant time.122 /// This function may not run in constant time.
122 pub fn toPrimitive(self: Self, comptime T: type) OverflowError!T {123 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;
124 var i = self.limbs_len - 1;126 var i = self.limbs_len - 1;
125 while (true) : (i -= 1) {127 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) {
127 return error.Overflow;131 return error.Overflow;
128 }132 }
129 x = math.shl(T, x, t_bits);133 x = math.shl(U, x, t_bits);
130 const v = math.cast(T, self.limbsConst()[i]) orelse return error.Overflow;134 const v = math.cast(U, self.limbsConst()[i]) orelse return error.Overflow;
131 x |= v;135 x |= v;
132 if (i == 0) break;136 if (i == 0) break;
133 }137 }
134 return x;138 return math.cast(T, x) orelse error.Overflow;
135 }139 }
136140
137 /// Encodes a big integer into a byte array.141 /// Encodes a big integer into a byte array.
...@@ -1085,3 +1089,20 @@ test ct {...@@ -1085,3 +1089,20 @@ test ct {
1085 try testCt(ct_protected);1089 try testCt(ct_protected);
1086 try testCt(ct_unprotected);1090 try testCt(ct_unprotected);
1087}1091}
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}