Add DILITHIUM_public_from_private function.

Change-Id: I9b9c53d4f994bd0fc731c9fff3119f0d388900e9
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/69667
Reviewed-by: Adam Langley <agl@google.com>
Commit-Queue: Adam Langley <agl@google.com>
diff --git a/crypto/dilithium/dilithium.c b/crypto/dilithium/dilithium.c
index 4f209bf..9630715 100644
--- a/crypto/dilithium/dilithium.c
+++ b/crypto/dilithium/dilithium.c
@@ -1214,6 +1214,48 @@
   return ret;
 }
 
+int DILITHIUM_public_from_private(
+    struct DILITHIUM_public_key *out_public_key,
+    const struct DILITHIUM_private_key *private_key) {
+  int ret = 0;
+
+  // Intermediate values, allocated on the heap to allow use when there is a
+  // limited amount of stack.
+  struct values_st {
+    matrix a_ntt;
+    vectorl s1_ntt;
+    vectork t;
+    vectork t0;
+  };
+  struct values_st *values = OPENSSL_malloc(sizeof(*values));
+  if (values == NULL) {
+    goto err;
+  }
+
+  const struct private_key *priv = private_key_from_external(private_key);
+  struct public_key *pub = public_key_from_external(out_public_key);
+
+  OPENSSL_memcpy(pub->rho, priv->rho, sizeof(pub->rho));
+  OPENSSL_memcpy(pub->public_key_hash, priv->public_key_hash,
+                 sizeof(pub->public_key_hash));
+
+  matrix_expand(&values->a_ntt, priv->rho);
+
+  OPENSSL_memcpy(&values->s1_ntt, &priv->s1, sizeof(values->s1_ntt));
+  vectorl_ntt(&values->s1_ntt);
+
+  matrix_mult(&values->t, &values->a_ntt, &values->s1_ntt);
+  vectork_inverse_ntt(&values->t);
+  vectork_add(&values->t, &values->t, &priv->s2);
+
+  vectork_power2_round(&pub->t1, &values->t0, &values->t);
+
+  ret = 1;
+err:
+  OPENSSL_free(values);
+  return ret;
+}
+
 // FIPS 204, Algorithm 2 (`ML-DSA.Sign`). Returns 1 on success and 0 on failure.
 static int dilithium_sign_with_randomizer(
     uint8_t out_encoded_signature[DILITHIUM_SIGNATURE_BYTES],
diff --git a/crypto/dilithium/dilithium_test.cc b/crypto/dilithium/dilithium_test.cc
index dcd2e8b..51cb295 100644
--- a/crypto/dilithium/dilithium_test.cc
+++ b/crypto/dilithium/dilithium_test.cc
@@ -92,6 +92,23 @@
             1);
 }
 
+TEST(DilithiumTest, PublicFromPrivateIsConsistent) {
+  std::vector<uint8_t> encoded_public_key(DILITHIUM_PUBLIC_KEY_BYTES);
+  auto priv = std::make_unique<DILITHIUM_private_key>();
+  EXPECT_TRUE(DILITHIUM_generate_key(encoded_public_key.data(), priv.get()));
+
+  auto pub = std::make_unique<DILITHIUM_public_key>();
+  EXPECT_TRUE(DILITHIUM_public_from_private(pub.get(), priv.get()));
+
+  std::vector<uint8_t> encoded_public_key2(DILITHIUM_PUBLIC_KEY_BYTES);
+
+  CBB cbb;
+  CBB_init_fixed(&cbb, encoded_public_key2.data(), encoded_public_key2.size());
+  ASSERT_TRUE(DILITHIUM_marshal_public_key(&cbb, pub.get()));
+
+  EXPECT_EQ(Bytes(encoded_public_key2), Bytes(encoded_public_key));
+}
+
 TEST(DilithiumTest, InvalidPublicKeyEncodingLength) {
   // Encode a public key with a trailing 0 at the end.
   std::vector<uint8_t> encoded_public_key(DILITHIUM_PUBLIC_KEY_BYTES + 1);
@@ -100,13 +117,13 @@
 
   // Public key is 1 byte too short.
   CBS cbs = bssl::MakeConstSpan(encoded_public_key)
-            .first(DILITHIUM_PUBLIC_KEY_BYTES - 1);
+                .first(DILITHIUM_PUBLIC_KEY_BYTES - 1);
   auto parsed_pub = std::make_unique<DILITHIUM_public_key>();
   EXPECT_FALSE(DILITHIUM_parse_public_key(parsed_pub.get(), &cbs));
 
   // Public key has the correct length.
-  cbs = bssl::MakeConstSpan(encoded_public_key)
-            .first(DILITHIUM_PUBLIC_KEY_BYTES);
+  cbs =
+      bssl::MakeConstSpan(encoded_public_key).first(DILITHIUM_PUBLIC_KEY_BYTES);
   EXPECT_TRUE(DILITHIUM_parse_public_key(parsed_pub.get(), &cbs));
 
   // Public key is 1 byte too long.
@@ -142,10 +159,6 @@
   EXPECT_FALSE(DILITHIUM_parse_private_key(parsed_priv.get(), &cbs));
 }
 
-// TODO: Add more parsing tests:
-// - signed values out of range (private key's s1/s2 beyond ETA)
-// - signature hints not in the canonical order (signature malleability)
-
 static void DilithiumFileTest(FileTest *t) {
   std::vector<uint8_t> seed, message, public_key_expected, private_key_expected,
       signed_message_expected;
diff --git a/include/openssl/experimental/dilithium.h b/include/openssl/experimental/dilithium.h
index 72e79bd..2273421 100644
--- a/include/openssl/experimental/dilithium.h
+++ b/include/openssl/experimental/dilithium.h
@@ -70,6 +70,12 @@
     uint8_t out_encoded_public_key[DILITHIUM_PUBLIC_KEY_BYTES],
     struct DILITHIUM_private_key *out_private_key);
 
+// DILITHIUM_public_from_private sets |*out_public_key| to the public key that
+// corresponds to |private_key|. Returns 1 on success and 0 on failure.
+OPENSSL_EXPORT int DILITHIUM_public_from_private(
+    struct DILITHIUM_public_key *out_public_key,
+    const struct DILITHIUM_private_key *private_key);
+
 // DILITHIUM_sign generates a signature for the message |msg| of length
 // |msg_len| using |private_key| following the randomized algorithm, and writes
 // the encoded signature to |out_encoded_signature|. Returns 1 on success and 0