|
const std = @import("std"); const builtin = @import("builtin"); const crypto = std.crypto; const math = std.math; const mem = std.mem; const meta = std.meta; const testing = std.testing; const BoundedArray = std.BoundedArray; const assert = std.debug.assert; const Endian = std.builtin.Endian; // A Limb is a single digit in a big integer. const Limb = usize; // The number of reserved bits in a Limb. const carry_bits = 1; // The number of active bits in a Limb. const t_bits: usize = @bitSizeOf(Limb) - carry_bits; // A TLimb is a Limb that is truncated to t_bits. const TLimb = meta.Int(.unsigned, t_bits); const native_endian = builtin.target.cpu.arch.endian(); // A WideLimb is a Limb that is twice as wide as a normal Limb. const WideLimb = struct { hi: Limb, lo: Limb, }; |
OverflowErrorValue is too large for the destination. |
pub const OverflowError = error{Overflow}; |
InvalidModulusErrorInvalid modulus. Modulus must be odd. |
pub const InvalidModulusError = error{ EvenModulus, ModulusTooSmall }; |
NullExponentErrorExponentation with a null exponent. Exponentiation in cryptographic protocols is almost always a sign of a bug which can lead to trivial attacks. Therefore, this module returns an error when a null exponent is encountered, encouraging applications to handle this case explicitly. |
pub const NullExponentError = error{NullExponent}; |
FieldElementErrorInvalid field element for the given modulus. |
pub const FieldElementError = error{NonCanonical}; |
RepresentationErrorInvalid representation (Montgomery vs non-Montgomery domain.) |
pub const RepresentationError = error{UnexpectedRepresentation}; |
Error The set of all possible errors |
pub const Error = OverflowError || InvalidModulusError || NullExponentError || FieldElementError || RepresentationError; |
Uint() An unsigned big integer with a fixed maximum size ( |
pub fn Uint(comptime max_bits: comptime_int) type { comptime assert(@bitSizeOf(Limb) % 8 == 0); // Limb size must be a multiple of 8 return struct { const Self = @This(); const max_limbs_count = math.divCeil(usize, max_bits, t_bits) catch unreachable; const Limbs = BoundedArray(Limb, max_limbs_count); limbs: Limbs, pub const encoded_bytes = math.divCeil(usize, max_bits, 8) catch unreachable; // Returns the number of active limbs. fn limbs_count(self: Self) usize { return self.limbs.len; } // Removes limbs whose value is zero from the active limbs. fn normalize(self: Self) Self { var res = self; if (self.limbs_count() < 2) { return res; } var i = self.limbs_count() - 1; while (i > 0 and res.limbs.get(i) == 0) : (i -= 1) {} res.limbs.resize(i + 1) catch unreachable; return res; } pub const zero = zero: { var limbs = Limbs.init(0) catch unreachable; limbs.appendNTimesAssumeCapacity(0, max_limbs_count); break :zero Self{ .limbs = limbs }; }; |
fromPrimitive()Number of bytes required to serialize an integer. The zero integer. Creates a new big integer from a primitive type. This function may not run in constant time. |
pub fn fromPrimitive(comptime T: type, x_: T) OverflowError!Self { var x = x_; var out = Self.zero; for (0..out.limbs.capacity()) |i| { const t = if (@bitSizeOf(T) > t_bits) @as(TLimb, @truncate(x)) else x; out.limbs.set(i, t); x = math.shr(T, x, t_bits); } if (x != 0) { return error.Overflow; } return out; } |
toPrimitive()Converts a big integer to a primitive type. This function may not run in constant time. |
pub fn toPrimitive(self: Self, comptime T: type) OverflowError!T { var x: T = 0; var i = self.limbs_count() - 1; while (true) : (i -= 1) { if (@bitSizeOf(T) >= t_bits and math.shr(T, x, @bitSizeOf(T) - t_bits) != 0) { return error.Overflow; } x = math.shl(T, x, t_bits); const v = math.cast(T, self.limbs.get(i)) orelse return error.Overflow; x |= v; if (i == 0) break; } return x; } |
toBytes()Encodes a big integer into a byte array. |
pub fn toBytes(self: Self, bytes: []u8, comptime endian: Endian) OverflowError!void { if (bytes.len == 0) { if (self.isZero()) return; return error.Overflow; } @memset(bytes, 0); var shift: usize = 0; var out_i: usize = switch (endian) { .big => bytes.len - 1, .little => 0, }; for (0..self.limbs.len) |i| { var remaining_bits = t_bits; var limb = self.limbs.get(i); while (remaining_bits >= 8) { bytes[out_i] |= math.shl(u8, @as(u8, @truncate(limb)), shift); const consumed = 8 - shift; limb >>= @as(u4, @truncate(consumed)); remaining_bits -= consumed; shift = 0; switch (endian) { .big => { if (out_i == 0) { if (i != self.limbs.len - 1 or limb != 0) { return error.Overflow; } return; } out_i -= 1; }, .little => { out_i += 1; if (out_i == bytes.len) { if (i != self.limbs.len - 1 or limb != 0) { return error.Overflow; } return; } }, } } bytes[out_i] |= @as(u8, @truncate(limb)); shift = remaining_bits; } } |
fromBytes()Creates a new big integer from a byte array. |
pub fn fromBytes(bytes: []const u8, comptime endian: Endian) OverflowError!Self { if (bytes.len == 0) return Self.zero; var shift: usize = 0; var out = Self.zero; var out_i: usize = 0; var i: usize = switch (endian) { .big => bytes.len - 1, .little => 0, }; while (true) { const bi = bytes[i]; out.limbs.set(out_i, out.limbs.get(out_i) | math.shl(Limb, bi, shift)); shift += 8; if (shift >= t_bits) { shift -= t_bits; out.limbs.set(out_i, @as(TLimb, @truncate(out.limbs.get(out_i)))); const overflow = math.shr(Limb, bi, 8 - shift); out_i += 1; if (out_i >= out.limbs.len) { if (overflow != 0 or i != 0) { return error.Overflow; } break; } out.limbs.set(out_i, overflow); } switch (endian) { .big => { if (i == 0) break; i -= 1; }, .little => { i += 1; if (i == bytes.len) break; }, } } return out; } |
eql() Returns |
pub fn eql(x: Self, y: Self) bool { return crypto.utils.timingSafeEql([max_limbs_count]Limb, x.limbs.buffer, y.limbs.buffer); } |
compare()Compares two integers. |
pub fn compare(x: Self, y: Self) math.Order { return crypto.utils.timingSafeCompare( Limb, x.limbs.constSlice(), y.limbs.constSlice(), .little, ); } |
isZero() Returns |
pub fn isZero(x: Self) bool { const x_limbs = x.limbs.constSlice(); var t: Limb = 0; for (0..x.limbs_count()) |i| { t |= x_limbs[i]; } return ct.eql(t, 0); } |
isOdd() Returns |
pub fn isOdd(x: Self) bool { return @as(bool, @bitCast(@as(u1, @truncate(x.limbs.get(0))))); } |
addWithOverflow() Adds |
pub fn addWithOverflow(x: *Self, y: Self) u1 { return x.conditionalAddWithOverflow(true, y); } |
subWithOverflow() Subtracts |
pub fn subWithOverflow(x: *Self, y: Self) u1 { return x.conditionalSubWithOverflow(true, y); } // Replaces the limbs of `x` with the limbs of `y` if `on` is `true`. fn cmov(x: *Self, on: bool, y: Self) void { const x_limbs = x.limbs.slice(); const y_limbs = y.limbs.constSlice(); for (0..y.limbs_count()) |i| { x_limbs[i] = ct.select(on, y_limbs[i], x_limbs[i]); } } // Adds `y` to `x` if `on` is `true`, and returns `true` if the operation overflowed. fn conditionalAddWithOverflow(x: *Self, on: bool, y: Self) u1 { assert(x.limbs_count() == y.limbs_count()); // Operands must have the same size. const x_limbs = x.limbs.slice(); const y_limbs = y.limbs.constSlice(); var carry: u1 = 0; for (0..x.limbs_count()) |i| { const res = x_limbs[i] + y_limbs[i] + carry; x_limbs[i] = ct.select(on, @as(TLimb, @truncate(res)), x_limbs[i]); carry = @as(u1, @truncate(res >> t_bits)); } return carry; } // Subtracts `y` from `x` if `on` is `true`, and returns `true` if the operation overflowed. fn conditionalSubWithOverflow(x: *Self, on: bool, y: Self) u1 { assert(x.limbs_count() == y.limbs_count()); // Operands must have the same size. const x_limbs = x.limbs.slice(); const y_limbs = y.limbs.constSlice(); var borrow: u1 = 0; for (0..x.limbs_count()) |i| { const res = x_limbs[i] -% y_limbs[i] -% borrow; x_limbs[i] = ct.select(on, @as(TLimb, @truncate(res)), x_limbs[i]); borrow = @as(u1, @truncate(res >> t_bits)); } return borrow; } }; } fn Fe_(comptime bits: comptime_int) type { return struct { const Self = @This(); const FeUint = Uint(bits); v: FeUint, montgomery: bool = false, pub const encoded_bytes = FeUint.encoded_bytes; // The number of active limbs to represent the field element. fn limbs_count(self: Self) usize { return self.v.limbs_count(); } |
fromPrimitive() A field element. The element value as a |
pub fn fromPrimitive(comptime T: type, m: Modulus(bits), x: T) (OverflowError || FieldElementError)!Self { comptime assert(@bitSizeOf(T) <= bits); // Primitive type is larger than the modulus type. const v = try FeUint.fromPrimitive(T, x); var fe = Self{ .v = v }; try m.shrink(&fe); try m.rejectNonCanonical(fe); return fe; } |
toPrimitive()Converts the field element to a primitive. This function may not run in constant time. |
pub fn toPrimitive(self: Self, comptime T: type) OverflowError!T { return self.v.toPrimitive(T); } |
fromBytes()Creates a field element from a byte string. |
pub fn fromBytes(m: Modulus(bits), bytes: []const u8, comptime endian: Endian) (OverflowError || FieldElementError)!Self { const v = try FeUint.fromBytes(bytes, endian); var fe = Self{ .v = v }; try m.shrink(&fe); try m.rejectNonCanonical(fe); return fe; } |
toBytes()Converts the field element to a byte string. |
pub fn toBytes(self: Self, bytes: []u8, comptime endian: Endian) OverflowError!void { return self.v.toBytes(bytes, endian); } |
eql() Returns |
pub fn eql(x: Self, y: Self) bool { return x.v.eql(y.v); } |
compare()Compares two field elements in constant time. |
pub fn compare(x: Self, y: Self) math.Order { return x.v.compare(y.v); } |
isZero() Returns |
pub fn isZero(self: Self) bool { return self.v.isZero(); } |
isOdd() Returns |
pub fn isOdd(self: Self) bool { return self.v.isOdd(); } }; } |
Modulus() A modulus, defining a finite field. All operations within the field are performed modulo this modulus, without heap allocations. |
pub fn Modulus(comptime max_bits: comptime_int) type { return struct { const Self = @This(); pub const Fe = Fe_(max_bits); const FeUint = Fe.FeUint; zero: Fe, v: FeUint, rr: Fe, m0inv: Limb, leading: usize, // Number of active limbs in the modulus. fn limbs_count(self: Self) usize { return self.v.limbs_count(); } |
bits()A field element, representing a value within the field defined by this modulus. The neutral element. The modulus value. R^2 for the Montgomery representation. Inverse of the first limb Number of leading zero bits in the modulus. Actual size of the modulus, in bits. |
pub fn bits(self: Self) usize { return self.limbs_count() * t_bits - self.leading; } |
one() Returns the element |
pub fn one(self: Self) Fe { var fe = self.zero; fe.v.limbs.set(0, 1); return fe; } |
fromUint() Creates a new modulus from a |
pub fn fromUint(v_: FeUint) InvalidModulusError!Self { if (!v_.isOdd()) return error.EvenModulus; var v = v_.normalize(); const hi = v.limbs.get(v.limbs_count() - 1); const lo = v.limbs.get(0); if (v.limbs_count() < 2 and lo < 3) { return error.ModulusTooSmall; } const leading = @clz(hi) - carry_bits; var y = lo; inline for (0..comptime math.log2_int(usize, t_bits)) |_| { y = y *% (2 -% lo *% y); } const m0inv = (@as(Limb, 1) << t_bits) - (@as(TLimb, @truncate(y))); const zero = Fe{ .v = FeUint.zero }; var m = Self{ .zero = zero, .v = v, .leading = leading, .m0inv = m0inv, .rr = undefined, // will be computed right after }; m.shrink(&m.zero) catch unreachable; computeRR(&m); return m; } |
fromPrimitive()Creates a new modulus from a primitive value. The modulus must be odd and larger than 2. |
pub fn fromPrimitive(comptime T: type, x: T) (InvalidModulusError || OverflowError)!Self { comptime assert(@bitSizeOf(T) <= max_bits); // Primitive type is larger than the modulus type. const v = try FeUint.fromPrimitive(T, x); return try Self.fromUint(v); } |
fromBytes()Creates a new modulus from a byte string. |
pub fn fromBytes(bytes: []const u8, comptime endian: Endian) (InvalidModulusError || OverflowError)!Self { const v = try FeUint.fromBytes(bytes, endian); return try Self.fromUint(v); } |
toBytes()Serializes the modulus to a byte string. |
pub fn toBytes(self: Self, bytes: []u8, comptime endian: Endian) OverflowError!void { return self.v.toBytes(bytes, endian); } |
rejectNonCanonical()Rejects field elements that are not in the canonical form. |
pub fn rejectNonCanonical(self: Self, fe: Fe) error{NonCanonical}!void { if (fe.limbs_count() != self.limbs_count() or ct.limbsCmpGeq(fe.v, self.v)) { return error.NonCanonical; } } // Makes the number of active limbs in a field element match the one of the modulus. fn shrink(self: Self, fe: *Fe) OverflowError!void { const new_len = self.limbs_count(); if (fe.limbs_count() < new_len) return error.Overflow; var acc: Limb = 0; for (fe.v.limbs.constSlice()[new_len..]) |limb| { acc |= limb; } if (acc != 0) return error.Overflow; try fe.v.limbs.resize(new_len); } // Computes R^2 for the Montgomery representation. fn computeRR(self: *Self) void { self.rr = self.zero; const n = self.rr.limbs_count(); self.rr.v.limbs.set(n - 1, 1); for ((n - 1)..(2 * n)) |_| { self.shiftIn(&self.rr, 0); } self.shrink(&self.rr) catch unreachable; } fn shiftIn(self: Self, x: *Fe, y: Limb) void { var d = self.zero; const x_limbs = x.v.limbs.slice(); const d_limbs = d.v.limbs.slice(); const m_limbs = self.v.limbs.constSlice(); var need_sub = false; var i: usize = t_bits - 1; while (true) : (i -= 1) { var carry: u1 = @truncate(math.shr(Limb, y, i)); var borrow: u1 = 0; for (0..self.limbs_count()) |j| { const l = ct.select(need_sub, d_limbs[j], x_limbs[j]); var res = (l << 1) + carry; x_limbs[j] = @as(TLimb, @truncate(res)); carry = @truncate(res >> t_bits); res = x_limbs[j] -% m_limbs[j] -% borrow; d_limbs[j] = @as(TLimb, @truncate(res)); borrow = @truncate(res >> t_bits); } need_sub = ct.eql(carry, borrow); if (i == 0) break; } x.v.cmov(need_sub, d.v); } |
add()Computes x << t_bits + y (mod m) Adds two field elements (mod m). |
pub fn add(self: Self, x: Fe, y: Fe) Fe { var out = x; const overflow = out.v.addWithOverflow(y.v); const underflow: u1 = @bitCast(ct.limbsCmpLt(out.v, self.v)); const need_sub = ct.eql(overflow, underflow); _ = out.v.conditionalSubWithOverflow(need_sub, self.v); return out; } |
sub()Subtracts two field elements (mod m). |
pub fn sub(self: Self, x: Fe, y: Fe) Fe { var out = x; const underflow: bool = @bitCast(out.v.subWithOverflow(y.v)); _ = out.v.conditionalAddWithOverflow(underflow, self.v); return out; } |
toMontgomery()Converts a field element to the Montgomery form. |
pub fn toMontgomery(self: Self, x: *Fe) RepresentationError!void { if (x.montgomery) { return error.UnexpectedRepresentation; } self.shrink(x) catch unreachable; x.* = self.montgomeryMul(x.*, self.rr); x.montgomery = true; } |
fromMontgomery()Takes a field element out of the Montgomery form. |
pub fn fromMontgomery(self: Self, x: *Fe) RepresentationError!void { if (!x.montgomery) { return error.UnexpectedRepresentation; } self.shrink(x) catch unreachable; x.* = self.montgomeryMul(x.*, self.one()); x.montgomery = false; } |
reduce() Reduces an arbitrary |
pub fn reduce(self: Self, x: anytype) Fe { var out = self.zero; var i = x.limbs_count() - 1; if (self.limbs_count() >= 2) { const start = @min(i, self.limbs_count() - 2); var j = start; while (true) : (j -= 1) { out.v.limbs.set(j, x.limbs.get(i)); i -= 1; if (j == 0) break; } } while (true) : (i -= 1) { self.shiftIn(&out, x.limbs.get(i)); if (i == 0) break; } return out; } fn montgomeryLoop(self: Self, d: *Fe, x: Fe, y: Fe) u1 { assert(d.limbs_count() == x.limbs_count()); assert(d.limbs_count() == y.limbs_count()); assert(d.limbs_count() == self.limbs_count()); const a_limbs = x.v.limbs.constSlice(); const b_limbs = y.v.limbs.constSlice(); const d_limbs = d.v.limbs.slice(); const m_limbs = self.v.limbs.constSlice(); var overflow: u1 = 0; for (0..self.limbs_count()) |i| { var carry: Limb = 0; var wide = ct.mulWide(a_limbs[i], b_limbs[0]); var z_lo = @addWithOverflow(d_limbs[0], wide.lo); const f = @as(TLimb, @truncate(z_lo[0] *% self.m0inv)); var z_hi = wide.hi +% z_lo[1]; wide = ct.mulWide(f, m_limbs[0]); z_lo = @addWithOverflow(z_lo[0], wide.lo); z_hi +%= z_lo[1]; z_hi +%= wide.hi; carry = (z_hi << 1) | (z_lo[0] >> t_bits); for (1..self.limbs_count()) |j| { wide = ct.mulWide(a_limbs[i], b_limbs[j]); z_lo = @addWithOverflow(d_limbs[j], wide.lo); z_hi = wide.hi +% z_lo[1]; wide = ct.mulWide(f, m_limbs[j]); z_lo = @addWithOverflow(z_lo[0], wide.lo); z_hi +%= z_lo[1]; z_hi +%= wide.hi; z_lo = @addWithOverflow(z_lo[0], carry); z_hi +%= z_lo[1]; if (j > 0) { d_limbs[j - 1] = @as(TLimb, @truncate(z_lo[0])); } carry = (z_hi << 1) | (z_lo[0] >> t_bits); } const z = overflow + carry; d_limbs[self.limbs_count() - 1] = @as(TLimb, @truncate(z)); overflow = @as(u1, @truncate(z >> t_bits)); } return overflow; } // Montgomery multiplication. fn montgomeryMul(self: Self, x: Fe, y: Fe) Fe { var d = self.zero; assert(x.limbs_count() == self.limbs_count()); assert(y.limbs_count() == self.limbs_count()); const overflow = self.montgomeryLoop(&d, x, y); const underflow = 1 -% @intFromBool(ct.limbsCmpGeq(d.v, self.v)); const need_sub = ct.eql(overflow, underflow); _ = d.v.conditionalSubWithOverflow(need_sub, self.v); d.montgomery = x.montgomery == y.montgomery; return d; } // Montgomery squaring. fn montgomerySq(self: Self, x: Fe) Fe { var d = self.zero; assert(x.limbs_count() == self.limbs_count()); const overflow = self.montgomeryLoop(&d, x, x); const underflow = 1 -% @intFromBool(ct.limbsCmpGeq(d.v, self.v)); const need_sub = ct.eql(overflow, underflow); _ = d.v.conditionalSubWithOverflow(need_sub, self.v); d.montgomery = true; return d; } // Returns x^e (mod m), with the exponent provided as a byte string. // `public` must be set to `false` if the exponent it secret. fn powWithEncodedExponentInternal(self: Self, x: Fe, e: []const u8, endian: Endian, comptime public: bool) NullExponentError!Fe { var acc: u8 = 0; for (e) |b| acc |= b; if (acc == 0) return error.NullExponent; var out = self.one(); self.toMontgomery(&out) catch unreachable; if (public and e.len < 3 or (e.len == 3 and e[if (endian == .big) 0 else 2] <= 0b1111)) { // Do not use a precomputation table for short, public exponents var x_m = x; if (x.montgomery == false) { self.toMontgomery(&x_m) catch unreachable; } var s = switch (endian) { .big => 0, .little => e.len - 1, }; while (true) { const b = e[s]; var j: u3 = 7; while (true) : (j -= 1) { out = self.montgomerySq(out); const k: u1 = @truncate(b >> j); if (k != 0) { const t = self.montgomeryMul(out, x_m); @memcpy(out.v.limbs.slice(), t.v.limbs.constSlice()); } if (j == 0) break; } switch (endian) { .big => { s += 1; if (s == e.len) break; }, .little => { if (s == 0) break; s -= 1; }, } } } else { // Use a precomputation table for large exponents var pc = [1]Fe{x} ++ [_]Fe{self.zero} ** 14; if (x.montgomery == false) { self.toMontgomery(&pc[0]) catch unreachable; } for (1..pc.len) |i| { pc[i] = self.montgomeryMul(pc[i - 1], pc[0]); } var t0 = self.zero; var s = switch (endian) { .big => 0, .little => e.len - 1, }; while (true) { const b = e[s]; for ([_]u3{ 4, 0 }) |j| { for (0..4) |_| { out = self.montgomerySq(out); } const k = (b >> j) & 0b1111; if (public or std.options.side_channels_mitigations == .none) { if (k == 0) continue; t0 = pc[k - 1]; } else { for (pc, 0..) |t, i| { t0.v.cmov(ct.eql(k, @as(u8, @truncate(i + 1))), t.v); } } const t1 = self.montgomeryMul(out, t0); if (public) { @memcpy(out.v.limbs.slice(), t1.v.limbs.constSlice()); } else { out.v.cmov(!ct.eql(k, 0), t1.v); } } switch (endian) { .big => { s += 1; if (s == e.len) break; }, .little => { if (s == 0) break; s -= 1; }, } } } self.fromMontgomery(&out) catch unreachable; return out; } |
mul()Multiplies two field elements. |
pub fn mul(self: Self, x: Fe, y: Fe) Fe { if (x.montgomery != y.montgomery) { return self.montgomeryMul(x, y); } var a_ = x; if (x.montgomery == false) { self.toMontgomery(&a_) catch unreachable; } else { self.fromMontgomery(&a_) catch unreachable; } return self.montgomeryMul(a_, y); } |
sq()Squares a field element. |
pub fn sq(self: Self, x: Fe) Fe { var out = x; if (x.montgomery == true) { self.fromMontgomery(&out) catch unreachable; } out = self.montgomerySq(out); out.montgomery = false; self.toMontgomery(&out) catch unreachable; return out; } |
pow()Returns x^e (mod m) in constant time. |
pub fn pow(self: Self, x: Fe, e: Fe) NullExponentError!Fe { var buf: [Fe.encoded_bytes]u8 = undefined; e.toBytes(&buf, native_endian) catch unreachable; return self.powWithEncodedExponent(x, &buf, native_endian); } |
powPublic() Returns x^e (mod m), assuming that the exponent is public. The function remains constant time with respect to |
pub fn powPublic(self: Self, x: Fe, e: Fe) NullExponentError!Fe { var e_normalized = Fe{ .v = e.v.normalize() }; var buf_: [Fe.encoded_bytes]u8 = undefined; var buf = buf_[0 .. math.divCeil(usize, e_normalized.v.limbs_count() * t_bits, 8) catch unreachable]; e_normalized.toBytes(buf, .little) catch unreachable; const leading = @clz(e_normalized.v.limbs.get(e_normalized.v.limbs_count() - carry_bits)); buf = buf[0 .. buf.len - leading / 8]; return self.powWithEncodedPublicExponent(x, buf, .little); } |
powWithEncodedExponent() Returns x^e (mod m), with the exponent provided as a byte string. Exponents are usually small, so this function is faster than |
pub fn powWithEncodedExponent(self: Self, x: Fe, e: []const u8, endian: Endian) NullExponentError!Fe { return self.powWithEncodedExponentInternal(x, e, endian, false); } |
powWithEncodedPublicExponent() Returns x^e (mod m), the exponent being public and provided as a byte string. Exponents are usually small, so this function is faster than |
pub fn powWithEncodedPublicExponent(self: Self, x: Fe, e: []const u8, endian: Endian) NullExponentError!Fe { return self.powWithEncodedExponentInternal(x, e, endian, true); } }; } const ct = if (std.options.side_channels_mitigations == .none) ct_unprotected else ct_protected; const ct_protected = struct { // Returns x if on is true, otherwise y. fn select(on: bool, x: Limb, y: Limb) Limb { const mask = @as(Limb, 0) -% @intFromBool(on); return y ^ (mask & (y ^ x)); } // Compares two values in constant time. fn eql(x: anytype, y: @TypeOf(x)) bool { const c1 = @subWithOverflow(x, y)[1]; const c2 = @subWithOverflow(y, x)[1]; return @as(bool, @bitCast(1 - (c1 | c2))); } // Compares two big integers in constant time, returning true if x < y. fn limbsCmpLt(x: anytype, y: @TypeOf(x)) bool { assert(x.limbs_count() == y.limbs_count()); const x_limbs = x.limbs.constSlice(); const y_limbs = y.limbs.constSlice(); var c: u1 = 0; for (0..x.limbs_count()) |i| { c = @as(u1, @truncate((x_limbs[i] -% y_limbs[i] -% c) >> t_bits)); } return @as(bool, @bitCast(c)); } // Compares two big integers in constant time, returning true if x >= y. fn limbsCmpGeq(x: anytype, y: @TypeOf(x)) bool { return @as(bool, @bitCast(1 - @intFromBool(ct.limbsCmpLt(x, y)))); } // Multiplies two limbs and returns the result as a wide limb. fn mulWide(x: Limb, y: Limb) WideLimb { const half_bits = @typeInfo(Limb).Int.bits / 2; const Half = meta.Int(.unsigned, half_bits); const x0 = @as(Half, @truncate(x)); const x1 = @as(Half, @truncate(x >> half_bits)); const y0 = @as(Half, @truncate(y)); const y1 = @as(Half, @truncate(y >> half_bits)); const w0 = math.mulWide(Half, x0, y0); const t = math.mulWide(Half, x1, y0) + (w0 >> half_bits); var w1: Limb = @as(Half, @truncate(t)); const w2 = @as(Half, @truncate(t >> half_bits)); w1 += math.mulWide(Half, x0, y1); const hi = math.mulWide(Half, x1, y1) + w2 + (w1 >> half_bits); const lo = x *% y; return .{ .hi = hi, .lo = lo }; } }; const ct_unprotected = struct { // Returns x if on is true, otherwise y. fn select(on: bool, x: Limb, y: Limb) Limb { return if (on) x else y; } // Compares two values in constant time. fn eql(x: anytype, y: @TypeOf(x)) bool { return x == y; } // Compares two big integers in constant time, returning true if x < y. fn limbsCmpLt(x: anytype, y: @TypeOf(x)) bool { assert(x.limbs_count() == y.limbs_count()); const x_limbs = x.limbs.constSlice(); const y_limbs = y.limbs.constSlice(); var i = x.limbs_count(); while (i != 0) { i -= 1; if (x_limbs[i] != y_limbs[i]) { return x_limbs[i] < y_limbs[i]; } } return false; } // Compares two big integers in constant time, returning true if x >= y. fn limbsCmpGeq(x: anytype, y: @TypeOf(x)) bool { return !ct.limbsCmpLt(x, y); } // Multiplies two limbs and returns the result as a wide limb. fn mulWide(x: Limb, y: Limb) WideLimb { const wide = math.mulWide(Limb, x, y); return .{ .hi = @as(Limb, @truncate(wide >> @typeInfo(Limb).Int.bits)), .lo = @as(Limb, @truncate(wide)), }; } }; test { if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; const M = Modulus(256); const m = try M.fromPrimitive(u256, 3429938563481314093726330772853735541133072814650493833233); var x = try M.Fe.fromPrimitive(u256, m, 80169837251094269539116136208111827396136208141182357733); var y = try M.Fe.fromPrimitive(u256, m, 24620149608466364616251608466389896540098571); const x_ = try x.toPrimitive(u256); try testing.expect((try M.Fe.fromPrimitive(@TypeOf(x_), m, x_)).eql(x)); try testing.expectError(error.Overflow, x.toPrimitive(u50)); const bits = m.bits(); try testing.expectEqual(bits, 192); var x_y = m.mul(x, y); try testing.expectEqual(x_y.toPrimitive(u256), 1666576607955767413750776202132407807424848069716933450241); try m.toMontgomery(&x); x_y = m.mul(x, y); try testing.expectEqual(x_y.toPrimitive(u256), 1666576607955767413750776202132407807424848069716933450241); try m.fromMontgomery(&x); x = m.add(x, y); try testing.expectEqual(x.toPrimitive(u256), 80169837251118889688724602572728079004602598037722456304); x = m.sub(x, y); try testing.expectEqual(x.toPrimitive(u256), 80169837251094269539116136208111827396136208141182357733); const big = try Uint(512).fromPrimitive(u495, 77285373554113307281465049383342993856348131409372633077285373554113307281465049383323332333429938563481314093726330772853735541133072814650493833233); const reduced = m.reduce(big); try testing.expectEqual(reduced.toPrimitive(u495), 858047099884257670294681641776170038885500210968322054970); const x_pow_y = try m.powPublic(x, y); try testing.expectEqual(x_pow_y.toPrimitive(u256), 1631933139300737762906024873185789093007782131928298618473); try m.toMontgomery(&x); const x_pow_y2 = try m.powPublic(x, y); try m.fromMontgomery(&x); try testing.expect(x_pow_y2.eql(x_pow_y)); try testing.expectError(error.NullExponent, m.powPublic(x, m.zero)); try testing.expect(!x.isZero()); try testing.expect(!y.isZero()); try testing.expect(m.v.isOdd()); const x_sq = m.sq(x); const x_sq2 = m.mul(x, x); try testing.expect(x_sq.eql(x_sq2)); try m.toMontgomery(&x); const x_sq3 = m.sq(x); const x_sq4 = m.mul(x, x); try testing.expect(x_sq.eql(x_sq3)); try testing.expect(x_sq3.eql(x_sq4)); try m.fromMontgomery(&x); } |
Generated by zstd-browse2 on 2023-11-04 14:12:24 -0400. |