authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2026-09-07 21:07:26+02:00
committergravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2026-10-25 08:38:01+01:00
log00795888e851bd3c082a16cae8507d7cad7cc4ba
treefaf05bd91eade8d601677ccbc1cfa0b3789d2ce7
parent0f17c8c1e7fa214b8a563a73c88b8096359b2282

crypto.ff: add support for even moduli

Allow options to create a modulus. Right now, there's one option, to allow even moduli (starting at 2). Even moduli are disabled by default: even moduli can't use the Montgomery form, so multiplication and exponentiation are slower. The Option struct is there to future-proof the interface, as we can later use it to add support for blinding, etc.

1 files changed, 79 insertions(+), 16 deletions(-)

lib/std/crypto/ff.zig+79-16
......@@ -601,6 +601,12 @@ pub fn Modulus(comptime max_bits: comptime_int) type {
601601
602602 const FeUint = Fe.FeUint;
603603
604 pub const Options = struct {
605 /// Accept even moduli and treat their parity as public.
606 /// Disabled by default: with even moduli, we can't use Montgomery form, so multiplication is slower.
607 allow_even: bool = false,
608 };
609
604610 /// The neutral element.
605611 zero: Fe,
606612
......@@ -698,15 +704,51 @@ pub fn Modulus(comptime max_bits: comptime_int) type {
698704 /// Creates a new modulus from a primitive value.
699705 /// The modulus must be odd and larger than 2.
700706 pub fn fromPrimitive(comptime T: type, x: T) (InvalidModulusError || OverflowError)!Self {
701 comptime assert(@bitSizeOf(T) <= max_bits); // Primitive type is larger than the modulus type.
702 const v = try FeUint.fromPrimitive(T, x);
703 return try Self.fromUint(v);
707 return fromPrimitiveWithOptions(T, x, .{});
704708 }
705709
706710 /// Creates a new modulus from a byte string.
707711 pub fn fromBytes(bytes: []const u8, comptime endian: Endian) (InvalidModulusError || OverflowError)!Self {
712 return fromBytesWithOptions(bytes, endian, .{});
713 }
714
715 /// Creates a new modulus from a Uint value, with options.
716 pub fn fromUintWithOptions(v_: FeUint, options: Options) InvalidModulusError!Self {
717 if (v_.isOdd()) return fromUint(v_);
718 if (!options.allow_even) return error.EvenModulus;
719
720 const v = v_.normalize();
721 if (v.limbs_len < 2 and v.limbsConst()[0] < 2) {
722 return error.ModulusTooSmall;
723 }
724 const hi = v.limbsConst()[v.limbs_len - 1];
725
726 var m = initWithoutRR(v, @clz(hi) - carry_bits, 0);
727 m.rr = m.zero;
728
729 return m;
730 }
731
732 /// Creates a new big integer from a primitive type, with options.
733 pub fn fromPrimitiveWithOptions(comptime T: type, x: T, options: Options) (InvalidModulusError || OverflowError)!Self {
734 comptime assert(@bitSizeOf(T) <= max_bits);
735 const v = try FeUint.fromPrimitive(T, x);
736 return try Self.fromUintWithOptions(v, options);
737 }
738
739 /// Creates a new big integer from a byte array, with options.
740 pub fn fromBytesWithOptions(bytes: []const u8, comptime endian: Endian, options: Options) (InvalidModulusError || OverflowError)!Self {
708741 const v = try FeUint.fromBytes(bytes, endian);
709 return try Self.fromUint(v);
742 return try Self.fromUintWithOptions(v, options);
743 }
744
745 const ProductUint = Uint(2 * FeUint.capacity_bits);
746
747 fn fullProduct(a: []const Limb, b: []const Limb) ProductUint {
748 var out = ProductUint.zero;
749 out.limbs_len = a.len + b.len;
750 mulAddWide(out.limbs(), a, b);
751 return out;
710752 }
711753
712754 /// Serializes the modulus to a byte string.
......@@ -867,8 +909,9 @@ pub fn Modulus(comptime max_bits: comptime_int) type {
867909 }
868910
869911 /// Converts a field element to the Montgomery form.
912 /// Rejects even moduli, which have no Montgomery form.
870913 pub fn toMontgomery(self: Self, x: *Fe) RepresentationError!void {
871 if (x.montgomery) {
914 if (x.montgomery or !self.v.isOdd()) {
872915 return error.UnexpectedRepresentation;
873916 }
874917 self.shrink(x) catch unreachable;
......@@ -877,8 +920,9 @@ pub fn Modulus(comptime max_bits: comptime_int) type {
877920 }
878921
879922 /// Takes a field element out of the Montgomery form.
923 /// Rejects even moduli, which have no Montgomery form.
880924 pub fn fromMontgomery(self: Self, x: *Fe) RepresentationError!void {
881 if (!x.montgomery) {
925 if (!x.montgomery or !self.v.isOdd()) {
882926 return error.UnexpectedRepresentation;
883927 }
884928 self.shrink(x) catch unreachable;
......@@ -985,25 +1029,36 @@ pub fn Modulus(comptime max_bits: comptime_int) type {
9851029 return d;
9861030 }
9871031
1032 fn plainMul(self: Self, x: Fe, y: Fe) Fe {
1033 assert(x.limbs_count() == self.limbs_count());
1034 assert(y.limbs_count() == self.limbs_count());
1035 return self.reduce(fullProduct(x.v.limbsConst(), y.v.limbsConst()));
1036 }
1037
9881038 // Set `public` to `false` for secret exponents.
9891039 fn powWithEncodedExponentInternal(self: Self, x: Fe, e: []const u8, endian: Endian, comptime public: bool) NullExponentError!Fe {
9901040 var acc: u8 = 0;
9911041 for (e) |b| acc |= b;
9921042 if (acc == 0) return error.NullExponent;
9931043
1044 // Modulus parity is public; even moduli need plain arithmetic.
1045 if (!self.v.isOdd()) {
1046 return self.powLadder(x, e, endian, public, false);
1047 }
1048
9941049 var x_m = x;
9951050 if (!x.montgomery) {
9961051 self.toMontgomery(&x_m) catch unreachable;
9971052 }
998 var out = self.powLadder(x_m, e, endian, public);
1053 var out = self.powLadder(x_m, e, endian, public, true);
9991054 if (!x.montgomery) {
10001055 self.fromMontgomery(&out) catch unreachable;
10011056 }
10021057 return out;
10031058 }
10041059
1005 // `x` and the result use Montgomery form; the exponent must be nonzero.
1006 fn powLadder(self: Self, x: Fe, e: []const u8, endian: Endian, comptime public: bool) Fe {
1060 // `x` and the result use the representation selected by `montgomery`; the exponent must be nonzero.
1061 fn powLadder(self: Self, x: Fe, e: []const u8, endian: Endian, comptime public: bool, comptime montgomery: bool) Fe {
10071062 if (public and
10081063 (e.len < 3 or (e.len == 3 and e[if (endian == .big) 0 else 2] <= 0b1111)))
10091064 {
......@@ -1016,9 +1071,9 @@ pub fn Modulus(comptime max_bits: comptime_int) type {
10161071 var j = math.log2_int(u32, e_int);
10171072 while (j > 0) {
10181073 j -= 1;
1019 out = self.montgomerySq(out);
1074 out = if (montgomery) self.montgomerySq(out) else self.plainMul(out, out);
10201075 if ((e_int >> j) & 1 != 0) {
1021 out = self.montgomeryMul(out, x);
1076 out = if (montgomery) self.montgomeryMul(out, x) else self.plainMul(out, x);
10221077 }
10231078 }
10241079 return out;
......@@ -1026,18 +1081,20 @@ pub fn Modulus(comptime max_bits: comptime_int) type {
10261081
10271082 var pc: [15]Fe = [1]Fe{x} ++ @as([14]Fe, @splat(self.zero));
10281083 for (1..pc.len) |i| {
1029 pc[i] = self.montgomeryMul(pc[i - 1], pc[0]);
1084 pc[i] = if (montgomery) self.montgomeryMul(pc[i - 1], pc[0]) else self.plainMul(pc[i - 1], pc[0]);
10301085 }
10311086 var out = self.one();
1032 self.toMontgomery(&out) catch unreachable;
1087 if (montgomery) {
1088 self.toMontgomery(&out) catch unreachable;
1089 }
10331090
10341091 var t0 = self.zero;
1035 t0.montgomery = true;
1092 t0.montgomery = montgomery;
10361093 for (0..e.len) |s| {
10371094 const b = e[if (endian == .big) s else e.len - 1 - s];
10381095 for ([_]u3{ 4, 0 }) |j| {
10391096 for (0..4) |_| {
1040 out = self.montgomerySq(out);
1097 out = if (montgomery) self.montgomerySq(out) else self.plainMul(out, out);
10411098 }
10421099 const k = (b >> j) & 0b1111;
10431100 if (public or std.options.side_channels_mitigations == .none) {
......@@ -1048,7 +1105,7 @@ pub fn Modulus(comptime max_bits: comptime_int) type {
10481105 t0.v.cmov(ct.eql(k, @truncate(i + 1)), t.v);
10491106 }
10501107 }
1051 const t1 = self.montgomeryMul(out, t0);
1108 const t1 = if (montgomery) self.montgomeryMul(out, t0) else self.plainMul(out, t0);
10521109 if (public) {
10531110 @memcpy(out.v.limbs(), t1.v.limbsConst());
10541111 } else {
......@@ -1062,6 +1119,9 @@ pub fn Modulus(comptime max_bits: comptime_int) type {
10621119 /// Multiplies two field elements.
10631120 /// Result preserves the first operand's form.
10641121 pub fn mul(self: Self, x: Fe, y: Fe) Fe {
1122 if (!self.v.isOdd()) {
1123 return self.plainMul(x, y);
1124 }
10651125 if (x.montgomery) {
10661126 const y_ = if (!y.montgomery) blk: {
10671127 var yy = y;
......@@ -1086,6 +1146,9 @@ pub fn Modulus(comptime max_bits: comptime_int) type {
10861146
10871147 /// Squares a field element.
10881148 pub fn sq(self: Self, x: Fe) Fe {
1149 if (!self.v.isOdd()) {
1150 return self.plainMul(x, x);
1151 }
10891152 if (x.montgomery) {
10901153 return self.montgomerySq(x);
10911154 } else {