[mask_rom, sig_verify] Add modular exponentiation

This change adds modular exponentiation using Montgomery multiplication
for exponents 3 and 65537.

Signed-off-by: Alphan Ulusoy <alphan@google.com>
diff --git a/sw/device/silicon_creator/mask_rom/rsa_verify.c b/sw/device/silicon_creator/mask_rom/rsa_verify.c
index 9974342..c98663f 100644
--- a/sw/device/silicon_creator/mask_rom/rsa_verify.c
+++ b/sw/device/silicon_creator/mask_rom/rsa_verify.c
@@ -26,6 +26,24 @@
   }
 }
 
+/**
+ * Checks if `a` is greater than or equal to `b`.
+ *
+ * @param a A `kRsaNumWords` long buffer, little-endian.
+ * @param b A `kRsaNumWords` long buffer, little-endian.
+ * @return Comparison result.
+ */
+static bool greater_equal(const uint32_t *a, const uint32_t *b) {
+  // TODO(#33): Hardening?
+  // Note: Loop terminates when `i` wraps around.
+  for (size_t i = kRsaNumWords - 1; i < kRsaNumWords; --i) {
+    if (a[i] != b[i]) {
+      return a[i] > b[i];
+    }
+  }
+  return true;
+}
+
 // FIXME: Merge this comment with the one in the header file.
 // This function implements Alg. 14.36 in Handbook of Applied Cryptography:
 // 1. result = 0
@@ -35,7 +53,7 @@
 // 3. If result >= m then result = result - m
 // 4. Return result
 void mont_mul(const uint32_t *x, const uint32_t *y, const uint32_t *m,
-              const uint32_t m_prime, uint32_t *result) {
+              const uint32_t m0_inv, uint32_t *result) {
   memset(result, 0, kRsaNumWords * sizeof(uint32_t));
 
   for (size_t i = 0; i < kRsaNumWords; ++i) {
@@ -50,7 +68,7 @@
 
     // Holds the sum of the first two addends in step 2.2.
     uint64_t acc0 = (uint64_t)x[i] * y[0] + result[0];
-    const uint32_t u_i = (uint32_t)acc0 * m_prime;
+    const uint32_t u_i = (uint32_t)acc0 * m0_inv;
     // Holds the sum of the all three addends in step 2.2.
     uint64_t acc1 = (uint64_t)u_i * m[0] + (uint32_t)acc0;
 
@@ -78,3 +96,39 @@
     }
   }
 }
+
+bool mod_exp(const uint32_t *sig, const rsa_verify_exponent_t e,
+             const uint32_t *r_square, const uint32_t *m, const uint32_t m0_inv,
+             uint32_t *result) {
+  uint32_t buf[kRsaNumWords];
+
+  if (e == kRsaVerifyExponent3) {
+    // result = sig * R mod m
+    mont_mul(sig, r_square, m, m0_inv, result);
+    // buf = sig^2 * R mod m
+    mont_mul(result, result, m, m0_inv, buf);
+  } else if (e == kRsaVerifyExponent65537) {
+    // buf = sig * R mod m
+    mont_mul(sig, r_square, m, m0_inv, buf);
+    for (size_t i = 0; i < 8; ++i) {
+      // result = sig^{2*i+1} * R mod m (sig's exponent: 2, 8, 32, ..., 32768)
+      mont_mul(buf, buf, m, m0_inv, result);
+      // buf = sig^{4*i+2} * R mod m (sig's exponent: 4, 16, 64, ..., 65536)
+      mont_mul(result, result, m, m0_inv, buf);
+    }
+  } else {
+    return false;
+  }
+  // result = sig^e mod m
+  mont_mul(buf, sig, m, m0_inv, result);
+
+  // We need this check because the result of `mont_mul` is not guaranteed to be
+  // the least non-negative residue. We need to subtract `m` from `result` at
+  // most once because  `m` is the modulus of an RSA public key and therefore
+  // R/2 < m < R.
+  if (greater_equal(result, m)) {
+    subtract(result, m);
+  }
+
+  return true;
+}
diff --git a/sw/device/silicon_creator/mask_rom/rsa_verify.h b/sw/device/silicon_creator/mask_rom/rsa_verify.h
index 1f818d2..f232703 100644
--- a/sw/device/silicon_creator/mask_rom/rsa_verify.h
+++ b/sw/device/silicon_creator/mask_rom/rsa_verify.h
@@ -5,6 +5,7 @@
 #ifndef OPENTITAN_SW_DEVICE_SILICON_CREATOR_MASK_ROM_RSA_VERIFY_H_
 #define OPENTITAN_SW_DEVICE_SILICON_CREATOR_MASK_ROM_RSA_VERIFY_H_
 
