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