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