+#include <stdbool.h>
 #include <stdint.h>
 
 #ifdef __cplusplus
@@ -18,6 +19,23 @@
   kRsaNumWords = 96,
 };
 
+/**
+ * Exponent to use for signature verification.
+ *
+ * This determines the e in s^e mod n.
+ */
+// TODO(#22): May need to be updated after we decide on a key storage format.
+typedef enum rsa_verify_exponent {
+  /**
+   * e = 3.
+   */
+  kRsaVerifyExponent3,
+  /**
+   * e = 65537.
+   */
+  kRsaVerifyExponent65537,
+} rsa_verify_exponent_t;
+
 // FIXME: Make static and move this comment to the source file. This is here
 // just to be able to add a simple test.
 /**
@@ -34,11 +52,37 @@
  * @param x A `kRsaNumWords` long buffer, little-endian.
  * @param y A `kRsaNumWords` long buffer, little-endian.
  * @param m A `kRsaNumWords` long buffer, little-endian.
- * @param m_prime Negative of the multiplicative inverse of m modulo b.
+ * @param m0_inv Negative of the multiplicative inverse of m modulo b.
  * @param[out] result A `kRsaNumWords` long buffer, little-endian.
  */
 void mont_mul(const uint32_t *x, const uint32_t *y, const uint32_t *m,
-              uint32_t m_prime, uint32_t *result);
+              uint32_t m0_inv, uint32_t *result);
+
+/**
+ * Computes the modular exponentiation of an integer.
+ *
+ * Given sig, e, R^2 mod m, m, and m', this function computes sig^e mod m using
+ * Montgomery multiplication, where
+ * - sig, R^2 mod m, and m are integers with kRsaNumWords base b digits,
+ * - e is the exponent (3 or 65537),
+ * - m' = -m^-1 mod b,
+ * - R is b^kRsaNumWords, e.g. 2^3072 for RSA-3072, and
+ * - b is 2^32.
+ *
+ * @param sig A `kRsaNumWords` long buffer, little-endian.
+ * @param exponent Exponent to use for signature verification.
+ * @param r_square A `kRsaNumWords` long buffer, little-endian.
+ * @param m A `kRsaNumWords` long buffer, little-endian.
+ * @param m0_inv Negative of the multiplicative inverse of m modulo b.
+ * @param[out] result A `kRsaNumWords` long buffer, little-endian.
+ * @return True if successful, false otherwise.
+ */
+// TODO(#22): Update this after we decide on a key storage format.
+// FIXME: Error codes are still under discussion, update after we reach a
+// decision.
+bool mod_exp(const uint32_t *sig, rsa_verify_exponent_t e,
+             const uint32_t *r_square, const uint32_t *m, uint32_t m0_inv,
+             uint32_t *result);
 
 #ifdef __cplusplus
 }  // extern "C"
