authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2026-09-07 21:03:12+02:00
committergravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2026-10-03 19:31:10+02:00
log12caf4e3de3b8fb54ce5dd705d50609dd05264b4
tree38cbfce865541c00ba9d7ef5cf6bd6b7b7301789
parent3c4cb8addcaefcd63d16845ef42471f7fe18acdb
signature Signed by SSH key SHA256:7B/LJ7bpR1eX8aCXSr4mtd5M45VMPKcx9zY8e95b5QM

crypto.ff: fix encoded exponent lengths

Use a buffer large enough for the field's full capacity. For exponentiation with secret exponents, always encode the exponents at the modulus public length, so that their length doesn't act as a side channel. And the public counterpart should use the exponent's public bit length to include every byte needed for its highest set bit.

1 files changed, 10 insertions(+), 9 deletions(-)

lib/std/crypto/ff.zig+10-9
......@@ -1031,14 +1031,18 @@ pub fn Modulus(comptime max_bits: comptime_int) type {
10311031 }
10321032 }
10331033
1034 const max_encoded_bytes = @divCeil(FeUint.capacity_bits, 8);
1035
10341036 /// Returns x^e (mod m) in constant time.
10351037 pub fn pow(self: Self, x: Fe, e: Fe) (NullExponentError || RepresentationError)!Fe {
10361038 if (e.montgomery) {
10371039 return error.UnexpectedRepresentation;
10381040 }
1039 var buf: [Fe.encoded_bytes]u8 = undefined;
1040 e.toBytes(&buf, native_endian) catch unreachable;
1041 return self.powWithEncodedExponent(x, &buf, native_endian);
1041
1042 var buf: [max_encoded_bytes]u8 = undefined;
1043 const len = self.encodedLen();
1044 e.toBytes(buf[0..len], native_endian) catch unreachable;
1045 return self.powWithEncodedExponent(x, buf[0..len], native_endian);
10421046 }
10431047
10441048 /// Returns x^e (mod m), assuming that the exponent is public.
......@@ -1047,12 +1051,9 @@ pub fn Modulus(comptime max_bits: comptime_int) type {
10471051 if (e.montgomery) {
10481052 return error.UnexpectedRepresentation;
10491053 }
1050 var e_normalized = Fe{ .v = e.v.normalize() };
1051 var buf_: [Fe.encoded_bytes]u8 = undefined;
1052 var buf = buf_[0..@divCeil(e_normalized.v.limbs_len * t_bits, 8)];
1053 e_normalized.toBytes(buf, .little) catch unreachable;
1054 const leading = @clz(e_normalized.v.limbsConst()[e_normalized.v.limbs_len - carry_bits]);
1055 buf = buf[0 .. buf.len - leading / 8];
1054 var buf_: [max_encoded_bytes]u8 = undefined;
1055 const buf = buf_[0..@divCeil(e.v.bitLenPublic(), 8)];
1056 e.toBytes(buf, .little) catch unreachable;
10561057 return self.powWithEncodedPublicExponent(x, buf, .little);
10571058 }
10581059