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
log0f17c8c1e7fa214b8a563a73c88b8096359b2282
tree3225ae6b89405c90b6d7f195e3e73665b8a0d7a7
parentad2225deb5b01eb751ba1882c0e9c2fc6b1fcafa

crypto.ff: optimize short public exponents

For short public exponents like the ones commonly used in RSA, just start at their highest set bit and skip leading zeros; no nned to build a table. Separate exponent decoding and representation conversion from the exp loop by the way.

1 files changed, 53 insertions(+), 80 deletions(-)

lib/std/crypto/ff.zig+53-80
......@@ -985,104 +985,77 @@ pub fn Modulus(comptime max_bits: comptime_int) type {
985985 return d;
986986 }
987987
988 // Returns x^e (mod m), with the exponent provided as a byte string.
989 // `public` must be set to `false` if the exponent it secret.
988 // Set `public` to `false` for secret exponents.
990989 fn powWithEncodedExponentInternal(self: Self, x: Fe, e: []const u8, endian: Endian, comptime public: bool) NullExponentError!Fe {
991990 var acc: u8 = 0;
992991 for (e) |b| acc |= b;
993992 if (acc == 0) return error.NullExponent;
994993
995 const was_montgomery = x.montgomery;
996
997 var out = self.one();
998 self.toMontgomery(&out) catch unreachable;
994 var x_m = x;
995 if (!x.montgomery) {
996 self.toMontgomery(&x_m) catch unreachable;
997 }
998 var out = self.powLadder(x_m, e, endian, public);
999 if (!x.montgomery) {
1000 self.fromMontgomery(&out) catch unreachable;
1001 }
1002 return out;
1003 }
9991004
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 {
10001007 if (public and
10011008 (e.len < 3 or (e.len == 3 and e[if (endian == .big) 0 else 2] <= 0b1111)))
10021009 {
10031010 // Do not use a precomputation table for short, public exponents
1004 var x_m = x;
1005 if (!x.montgomery) {
1006 self.toMontgomery(&x_m) catch unreachable;
1011 var e_int: u32 = 0;
1012 for (0..e.len) |i| {
1013 e_int = (e_int << 8) | e[if (endian == .big) i else e.len - 1 - i];
10071014 }
1008 var s = switch (endian) {
1009 .big => 0,
1010 .little => e.len - 1,
1011 };
1012 while (true) {
1013 const b = e[s];
1014 var j: u3 = 7;
1015 while (true) : (j -= 1) {
1016 out = self.montgomerySq(out);
1017 const k: u1 = @truncate(b >> j);
1018 if (k != 0) {
1019 const t = self.montgomeryMul(out, x_m);
1020 @memcpy(out.v.limbs(), t.v.limbsConst());
1021 }
1022 if (j == 0) break;
1023 }
1024 switch (endian) {
1025 .big => {
1026 s += 1;
1027 if (s == e.len) break;
1028 },
1029 .little => {
1030 if (s == 0) break;
1031 s -= 1;
1032 },
1015 var out = x;
1016 var j = math.log2_int(u32, e_int);
1017 while (j > 0) {
1018 j -= 1;
1019 out = self.montgomerySq(out);
1020 if ((e_int >> j) & 1 != 0) {
1021 out = self.montgomeryMul(out, x);
10331022 }
10341023 }
1035 } else {
1036 // Use a precomputation table for large exponents
1037 var pc: [15]Fe = [1]Fe{x} ++ @as([14]Fe, @splat(self.zero));
1038 if (!x.montgomery) {
1039 self.toMontgomery(&pc[0]) catch unreachable;
1040 }
1041 for (1..pc.len) |i| {
1042 pc[i] = self.montgomeryMul(pc[i - 1], pc[0]);
1043 }
1044 var t0 = self.zero;
1045 var s = switch (endian) {
1046 .big => 0,
1047 .little => e.len - 1,
1048 };
1049 while (true) {
1050 const b = e[s];
1051 for ([_]u3{ 4, 0 }) |j| {
1052 for (0..4) |_| {
1053 out = self.montgomerySq(out);
1054 }
1055 const k = (b >> j) & 0b1111;
1056 if (public or std.options.side_channels_mitigations == .none) {
1057 if (k == 0) continue;
1058 t0 = pc[k - 1];
1059 } else {
1060 for (pc, 0..) |t, i| {
1061 t0.v.cmov(ct.eql(k, @as(u8, @truncate(i + 1))), t.v);
1062 }
1063 }
1064 const t1 = self.montgomeryMul(out, t0);
1065 if (public) {
1066 @memcpy(out.v.limbs(), t1.v.limbsConst());
1067 } else {
1068 out.v.cmov(!ct.eql(k, 0), t1.v);
1024 return out;
1025 }
1026
1027 var pc: [15]Fe = [1]Fe{x} ++ @as([14]Fe, @splat(self.zero));
1028 for (1..pc.len) |i| {
1029 pc[i] = self.montgomeryMul(pc[i - 1], pc[0]);
1030 }
1031 var out = self.one();
1032 self.toMontgomery(&out) catch unreachable;
1033
1034 var t0 = self.zero;
1035 t0.montgomery = true;
1036 for (0..e.len) |s| {
1037 const b = e[if (endian == .big) s else e.len - 1 - s];
1038 for ([_]u3{ 4, 0 }) |j| {
1039 for (0..4) |_| {
1040 out = self.montgomerySq(out);
1041 }
1042 const k = (b >> j) & 0b1111;
1043 if (public or std.options.side_channels_mitigations == .none) {
1044 if (k == 0) continue;
1045 t0 = pc[k - 1];
1046 } else {
1047 for (pc, 0..) |t, i| {
1048 t0.v.cmov(ct.eql(k, @truncate(i + 1)), t.v);
10691049 }
10701050 }
1071 switch (endian) {
1072 .big => {
1073 s += 1;
1074 if (s == e.len) break;
1075 },
1076 .little => {
1077 if (s == 0) break;
1078 s -= 1;
1079 },
1051 const t1 = self.montgomeryMul(out, t0);
1052 if (public) {
1053 @memcpy(out.v.limbs(), t1.v.limbsConst());
1054 } else {
1055 out.v.cmov(!ct.eql(k, 0), t1.v);
10801056 }
10811057 }
10821058 }
1083 if (!was_montgomery) {
1084 self.fromMontgomery(&out) catch unreachable;
1085 }
10861059 return out;
10871060 }
10881061