diff --git a/sw/device/tests/silicon_creator/mask_rom/rsa_verify_unittest.cc b/sw/device/tests/silicon_creator/mask_rom/rsa_verify_unittest.cc
index 45392e8..ff6753c 100644
--- a/sw/device/tests/silicon_creator/mask_rom/rsa_verify_unittest.cc
+++ b/sw/device/tests/silicon_creator/mask_rom/rsa_verify_unittest.cc
@@ -22,12 +22,12 @@
   constexpr RsaBuffer x{5792};
   constexpr RsaBuffer y{1229};
   constexpr RsaBuffer m{72639};
-  constexpr uint32_t m_prime = 3837733825;
+  constexpr uint32_t m0_inv = 3837733825;
   constexpr RsaBuffer exp_result{55123};
   // Uninitialized on purpose.
   RsaBuffer act_res;
 
-  mont_mul(x.data(), y.data(), m.data(), m_prime, act_res.data());
+  mont_mul(x.data(), y.data(), m.data(), m0_inv, act_res.data());
 
   EXPECT_EQ(exp_result, act_res);
 }
@@ -69,7 +69,7 @@
       0xa29d7536, 0x55c19326, 0x9ebbc63e, 0x20c75aee, 0xef6783d7, 0x59ffdba5,
       0x879b937b, 0x43a5c74c, 0x82b8f825, 0xfdf04b3a, 0x8fc62fbe, 0x114e6da5,
   };
-  constexpr uint32_t m_prime = 4036719071;
+  constexpr uint32_t m0_inv = 4036719071;
   constexpr RsaBuffer exp_result{
       0x7603d6bd, 0xe7714c93, 0x97cf4c0a, 0xa2f79f2b, 0xd6d9a47a, 0x666d8e1d,
       0x298e418a, 0xdbb713ec, 0x84d319fd, 0x1e89e6c3, 0xa4671f40, 0xc324a13b,
@@ -91,10 +91,222 @@
   // Uninitialized on purpose.
   RsaBuffer act_res;
 
-  mont_mul(sig.data(), sig.data(), m.data(), m_prime, act_res.data());
+  mont_mul(sig.data(), sig.data(), m.data(), m0_inv, act_res.data());
 
   EXPECT_EQ(exp_result, act_res);
 }
 
