authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2026-09-07 21:03:05+02:00
committergravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2026-10-01 12:06:17+02:00
logd5a8438044627bb7d6b7d28c6ecda50a6c6998b5
tree1043b6eac0230a29b952cfdb8e34e17e3369d35d
parentc6124a9a235fce00344c7a804fe11987dd17e1f6

crypto.ff: add bit measurement functions for public values, and shifts

- bitLenPublic(): returns the bit length of a public integer - trailingZeroBitsPublic(): number of trailing zero bits Also add internal shifts that will be needed for GCD and inversion.

1 files changed, 115 insertions(+), 0 deletions(-)

lib/std/crypto/ff.zig+115
......@@ -3,6 +3,8 @@
33//! Unlike `std.math.big`, these integers have a fixed maximum length and are only designed to be used for modular arithmetic.
44//! Arithmetic operations are meant to run in constant-time for a given modulus, making them suitable for cryptography.
55//!
6//! Functions with `Public` in their name are the exception.
7//!
68//! Parts of that code was ported from the BSD-licensed crypto/internal/bigmod/nat.go file in the Go language, itself inspired from BearSSL.
79
810const std = @import("std");
......@@ -253,6 +255,49 @@ pub fn Uint(comptime max_bits: comptime_int) type {
253255 return @intFromBool(!ct.eql(orLimbs(wide[n..][0..n]), 0));
254256 }
255257
258 /// Returns the bit length, or 0 for zero.
259 pub fn bitLenPublic(x: Self) usize {
260 var i = x.limbs_len;
261 while (i != 0) {
262 i -= 1;
263 const limb = x.limbsConst()[i];
264 if (limb != 0) {
265 return i * t_bits + t_bits - @clz(@as(TLimb, @intCast(limb)));
266 }
267 }
268 return 0;
269 }
270
271 /// Returns the number of trailing zero bits, or the active width for zero.
272 pub fn trailingZeroBitsPublic(x: Self) usize {
273 for (x.limbsConst(), 0..) |limb, i| {
274 if (limb != 0) {
275 return i * t_bits + @ctz(@as(TLimb, @intCast(limb)));
276 }
277 }
278 return x.limbs_len * t_bits;
279 }
280
281 pub fn shiftRightPublic(x: *Self, shift: usize) void {
282 const limb_shift = shift / t_bits;
283 const bit_shift = shift % t_bits;
284 const x_limbs = x.limbs();
285 if (limb_shift >= x_limbs.len) {
286 @memset(x_limbs, 0);
287 return;
288 }
289 const active = x_limbs.len - limb_shift;
290 for (0..active) |i| {
291 const lo = math.shr(Limb, x_limbs[i + limb_shift], bit_shift);
292 const hi = if (i + limb_shift + 1 < x_limbs.len)
293 math.shl(Limb, x_limbs[i + limb_shift + 1], t_bits - bit_shift)
294 else
295 0;
296 x_limbs[i] = @as(TLimb, @truncate(lo | hi));
297 }
298 @memset(x_limbs[active..], 0);
299 }
300
256301 fn expandTo(x: *Self, new_len: usize) void {
257302 assert(new_len >= x.limbs_len and new_len <= x.limbs_buffer.len);
258303 @memset(x.limbs_buffer[x.limbs_len..new_len], 0);
......@@ -289,6 +334,40 @@ pub fn Uint(comptime max_bits: comptime_int) type {
289334 }
290335 return borrow;
291336 }
337
338 fn shiftLeft(x: *Self, shift: usize) void {
339 assert(x.bitLenPublic() + shift <= x.limbs_len * t_bits);
340 const limb_shift = shift / t_bits;
341 const bit_shift = shift % t_bits;
342 const x_limbs = x.limbs();
343 var i = x_limbs.len;
344 while (i != 0) {
345 i -= 1;
346 const hi = if (i >= limb_shift)
347 math.shl(Limb, x_limbs[i - limb_shift], bit_shift)
348 else
349 0;
350 const lo = if (i >= limb_shift + 1)
351 math.shr(Limb, x_limbs[i - limb_shift - 1], t_bits - bit_shift)
352 else
353 0;
354 x_limbs[i] = @as(TLimb, @truncate(hi | lo));
355 }
356 }
357
358 // Shifts in `carry` at the top and returns the low bit shifted out.
359 fn shiftRightByOneWithCarry(x: *Self, carry: u1) u1 {
360 var c: Limb = carry;
361 var i = x.limbs_len;
362 const x_limbs = x.limbs();
363 while (i != 0) {
364 i -= 1;
365 const limb = x_limbs[i];
366 x_limbs[i] = (limb >> 1) | (c << (t_bits - 1));
367 c = @as(u1, @truncate(limb));
368 }
369 return @truncate(c);
370 }
292371 };
293372}
294373
......@@ -1256,6 +1335,42 @@ test "field element decoding" {
12561335 }
12571336}
12581337
1338test "Uint bit measurement and shifts" {
1339 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
1340
1341 const U = Uint(256);
1342 try testing.expectEqual(0, U.zero.bitLenPublic());
1343 try testing.expectEqual(U.max_limbs_count * t_bits, U.zero.trailingZeroBitsPublic());
1344 try testing.expectEqual(t_bits, U.zero.normalize().trailingZeroBitsPublic());
1345 for ([_]usize{ 0, t_bits - 1, t_bits, t_bits + 1, 255 }) |offset| {
1346 const x = try U.fromPrimitive(u256, math.shl(u256, 1, offset));
1347 try testing.expectEqual(offset + 1, x.bitLenPublic());
1348 try testing.expectEqual(offset, x.trailingZeroBitsPublic());
1349 }
1350
1351 const v: u256 = (1 << 255) | (1 << t_bits) | 3;
1352 for ([_]usize{ 0, 1, t_bits - 1, t_bits, t_bits + 1, U.max_limbs_count * t_bits, 10_000 }) |shift| {
1353 var x = try U.fromPrimitive(u256, v);
1354 x.shiftRightPublic(shift);
1355 try testing.expectEqual(math.shr(u256, v, shift), try x.toPrimitive(u256));
1356 try testing.expectEqual(U.max_limbs_count, x.limbs_len);
1357 try expectWellFormedLimbs(x);
1358 }
1359 for ([_]usize{ 0, 1, t_bits - 1, t_bits, t_bits + 1 }) |shift| {
1360 var x = try U.fromPrimitive(u256, (1 << t_bits) + 3);
1361 x.shiftLeft(shift);
1362 try testing.expectEqual(math.shl(u256, (1 << t_bits) + 3, shift), try x.toPrimitive(u256));
1363 }
1364
1365 var x = (try U.fromPrimitive(u8, 5)).normalize();
1366 try testing.expectEqual(1, x.shiftRightByOneWithCarry(1));
1367 try testing.expectEqual((1 << (t_bits - 1)) + 2, try x.toPrimitive(u64));
1368 try testing.expectEqual(0, x.shiftRightByOneWithCarry(0));
1369 x.shiftRightPublic(t_bits);
1370 try testing.expect(x.isZero());
1371 try testing.expectEqual(1, x.limbs_len);
1372}
1373
12591374test "Uint addition and multiply-add" {
12601375 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
12611376