authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2023-10-21 11:09:43+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-10-21 02:09:43-07:00
loga5c79c79983739630d7269f8583525d83ba9677b
treea1e29fca84b608a66f96fce1feb7d764e308a43a
parent54a4f24ea7c76297e97683501aef607b2a27cd16
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

crypto.ff: faster exponentiation with short/public exponents (#17617)

RSA exponents are typically 3 or 65537, and public. For those, we don't need to use conditional moves on the exponent, and precomputing a lookup table is not worth it. So, save a few cpu cycles and some memory for that common case. For safety, make `powWithEncodedExponent()` constant-time by default, and introduce a `powWithEncodedPublicExponent()` function for exponents that are assumed to be public. With `powWithEncodedPublicExponent()`, short (<= 36 bits) exponents will take the fast path.

1 files changed, 108 insertions(+), 50 deletions(-)

lib/std/crypto/ff.zig+108-50
...@@ -656,6 +656,101 @@ pub fn Modulus(comptime max_bits: comptime_int) type {...@@ -656,6 +656,101 @@ pub fn Modulus(comptime max_bits: comptime_int) type {
656 return d;656 return d;
657 }657 }
658658
659 // Returns x^e (mod m), with the exponent provided as a byte string.
660 // `public` must be set to `false` if the exponent it secret.
661 fn powWithEncodedExponentInternal(self: Self, x: Fe, e: []const u8, endian: builtin.Endian, comptime public: bool) NullExponentError!Fe {
662 var acc: u8 = 0;
663 for (e) |b| acc |= b;
664 if (acc == 0) return error.NullExponent;
665
666 var out = self.one();
667 self.toMontgomery(&out) catch unreachable;
668
669 if (public and e.len < 3 or (e.len == 3 and e[if (endian == .Big) 0 else 2] <= 0b1111)) {
670 // Do not use a precomputation table for short, public exponents
671 var x_m = x;
672 if (x.montgomery == false) {
673 self.toMontgomery(&x_m) catch unreachable;
674 }
675 var s = switch (endian) {
676 .Big => 0,
677 .Little => e.len - 1,
678 };
679 while (true) {
680 const b = e[s];
681 var j: u3 = 7;
682 while (true) : (j -= 1) {
683 out = self.montgomerySq(out);
684 const k: u1 = @truncate(b >> j);
685 if (k != 0) {
686 const t = self.montgomeryMul(out, x_m);
687 @memcpy(out.v.limbs.slice(), t.v.limbs.constSlice());
688 }
689 if (j == 0) break;
690 }
691 switch (endian) {
692 .Big => {
693 s += 1;
694 if (s == e.len) break;
695 },
696 .Little => {
697 if (s == 0) break;
698 s -= 1;
699 },
700 }
701 }
702 } else {
703 // Use a precomputation table for large exponents
704 var pc = [1]Fe{x} ++ [_]Fe{self.zero} ** 14;
705 if (x.montgomery == false) {
706 self.toMontgomery(&pc[0]) catch unreachable;
707 }
708 for (1..pc.len) |i| {
709 pc[i] = self.montgomeryMul(pc[i - 1], pc[0]);
710 }
711 var t0 = self.zero;
712 var s = switch (endian) {
713 .Big => 0,
714 .Little => e.len - 1,
715 };
716 while (true) {
717 const b = e[s];
718 for ([_]u3{ 4, 0 }) |j| {
719 for (0..4) |_| {
720 out = self.montgomerySq(out);
721 }
722 const k = (b >> j) & 0b1111;
723 if (public or std.options.side_channels_mitigations == .none) {
724 if (k == 0) continue;
725 t0 = pc[k - 1];
726 } else {
727 for (pc, 0..) |t, i| {
728 t0.v.cmov(ct.eql(k, @as(u8, @truncate(i + 1))), t.v);
729 }
730 }
731 const t1 = self.montgomeryMul(out, t0);
732 if (public) {
733 @memcpy(out.v.limbs.slice(), t1.v.limbs.constSlice());
734 } else {
735 out.v.cmov(!ct.eql(k, 0), t1.v);
736 }
737 }
738 switch (endian) {
739 .Big => {
740 s += 1;
741 if (s == e.len) break;
742 },
743 .Little => {
744 if (s == 0) break;
745 s -= 1;
746 },
747 }
748 }
749 }
750 self.fromMontgomery(&out) catch unreachable;
751 return out;
752 }
753
659 /// Multiplies two field elements.754 /// Multiplies two field elements.
660 pub fn mul(self: Self, x: Fe, y: Fe) Fe {755 pub fn mul(self: Self, x: Fe, y: Fe) Fe {
661 if (x.montgomery != y.montgomery) {756 if (x.montgomery != y.montgomery) {
...@@ -698,62 +793,25 @@ pub fn Modulus(comptime max_bits: comptime_int) type {...@@ -698,62 +793,25 @@ pub fn Modulus(comptime max_bits: comptime_int) type {
698 e_normalized.toBytes(buf, .Little) catch unreachable;793 e_normalized.toBytes(buf, .Little) catch unreachable;
699 const leading = @clz(e_normalized.v.limbs.get(e_normalized.v.limbs_count() - carry_bits));794 const leading = @clz(e_normalized.v.limbs.get(e_normalized.v.limbs_count() - carry_bits));
700 buf = buf[0 .. buf.len - leading / 8];795 buf = buf[0 .. buf.len - leading / 8];
701 return self.powWithEncodedExponent(x, buf, .Little);796 return self.powWithEncodedPublicExponent(x, buf, .Little);
702 }797 }
703798
704 /// Returns x^e (mod m), assuming that the exponent is public, and provided as a byte string.799 /// Returns x^e (mod m), with the exponent provided as a byte string.
705 /// Exponents are usually small, so this function is faster than `powPublic` as a field element800 /// Exponents are usually small, so this function is faster than `powPublic` as a field element
706 /// doesn't have to be created if a serialized representation is already available.801 /// doesn't have to be created if a serialized representation is already available.
802 ///
803 /// If the exponent is public, `powWithEncodedPublicExponent()` can be used instead for a slight speedup.
707 pub fn powWithEncodedExponent(self: Self, x: Fe, e: []const u8, endian: builtin.Endian) NullExponentError!Fe {804 pub fn powWithEncodedExponent(self: Self, x: Fe, e: []const u8, endian: builtin.Endian) NullExponentError!Fe {
708 var acc: u8 = 0;805 return self.powWithEncodedExponentInternal(x, e, endian, false);
709 for (e) |b| acc |= b;806 }
710 if (acc == 0) return error.NullExponent;
711807
712 var pc = [1]Fe{x} ++ [_]Fe{self.zero} ** 14;808 /// Returns x^e (mod m), the exponent being public and provided as a byte string.
713 if (x.montgomery == false) {809 /// Exponents are usually small, so this function is faster than `powPublic` as a field element
714 self.toMontgomery(&pc[0]) catch unreachable;810 /// doesn't have to be created if a serialized representation is already available.
715 }811 ///
716 for (1..pc.len) |i| {812 /// If the exponent is secret, `powWithEncodedExponent` must be used instead.
717 pc[i] = self.montgomeryMul(pc[i - 1], pc[0]);813 pub fn powWithEncodedPublicExponent(self: Self, x: Fe, e: []const u8, endian: builtin.Endian) NullExponentError!Fe {
718 }814 return self.powWithEncodedExponentInternal(x, e, endian, true);
719 var out = self.one();
720 self.toMontgomery(&out) catch unreachable;
721 var t0 = self.zero;
722 var s = switch (endian) {
723 .Big => 0,
724 .Little => e.len - 1,
725 };
726 while (true) {
727 const b = e[s];
728 for ([_]u3{ 4, 0 }) |j| {
729 for (0..4) |_| {
730 out = self.montgomerySq(out);
731 }
732 const k = (b >> j) & 0b1111;
733 if (std.options.side_channels_mitigations == .none) {
734 if (k == 0) continue;
735 t0 = pc[k - 1];
736 } else {
737 for (pc, 0..) |t, i| {
738 t0.v.cmov(ct.eql(k, @as(u8, @truncate(i + 1))), t.v);
739 }
740 }
741 const t1 = self.montgomeryMul(out, t0);
742 out.v.cmov(!ct.eql(k, 0), t1.v);
743 }
744 switch (endian) {
745 .Big => {
746 s += 1;
747 if (s == e.len) break;
748 },
749 .Little => {
750 if (s == 0) break;
751 s -= 1;
752 },
753 }
754 }
755 self.fromMontgomery(&out) catch unreachable;
756 return out;
757 }815 }
758 };816 };
759}817}