+TEST(ModExp, BadExp) {
+  RsaBuffer empty{};
+  rsa_verify_exponent_t bad_enum =
+      static_cast<rsa_verify_exponent_t>(kRsaVerifyExponent65537 + 1);
+
+  EXPECT_EQ(mod_exp(empty.data(), bad_enum, empty.data(), empty.data(), 0,
+                    empty.data()),
+            false);
+}
+
+TEST(ModExp, Exp65537) {
+  // Private key d: {
+  //     0xbebf06e3, 0x59d7e511, 0xe619ae0c, 0xb93ce7f6, 0xa83b89ca, 0x53c176f4,
+  //     0x869ef22b, 0xf8df4b83, 0x12ab013a, 0x98c927fc, 0xa4d57150, 0x0ce4696f,
+  //     0xdc931298, 0x2ced7d20, 0x973b5537, 0x2069a742, 0xf1559248, 0x13158c94,
+  //     0x16fef892, 0x82581bed, 0x15a7cba4, 0xc72a52e0, 0x7455bd70, 0x23eab41c,
+  //     0x0158da45, 0x82db6557, 0xec7bc157, 0x111b4eb0, 0x81c487dc, 0xf166df69,
+  //     0xde9a6b93, 0xedec8247, 0x6ddc889a, 0x5224cc0a, 0xce4b46fd, 0xde1e8772,
+  //     0x6417d968, 0x1d960144, 0x72dffbec, 0xd2619e50, 0x4ce73407, 0x25d7da23,
+  //     0xae72329a, 0xe43d4e29, 0x3e177edf, 0x70c58f2c, 0x8c9b7735, 0x514af916,
+  //     0x7e40bb19, 0xfab769ff, 0xdebb08e7, 0x6814f4f4, 0x352e82b3, 0x29c4d1b5,
+  //     0x61d485c8, 0x5cdb05a4, 0xcc6dd5cb, 0xd36a6e38, 0xf987ff5b, 0x2d060e8b,
+  //     0x84e66f62, 0xb0812761, 0x43414a06, 0xd21ff673, 0xa1317052, 0xf6f8cc01,
+  //     0x51c14c15, 0x1809b28f, 0x176965e5, 0x19b912be, 0x4097243f, 0xd7db29e3,
+  //     0xdf239b2c, 0xd08adbd2, 0xe25a3638, 0x2aac03ff, 0xa852eef0, 0xc41587cb,
+  //     0x8e20154b, 0x5232c192, 0x1d222a04, 0x5dedf724, 0x2fc54533, 0x3280b58b,
+  //     0x48e91ae9, 0x7748ae0c, 0x5a41dee3, 0xf5d82e3e, 0x85ff1006, 0x84bd1a55,
+  //     0x51f24349, 0x6f3e7cb3, 0x2cdaa80f, 0x947efef7, 0x07bffad6, 0x60417307,
+  // };
+  constexpr RsaBuffer m{
+      0x6a6a75e1, 0xa018ddc5, 0x687bb168, 0x8e8205a5, 0x7dbfffa7, 0xc8722ac5,
+      0xf84d21cf, 0xe1312531, 0x0ce3f8a3, 0xa825f988, 0x57f51964, 0xb27e206a,
+      0x8e1dd008, 0x1c4fb8d7, 0x824fb142, 0x1c8be7b3, 0x7b9d6366, 0xc56ad0f2,
+      0xef762d5b, 0x4b1431e3, 0x8ae28eb9, 0xd41db7aa, 0x43cccdf7, 0x91b74a84,
+      0x80183850, 0x30e74d0d, 0xb62ed015, 0x235574d2, 0x8c28f251, 0x4f40def2,
+      0x24e2efdb, 0x9ebd1ff2, 0xfa7b49ee, 0x2819a938, 0x6e66b8c8, 0x24e41546,
+      0x4d783a7c, 0xd2947d3d, 0x1ab269e9, 0xfad39f16, 0xaab78f7b, 0x49d8b510,
+      0x35bf0dfb, 0xeb274754, 0x069eccc9, 0xc13c437e, 0xe3bc0f60, 0xc9e0e12f,
+      0xc253ac43, 0x89c240e0, 0xc4aba4e5, 0xedf34bc0, 0x5402c462, 0x4021b0bd,
+      0x996b6241, 0xc3d9945f, 0xa137ac60, 0xf0250bf5, 0xc8c7100f, 0xb70d6b88,
+      0x78916a8c, 0x33370e5d, 0x3970dcb9, 0xaf4c58b4, 0x5f78cb0d, 0xb02d90b7,
+      0xeb6c3d05, 0x04afc71a, 0x45185f0f, 0x987caa5b, 0x33976249, 0x565afdbc,
+      0x80a85056, 0x59e07655, 0x9a29e77d, 0x7a8dfb7f, 0x782e0204, 0x4d6713ff,
+      0x131000ea, 0xe18e1206, 0x21f57f30, 0xf24f038b, 0x59cf874d, 0x24c50525,
+      0xb52f170d, 0x46c9adde, 0x90e82c73, 0x1344ceaf, 0x663209f2, 0x24bd4fbf,
+      0x5e4ed04d, 0x0fce770a, 0x81f78793, 0xa792e13e, 0xa6c7bf58, 0xe1df9be8,
+  };
+  constexpr uint32_t m0_inv = 4036719071;
+  constexpr RsaBuffer sig{
+      0xceb7e983, 0xe693b200, 0xf9153989, 0xcf899599, 0x1ec09fae, 0xf2f88007,
+      0x2a24eed5, 0x9c5b7c4e, 0x21a153b2, 0xaf7583ae, 0x04fdd694, 0x7550094b,
+      0xb2a69ac4, 0xe49d8022, 0x7ed6f162, 0x14bb3a1b, 0xbb29d8dd, 0x5c5815c2,
+      0x7a80d848, 0xb122f449, 0x59dca808, 0xbc1443e2, 0xe304ff93, 0xcc97ee4b,
+      0x42ef6b57, 0x1436839f, 0xae860b45, 0x6a843a17, 0x2381fb91, 0x09fd0635,
+      0xa431aac3, 0xd7220269, 0xdf3e2697, 0x35e2915e, 0xedba6956, 0x1d387448,
+      0x930006df, 0x961e5f00, 0xf2a7e960, 0x884e4add, 0x7dfe76b1, 0x4079aa79,
+      0x1f3a378d, 0x96c20697, 0x268aea57, 0x2c8569a4, 0x0474f512, 0x2388555c,
+      0x58679953, 0xe73da3a0, 0x43431b9a, 0x699f04d3, 0xfc0be066, 0xcce606f2,
+      0xd94cdfa0, 0x6c1ddca3, 0xe96c11f6, 0xfc635db4, 0x3bdb4f69, 0xa621c3e7,
+      0x9f292111, 0xb86e1e6b, 0xb74f923b, 0x592967a0, 0xc412097f, 0x8c1c8ca7,
+      0x494fcdb6, 0x87c5fe0f, 0x50c01aee, 0x8a26368e, 0xeaf12232, 0x7dade4d8,
+      0x39eb2ac6, 0x744f8aaa, 0xf34908ca, 0x1e0c656c, 0xe96d4e29, 0x8575d194,
+      0xe439bd31, 0xa74a77e3, 0x0f465b88, 0xf4e21152, 0x80400ad8, 0xe58501ec,
+      0xa29d7536, 0x55c19326, 0x9ebbc63e, 0x20c75aee, 0xef6783d7, 0x59ffdba5,
+      0x879b937b, 0x43a5c74c, 0x82b8f825, 0xfdf04b3a, 0x8fc62fbe, 0x114e6da5,
+  };
+  constexpr RsaBuffer r_square{
+      0xa3eb77fa, 0x9db9a2ac, 0x2c19d4ae, 0xfb5be1e7, 0xdd38f5fb, 0xd0f4fdda,
+      0xeb165cd3, 0x546a7cfe, 0xcd410c5c, 0x73f5cf6b, 0x1185bcae, 0xda2e2103,
+      0xbab5ae26, 0x76e77aba, 0xf49dd5f7, 0x32318a29, 0x689a85bc, 0x8aa862a9,
+      0x538c240e, 0xb61eab77, 0x9ccd73f2, 0x6563c81a, 0x6c65ac0e, 0x90b209bf,
+      0xe642e25e, 0x7e351549, 0x879a1830, 0xc75cbb02, 0xe0112362, 0xebc2405f,
+      0x01dc7990, 0x3d3d07f3, 0xc5b9a5be, 0x98d8cc33, 0xdd65e108, 0xce301343,
+      0x0dbdc0cb, 0xc204b9ca, 0xeabe1810, 0x9849163a, 0x234c8ff7, 0x9bc14e3b,
+      0x4b4c2226, 0x079883be, 0xba59c5f5, 0xd9c77317, 0x1ce689f5, 0x05f49af5,
+      0x7a83d42a, 0xc509b5ca, 0x0811a95f, 0x093520a2, 0x73649941, 0xd9691ef5,
+      0x6878ec0d, 0x4043add6, 0x7516d8b7, 0x5c7070ff, 0x4ce52e1d, 0xf209e123,
+      0xfe4319c4, 0x9774620a, 0x7a58d047, 0x524b09b7, 0x96cbf044, 0x2a9044a2,
+      0x514995dc, 0xe4b83ed6, 0xd21be300, 0x2966d4f8, 0xd9ee19c4, 0xb60788f6,
+      0xf8d074ab, 0xa7e13295, 0x93718edc, 0xba9fc096, 0x0ad2fbbc, 0x9fe0c363,
+      0x472a10b4, 0xda9c946b, 0x37276997, 0x04e452fc, 0xd19233b5, 0xa277ef0e,
+      0x49619ddd, 0xb5822d56, 0x6ca4d02f, 0x7d0c0fc3, 0xa29196e2, 0xb6988a4f,
+      0x785b7552, 0xeaee3c24, 0x87993424, 0xfcb49693, 0x21e64d84, 0x9e2dcea8,
+  };
+  constexpr RsaBuffer exp_res{
+      0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555,
+      0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555,
+      0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555,
+      0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555,
+      0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555,
+      0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555,
+      0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555,
+      0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555,
+      0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555,
+      0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555,
+      0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555,
+      0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555,
+      0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555,
+      0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555,
+      0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555,
+      0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555,
+  };
+  // Uninitialized on purpose.
+  RsaBuffer act_res;
+
+  EXPECT_EQ(mod_exp(sig.data(), kRsaVerifyExponent65537, r_square.data(),
+                    m.data(), m0_inv, act_res.data()),
+            true);
+  EXPECT_EQ(exp_res, act_res);
+}
+
+TEST(ModExp, Exp3) {
+  // Private key d: {
+  //     0x129c022b, 0x13e47956, 0xdfec2aca, 0xb814ecac, 0x72c55992, 0xfcf87ab5,
+  //     0x29091c15, 0xed118f4d, 0xcdf98cba, 0x8ce9d3c4, 0x5870f4aa, 0x1466b189,
+  //     0xf61cd198, 0x898e7523, 0xb083e2af, 0x41c9533c, 0xf3a3d236, 0xb85f886e,
+  //     0x3a3d6426, 0x310a128e, 0x6bd63e65, 0xa8b5a275, 0xb01bd973, 0x0bfd31a7,
+  //     0xc0860d67, 0x9c82b2e4, 0x58b5313d, 0x10a5cdbb, 0x4c8756e1, 0xde699fc1,
+  //     0x64b7ea15, 0x0de1a5ab, 0x5ec3810e, 0xcdab8036, 0x2914896e, 0x3fc10cdd,
+  //     0x029e6787, 0x8a9fb6ee, 0x114d4a63, 0x48e7fb42, 0x28593dc1, 0x72ae9ccf,
+  //     0x921ad67b, 0xcccc6d1e, 0x79956f79, 0x6bc6189e, 0x1cb13c91, 0x80c0e7c9,
+  //     0x0c6ea14b, 0xf631e8ac, 0x866f54e7, 0xe1771b9a, 0x57026f77, 0xa4068cb9,
+  //     0xde218576, 0x932e8df6, 0xd0641b5b, 0x65cc6e0f, 0x3fdaac0e, 0x9d036254,
+  //     0x74f8e2f2, 0x55245512, 0xa3626209, 0x8bdec1d9, 0x393ce8b3, 0xa013b9bb,
+  //     0xf9708b92, 0x62aa3401, 0x7eed6670, 0xcc084151, 0xc594ff05, 0x359ba561,
+  //     0x6735cc4a, 0xbba98282, 0x3dec3980, 0xe91eb975, 0x854dc579, 0x09c5dc6f,
+  //     0x660655d4, 0x46d4c3fc, 0x7b942f7c, 0x756586c2, 0x7057ce35, 0x2ca213c7,
+  //     0xd900854e, 0xb22aab06, 0xb8c816a0, 0x941aa9ae, 0x864e97a1, 0xf69ecc7d,
+  //     0x5a3b56bc, 0xca0180ca, 0x67012e82, 0x4f7ac33c, 0x391a1b40, 0x1e70f217,
+  // };
+  constexpr RsaBuffer m{
+      0x3489eab3, 0x23e3d222, 0xbe69ae96, 0xccc537fa, 0xf83c6774, 0xe683f91b,
+      0x9c588896, 0x44654dec, 0x96729ce1, 0x625a6410, 0x6435e750, 0xec42933f,
+      0x130c9e34, 0xeb1834b6, 0x2a117689, 0xb3b2f7bf, 0x21f92171, 0x4c1a1765,
+      0x9a92fe4b, 0xac9b5286, 0x0049c14f, 0x2531d556, 0x3f6e28ae, 0xe12124a6,
+      0x40b11690, 0xecfb1d29, 0x9af61602, 0x3d0f71b9, 0x1b9246cf, 0xbe254ff6,
+      0xb2003ba0, 0x440c6682, 0x251589aa, 0xb65e34bb, 0x3c17861f, 0xc24717a0,
+      0x35588598, 0xf8b64e80, 0x71cd790b, 0xa7ba410a, 0x98ece611, 0x3502cc9b,
+      0xc75ac49f, 0x415dd4ee, 0x657989ed, 0x784146ac, 0x3d6dada5, 0xb59e1a7a,
+      0x4a97c7c6, 0xc52b7408, 0x269bfd6f, 0x48caa59f, 0x0a0e9ccf, 0xd8274c58,
+      0x34c920c7, 0x731753c9, 0xe258a425, 0x62ca945e, 0x7f200856, 0xae144df9,
+      0xbdd551af, 0xfed9fe6e, 0xd44e4c37, 0x47388b19, 0x576d7435, 0xc0765a63,
+      0xd8a3456f, 0x4ffd380b, 0xf99066a2, 0xc83187e8, 0xa17dfa22, 0x41a5e04a,
+      0x6b42c9bd, 0x65f90f0e, 0x73895904, 0x76b858bf, 0x1fd2a0db, 0x3aa32a9d,
+      0x642602f8, 0xa8fc97ea, 0xe5791ce9, 0xc061288e, 0xa20ed540, 0x0bcc76ac,
+      0x16031fd5, 0x2d000229, 0x54b087c4, 0x789ffa18, 0x25d78dc9, 0xc7b8caf1,
+      0x1d64086d, 0xbc0904be, 0x6a071710, 0xdce0936a, 0x569ca381, 0xb6a5ac8b,
+  };
+  constexpr uint32_t m0_inv = 0x88df2b85;
+  constexpr RsaBuffer sig{
+      0xca60cb6e, 0xd3a786de, 0x37623afb, 0x7f6cbc1a, 0x5366d957, 0xe69ffe2f,
+      0x3560dc40, 0xf8b205c5, 0xa612764f, 0xb0415cf7, 0x5c5b87d9, 0xe0081c67,
+      0xadc8d9bd, 0xdd072b18, 0x8e22b48b, 0x758b9df3, 0xb208d5ab, 0x5f1bcb08,
+      0xb16f9e88, 0xf2d37daf, 0xf7fc6ecb, 0x5a102bb9, 0xe61a9b3f, 0x96541e3e,
+      0x3718d3ef, 0x769b35c2, 0xf571f77a, 0x82a6f325, 0xaa5ef30c, 0x17048840,
+      0xc71ddc21, 0xc7bc71ed, 0x14f7ed74, 0x7a3d6a80, 0xebb0c73d, 0x5f28b2c2,
+      0x56a502db, 0x39a3814f, 0x37df9ba4, 0x397e700d, 0x6c03a24d, 0x9efa0232,
+      0x257c4b77, 0xa928a03a, 0x43455edd, 0x57e509fb, 0x32458a00, 0x09941f22,
+      0xca9af629, 0x76a01068, 0x4c638cbe, 0x9b8a3ff6, 0x3979752d, 0x6d4c9bb8,
+      0x3ee6189c, 0xab9b7212, 0x2116de70, 0x429344c4, 0x072412c7, 0x747fff3d,
+      0x9d07cc3f, 0xb188d846, 0xcf4959ed, 0xc622387c, 0x32aeb26b, 0xf2921d28,
+      0x60793032, 0x061e4108, 0x6d70682b, 0x062ffa0b, 0x2b1f1696, 0x507a26f5,
+      0x1401059c, 0x202485a9, 0xfe963ae9, 0x54423a9d, 0x9731f9bb, 0x227e9788,
+      0x50846b54, 0x1e9f59f1, 0x3f158119, 0xc36f0b7b, 0xbef3b349, 0xf9172b6f,
+      0x3daf21c0, 0x819ee37e, 0x0bba9299, 0x90727884, 0xc74908f9, 0xec095a40,
+      0x8e2120dd, 0xfbefd497, 0x2227f721, 0xb7abdc98, 0xf7e55656, 0x3be75b5c,
+  };
+  constexpr RsaBuffer r_square{
+      0x3863b1be, 0x2913ebf7, 0x4fc03d0e, 0x482a3973, 0xa3b8f6a9, 0xf7473acb,
+      0xbf4bd684, 0xa22a9423, 0x8d2fa6f6, 0x7f1acc71, 0x718c39ed, 0xffeaa651,
+      0xba592ce4, 0xa05e4c98, 0x89d5b580, 0x7f394220, 0xd02c350b, 0x71692df1,
+      0xf896a5b7, 0x791e5249, 0x0c7bf245, 0x80c8c8b2, 0x8bb7ca49, 0xc91f3f3a,
+      0x223bc9b1, 0x22b07bc8, 0xce145fbc, 0xa04cd67e, 0x3d7999d4, 0xaac652fc,
+      0x70380397, 0x7ad5512a, 0x605df8bb, 0x6e694ebf, 0x498901cb, 0x6966203d,
+      0x34ae90b6, 0x0859c604, 0x16b2ebaa, 0x44d734e0, 0x39caa18e, 0x76d1d9a3,
+      0x985570c8, 0xbd4216e8, 0x6ee8a815, 0xf45f6b13, 0xcafa40e9, 0xb8884042,
+      0xcda1fc24, 0x34e64f10, 0xeee01c26, 0x6b42d326, 0xe26972cd, 0x71f6230c,
+      0x27febf17, 0xdafc774b, 0x75777ad5, 0x6e5c4c40, 0x7995b95e, 0x2465f6f7,
+      0x6783399a, 0xe571421a, 0x8faf084a, 0xd303dd08, 0x92cc413e, 0x1029f59f,
+      0x08bec8f5, 0x8d3ae156, 0xbe3b3cae, 0x4a52fd6a, 0xc9979f63, 0x66dc5646,
+      0x315f0f71, 0xf867366a, 0x809ce4b1, 0xb69b56d3, 0xb0b219d7, 0xede105a6,
+      0xfc70656a, 0x7f27b7bd, 0x74e0004b, 0xe8bb9577, 0x2df61aba, 0x4ef701ea,
+      0x2d1f4d03, 0x17961c80, 0x7b01bded, 0x852a41bb, 0x5e173716, 0xf48a5071,
+      0x3dba1742, 0xa3a5c9ae, 0xfd275d1c, 0x2f3937f9, 0x45579413, 0x2951778f,
+  };
+  constexpr RsaBuffer exp_res{
+      0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555,
+      0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555,
+      0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555,
+      0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555,
+      0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555,
+      0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555,
+      0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555,
+      0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555,
+      0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555,
+      0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555,
+      0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555,
+      0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555,
+      0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555,
+      0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555,
+      0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555,
+      0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555,
+  };
+  // Uninitialized on purpose.
+  RsaBuffer act_res;
+
+  EXPECT_EQ(mod_exp(sig.data(), kRsaVerifyExponent3, r_square.data(), m.data(),
+                    m0_inv, act_res.data()),
+            true);
+  EXPECT_EQ(exp_res, act_res);
+}
+
 }  // namespace
 }  // namespace rsa_verify_unittest