authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2026-09-28 11:20:21+02:00
committergravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2026-09-28 11:20:21+02:00
logb6896c43001ea3ed8e5307fadcf58ff04255bc70
treee10a6f4cc77f351cdda73747ce0a546728a310ca
parentd8aab4878d471222daf448c63c8ae33cbc461bb3
parent7231fbda9c4707fa0edff5bb67f94b8df950ca71

Merge pull request 'crypto.ff: support mixed widths for additions and subtractions' (#36978) from jedisct1/zig:ff-fixes3 into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/36978

1 files changed, 37 insertions(+), 10 deletions(-)

lib/std/crypto/ff.zig+37-10
...@@ -228,16 +228,24 @@ pub fn Uint(comptime max_bits: comptime_int) type {...@@ -228,16 +228,24 @@ pub fn Uint(comptime max_bits: comptime_int) type {
228 return ct.eql((x_limbs[0] ^ 1) | orLimbs(x_limbs[1..]), 0);228 return ct.eql((x_limbs[0] ^ 1) | orLimbs(x_limbs[1..]), 0);
229 }229 }
230230
231 /// Adds `y` to `x`, and returns `true` if the operation overflowed.231 /// Adds `y` to `x`, wrapping at the larger active width. Returns 1 on overflow.
232 pub fn addWithOverflow(x: *Self, y: Self) u1 {232 pub fn addWithOverflow(x: *Self, y: Self) u1 {
233 x.expandTo(@max(x.limbs_len, y.limbs_len));
233 return x.conditionalAddWithOverflow(true, y);234 return x.conditionalAddWithOverflow(true, y);
234 }235 }
235236
236 /// Subtracts `y` from `x`, and returns `true` if the operation overflowed.237 /// Subtracts `y` from `x`, wrapping at the larger active width. Returns 1 on underflow.
237 pub fn subWithOverflow(x: *Self, y: Self) u1 {238 pub fn subWithOverflow(x: *Self, y: Self) u1 {
239 x.expandTo(@max(x.limbs_len, y.limbs_len));
238 return x.conditionalSubWithOverflow(true, y);240 return x.conditionalSubWithOverflow(true, y);
239 }241 }
240242
243 fn expandTo(x: *Self, new_len: usize) void {
244 assert(new_len >= x.limbs_len and new_len <= x.limbs_buffer.len);
245 @memset(x.limbs_buffer[x.limbs_len..new_len], 0);
246 x.limbs_len = new_len;
247 }
248
241 // Replaces the limbs of `x` with the limbs of `y` if `on` is `true`.249 // Replaces the limbs of `x` with the limbs of `y` if `on` is `true`.
242 fn cmov(x: *Self, on: bool, y: Self) void {250 fn cmov(x: *Self, on: bool, y: Self) void {
243 for (x.limbs(), y.limbsConst()) |*x_limb, y_limb| {251 for (x.limbs(), y.limbsConst()) |*x_limb, y_limb| {
...@@ -245,24 +253,24 @@ pub fn Uint(comptime max_bits: comptime_int) type {...@@ -245,24 +253,24 @@ pub fn Uint(comptime max_bits: comptime_int) type {
245 }253 }
246 }254 }
247255
248 // Adds `y` to `x` if `on` is `true`, and returns `true` if the256 // Zeroed unused limbs allow `y` to be narrower than `x`.
249 // operation overflowed.
250 fn conditionalAddWithOverflow(x: *Self, on: bool, y: Self) u1 {257 fn conditionalAddWithOverflow(x: *Self, on: bool, y: Self) u1 {
251 var carry: u1 = 0;258 var carry: u1 = 0;
252 for (x.limbs(), y.limbsConst()) |*x_limb, y_limb| {259 assert(y.limbs_len <= x.limbs_len);
253 const res = x_limb.* + y_limb + carry;260 for (x.limbs(), 0..) |*x_limb, i| {
261 const res = x_limb.* + y.limbs_buffer[i] + carry;
254 x_limb.* = ct.select(on, @as(TLimb, @truncate(res)), x_limb.*);262 x_limb.* = ct.select(on, @as(TLimb, @truncate(res)), x_limb.*);
255 carry = @truncate(res >> t_bits);263 carry = @truncate(res >> t_bits);
256 }264 }
257 return carry;265 return carry;
258 }266 }
259267
260 // Subtracts `y` from `x` if `on` is `true`, and returns `true` if the268 // Zeroed unused limbs allow `y` to be narrower than `x`.
261 // operation overflowed.
262 fn conditionalSubWithOverflow(x: *Self, on: bool, y: Self) u1 {269 fn conditionalSubWithOverflow(x: *Self, on: bool, y: Self) u1 {
263 var borrow: u1 = 0;270 var borrow: u1 = 0;
264 for (x.limbs(), y.limbsConst()) |*x_limb, y_limb| {271 assert(y.limbs_len <= x.limbs_len);
265 const res = x_limb.* -% y_limb -% borrow;272 for (x.limbs(), 0..) |*x_limb, i| {
273 const res = x_limb.* -% y.limbs_buffer[i] -% borrow;
266 x_limb.* = ct.select(on, @as(TLimb, @truncate(res)), x_limb.*);274 x_limb.* = ct.select(on, @as(TLimb, @truncate(res)), x_limb.*);
267 borrow = @truncate(res >> t_bits);275 borrow = @truncate(res >> t_bits);
268 }276 }
...@@ -1210,3 +1218,22 @@ test "field element decoding" {...@@ -1210,3 +1218,22 @@ test "field element decoding" {
1210 try testing.expect(x.eql(try M.Fe.fromBytes(m, &buf, endian)));1218 try testing.expect(x.eql(try M.Fe.fromBytes(m, &buf, endian)));
1211 }1219 }
1212}1220}
1221
1222test "Uint addition and multiply-add" {
1223 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
1224
1225 const U = Uint(256);
1226 const full = try U.fromBytes(&maxUintBytes(U), .big);
1227 const one = try U.fromPrimitive(u8, 1);
1228 var carry = (try U.fromPrimitive(u64, math.maxInt(TLimb))).normalize();
1229 try testing.expectEqual(0, carry.addWithOverflow(one));
1230 try testing.expectEqual(one.limbs_len, carry.limbs_len);
1231 try testing.expectEqual(1 << t_bits, try carry.toPrimitive(u128));
1232 try testing.expectEqual(0, carry.subWithOverflow(one.normalize()));
1233 try testing.expectEqual(math.maxInt(TLimb), try carry.toPrimitive(u64));
1234 var wrapped = U.zero.normalize();
1235 try testing.expectEqual(1, wrapped.subWithOverflow(one));
1236 try testing.expect(wrapped.eql(full));
1237 try testing.expectEqual(1, wrapped.addWithOverflow(one));
1238 try testing.expect(wrapped.isZero());
1239}