| ... | ... | @@ -3,6 +3,8 @@ |
| 3 | 3 | //! Unlike `std.math.big`, these integers have a fixed maximum length and are only designed to be used for modular arithmetic. |
| 4 | 4 | //! Arithmetic operations are meant to run in constant-time for a given modulus, making them suitable for cryptography. |
| 5 | 5 | //! |
| 6 | //! Functions with `Public` in their name are the exception. |
| 7 | //! |
| 6 | 8 | //! Parts of that code was ported from the BSD-licensed crypto/internal/bigmod/nat.go file in the Go language, itself inspired from BearSSL. |
| 7 | 9 | |
| 8 | 10 | const std = @import("std"); |
| ... | ... | @@ -253,6 +255,49 @@ pub fn Uint(comptime max_bits: comptime_int) type { |
| 253 | 255 | return @intFromBool(!ct.eql(orLimbs(wide[n..][0..n]), 0)); |
| 254 | 256 | } |
| 255 | 257 | |
| 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 | |
| 256 | 301 | fn expandTo(x: *Self, new_len: usize) void { |
| 257 | 302 | assert(new_len >= x.limbs_len and new_len <= x.limbs_buffer.len); |
| 258 | 303 | @memset(x.limbs_buffer[x.limbs_len..new_len], 0); |
| ... | ... | @@ -289,6 +334,40 @@ pub fn Uint(comptime max_bits: comptime_int) type { |
| 289 | 334 | } |
| 290 | 335 | return borrow; |
| 291 | 336 | } |
| 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 | } |
| 292 | 371 | }; |
| 293 | 372 | } |
| 294 | 373 | |
| ... | ... | @@ -1256,6 +1335,42 @@ test "field element decoding" { |
| 1256 | 1335 | } |
| 1257 | 1336 | } |
| 1258 | 1337 | |
| 1338 | test "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 | |
| 1259 | 1374 | test "Uint addition and multiply-add" { |
| 1260 | 1375 | if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; |
| 1261 | 1376 | |