Make built-in curves static.

This replaces our dynamically creating singleton EC_GROUPs from curve
data with static EC_GROUP instances.

They're just shy of being fully static because delocate still forces us
go to through CRYPTO_once to initialize structures with pointers.
(Though, without delocate, the loader would need similar initialization
via a runtime relocation.)

This means we can now have functions like EC_group_p256(), analogous to
EVP_sha256(). These are infallible functions that return const EC_GROUP
pointers. Although there is an initial 2KiB hit to binary size (now we
precompute a few more Montgomery values), I'm hoping it'll eventually
help binaries that only use a few of the curves to drop the others. Also
it removes some locks used to initialize the static curve objects, as
well as removing an annoying error condition.

Bug: 20
Change-Id: Id051c5439f2b2fe2b09bf10964d656503ee27d9e
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/60931
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/crypto/ec_extra/ec_asn1.c b/crypto/ec_extra/ec_asn1.c
index 5c0dab1..1cd9799 100644
--- a/crypto/ec_extra/ec_asn1.c
+++ b/crypto/ec_extra/ec_asn1.c
@@ -72,6 +72,16 @@
 static const CBS_ASN1_TAG kPublicKeyTag =
     CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 1;
 
+// TODO(https://crbug.com/boringssl/497): Allow parsers to specify a list of
+// acceptable groups, so parsers don't have to pull in all four.
+typedef const EC_GROUP *(*ec_group_func)(void);
+static const ec_group_func kAllGroups[] = {
+    &EC_group_p224,
+    &EC_group_p256,
+    &EC_group_p384,
+    &EC_group_p521,
+};
+
 EC_KEY *EC_KEY_parse_private_key(CBS *cbs, const EC_GROUP *group) {
   CBS ec_private_key, private_key;
   uint64_t version;
@@ -244,9 +254,12 @@
 // kPrimeFieldOID is the encoding of 1.2.840.10045.1.1.
 static const uint8_t kPrimeField[] = {0x2a, 0x86, 0x48, 0xce, 0x3d, 0x01, 0x01};
 
-static int parse_explicit_prime_curve(CBS *in, CBS *out_prime, CBS *out_a,
-                                      CBS *out_b, CBS *out_base_x,
-                                      CBS *out_base_y, CBS *out_order) {
+struct explicit_prime_curve {
+  CBS prime, a, b, base_x, base_y, order;
+};
+
+static int parse_explicit_prime_curve(CBS *in,
+                                      struct explicit_prime_curve *out) {
   // See RFC 3279, section 2.3.5. Note that RFC 3279 calls this structure an
   // ECParameters while RFC 5480 calls it a SpecifiedECDomain.
   CBS params, field_id, field_type, curve, base, cofactor;
@@ -260,18 +273,18 @@
       CBS_len(&field_type) != sizeof(kPrimeField) ||
       OPENSSL_memcmp(CBS_data(&field_type), kPrimeField, sizeof(kPrimeField)) !=
           0 ||
-      !CBS_get_asn1(&field_id, out_prime, CBS_ASN1_INTEGER) ||
-      !CBS_is_unsigned_asn1_integer(out_prime) ||
+      !CBS_get_asn1(&field_id, &out->prime, CBS_ASN1_INTEGER) ||
+      !CBS_is_unsigned_asn1_integer(&out->prime) ||
       CBS_len(&field_id) != 0 ||
       !CBS_get_asn1(&params, &curve, CBS_ASN1_SEQUENCE) ||
-      !CBS_get_asn1(&curve, out_a, CBS_ASN1_OCTETSTRING) ||
-      !CBS_get_asn1(&curve, out_b, CBS_ASN1_OCTETSTRING) ||
+      !CBS_get_asn1(&curve, &out->a, CBS_ASN1_OCTETSTRING) ||
+      !CBS_get_asn1(&curve, &out->b, CBS_ASN1_OCTETSTRING) ||
       // |curve| has an optional BIT STRING seed which we ignore.
       !CBS_get_optional_asn1(&curve, NULL, NULL, CBS_ASN1_BITSTRING) ||
       CBS_len(&curve) != 0 ||
       !CBS_get_asn1(&params, &base, CBS_ASN1_OCTETSTRING) ||
-      !CBS_get_asn1(&params, out_order, CBS_ASN1_INTEGER) ||
-      !CBS_is_unsigned_asn1_integer(out_order) ||
+      !CBS_get_asn1(&params, &out->order, CBS_ASN1_INTEGER) ||
+      !CBS_is_unsigned_asn1_integer(&out->order) ||
       !CBS_get_optional_asn1(&params, &cofactor, &has_cofactor,
                              CBS_ASN1_INTEGER) ||
       CBS_len(&params) != 0) {
@@ -300,25 +313,33 @@
     return 0;
   }
   size_t field_len = CBS_len(&base) / 2;
-  CBS_init(out_base_x, CBS_data(&base), field_len);
-  CBS_init(out_base_y, CBS_data(&base) + field_len, field_len);
+  CBS_init(&out->base_x, CBS_data(&base), field_len);
+  CBS_init(&out->base_y, CBS_data(&base) + field_len, field_len);
 
   return 1;
 }
 
-// integers_equal returns one if |a| and |b| are equal, up to leading zeros, and
+// integers_equal returns one if |bytes| is a big-endian encoding of |bn|, and
 // zero otherwise.
-static int integers_equal(const CBS *a, const uint8_t *b, size_t b_len) {
-  // Remove leading zeros from |a| and |b|.
-  CBS a_copy = *a;
-  while (CBS_len(&a_copy) > 0 && CBS_data(&a_copy)[0] == 0) {
-    CBS_skip(&a_copy, 1);
+static int integers_equal(const CBS *bytes, const BIGNUM *bn) {
+  // Although, in SEC 1, Field-Element-to-Octet-String has a fixed width,
+  // OpenSSL mis-encodes the |a| and |b|, so we tolerate any number of leading
+  // zeros. (This matters for P-521 whose |b| has a leading 0.)
+  CBS copy = *bytes;
+  while (CBS_len(&copy) > 0 && CBS_data(&copy)[0] == 0) {
+    CBS_skip(&copy, 1);
   }
-  while (b_len > 0 && b[0] == 0) {
-    b++;
-    b_len--;
+
+  if (CBS_len(&copy) > EC_MAX_BYTES) {
+    return 0;
   }
-  return CBS_mem_equal(&a_copy, b, b_len);
+  uint8_t buf[EC_MAX_BYTES];
+  if (!BN_bn2bin_padded(buf, CBS_len(&copy), bn)) {
+    ERR_clear_error();
+    return 0;
+  }
+
+  return CBS_mem_equal(&copy, buf, CBS_len(&copy));
 }
 
 EC_GROUP *EC_KEY_parse_curve_name(CBS *cbs) {
@@ -329,13 +350,12 @@
   }
 
   // Look for a matching curve.
-  const struct built_in_curves *const curves = OPENSSL_built_in_curves();
-  for (size_t i = 0; i < OPENSSL_NUM_BUILT_IN_CURVES; i++) {
-    const struct built_in_curve *curve = &curves->curves[i];
-    if (CBS_len(&named_curve) == curve->oid_len &&
-        OPENSSL_memcmp(CBS_data(&named_curve), curve->oid, curve->oid_len) ==
-            0) {
-      return EC_GROUP_new_by_curve_name(curve->nid);
+  for (size_t i = 0; i < OPENSSL_ARRAY_SIZE(kAllGroups); i++) {
+    const EC_GROUP *group = kAllGroups[i]();
+    if (CBS_mem_equal(&named_curve, group->oid, group->oid_len)) {
+      // TODO(davidben): Remove unnecessary calls to |EC_GROUP_free| within the
+      // library.
+      return (EC_GROUP *)group;
     }
   }
 
@@ -344,25 +364,15 @@
 }
 
 int EC_KEY_marshal_curve_name(CBB *cbb, const EC_GROUP *group) {
-  int nid = EC_GROUP_get_curve_name(group);
-  if (nid == NID_undef) {
+  if (group->oid_len == 0) {
     OPENSSL_PUT_ERROR(EC, EC_R_UNKNOWN_GROUP);
     return 0;
   }
 
-  const struct built_in_curves *const curves = OPENSSL_built_in_curves();
-  for (size_t i = 0; i < OPENSSL_NUM_BUILT_IN_CURVES; i++) {
-    const struct built_in_curve *curve = &curves->curves[i];
-    if (curve->nid == nid) {
-      CBB child;
-      return CBB_add_asn1(cbb, &child, CBS_ASN1_OBJECT) &&
-             CBB_add_bytes(&child, curve->oid, curve->oid_len) &&
-             CBB_flush(cbb);
-    }
-  }
-
-  OPENSSL_PUT_ERROR(EC, EC_R_UNKNOWN_GROUP);
-  return 0;
+  CBB child;
+  return CBB_add_asn1(cbb, &child, CBS_ASN1_OBJECT) &&
+         CBB_add_bytes(&child, group->oid, group->oid_len) &&  //
+         CBB_flush(cbb);
 }
 
 EC_GROUP *EC_KEY_parse_parameters(CBS *cbs) {
@@ -374,34 +384,58 @@
   // of named curves.
   //
   // TODO(davidben): Remove support for this.
-  CBS prime, a, b, base_x, base_y, order;
-  if (!parse_explicit_prime_curve(cbs, &prime, &a, &b, &base_x, &base_y,
-                                  &order)) {
+  struct explicit_prime_curve curve;
+  if (!parse_explicit_prime_curve(cbs, &curve)) {
     return NULL;
   }
 
-  // Look for a matching prime curve.
-  const struct built_in_curves *const curves = OPENSSL_built_in_curves();
-  for (size_t i = 0; i < OPENSSL_NUM_BUILT_IN_CURVES; i++) {
-    const struct built_in_curve *curve = &curves->curves[i];
-    const unsigned param_len = curve->param_len;
-    // |curve->params| is ordered p, a, b, x, y, order, each component
-    // zero-padded up to the field length. Although SEC 1 states that the
-    // Field-Element-to-Octet-String conversion also pads, OpenSSL mis-encodes
-    // |a| and |b|, so this comparison must allow omitting leading zeros. (This
-    // is relevant for P-521 whose |b| has a leading 0.)
-    if (integers_equal(&prime, curve->params, param_len) &&
-        integers_equal(&a, curve->params + param_len, param_len) &&
-        integers_equal(&b, curve->params + param_len * 2, param_len) &&
-        integers_equal(&base_x, curve->params + param_len * 3, param_len) &&
-        integers_equal(&base_y, curve->params + param_len * 4, param_len) &&
-        integers_equal(&order, curve->params + param_len * 5, param_len)) {
-      return EC_GROUP_new_by_curve_name(curve->nid);
-    }
+  const EC_GROUP *ret = NULL;
+  BIGNUM *p = BN_new(), *a = BN_new(), *b = BN_new(), *x = BN_new(),
+         *y = BN_new();
+  if (p == NULL || a == NULL || b == NULL || x == NULL || y == NULL) {
+    goto err;
   }
 
-  OPENSSL_PUT_ERROR(EC, EC_R_UNKNOWN_GROUP);
-  return NULL;
+  for (size_t i = 0; i < OPENSSL_ARRAY_SIZE(kAllGroups); i++) {
+    const EC_GROUP *group = kAllGroups[i]();
+    if (!integers_equal(&curve.order, EC_GROUP_get0_order(group))) {
+      continue;
+    }
+
+    // The order alone uniquely identifies the group, but we check the other
+    // parameters to avoid misinterpreting the group.
+    if (!EC_GROUP_get_curve_GFp(group, p, a, b, NULL)) {
+      goto err;
+    }
+    if (!integers_equal(&curve.prime, p) || !integers_equal(&curve.a, a) ||
+        !integers_equal(&curve.b, b)) {
+      break;
+    }
+    if (!EC_POINT_get_affine_coordinates_GFp(
+            group, EC_GROUP_get0_generator(group), x, y, NULL)) {
+      goto err;
+    }
+    if (!integers_equal(&curve.base_x, x) ||
+        !integers_equal(&curve.base_y, y)) {
+      break;
+    }
+    ret = group;
+    break;
+  }
+
+  if (ret == NULL) {
+    OPENSSL_PUT_ERROR(EC, EC_R_UNKNOWN_GROUP);
+  }
+
+err:
+  BN_free(p);
+  BN_free(a);
+  BN_free(b);
+  BN_free(x);
+  BN_free(y);
+  // TODO(davidben): Remove unnecessary calls to |EC_GROUP_free| within the
+  // library.
+  return (EC_GROUP *)ret;
 }
 
 int EC_POINT_point2cbb(CBB *out, const EC_GROUP *group, const EC_POINT *point,
@@ -532,3 +566,16 @@
   // Historically, this function used the wrong return value on error.
   return ret > 0 ? ret : 0;
 }
+
+size_t EC_get_builtin_curves(EC_builtin_curve *out_curves,
+                             size_t max_num_curves) {
+  if (max_num_curves > OPENSSL_ARRAY_SIZE(kAllGroups)) {
+    max_num_curves = OPENSSL_ARRAY_SIZE(kAllGroups);
+  }
+  for (size_t i = 0; i < max_num_curves; i++) {
+    const EC_GROUP *group = kAllGroups[i]();
+    out_curves[i].nid = group->curve_name;
+    out_curves[i].comment = group->comment;
+  }
+  return OPENSSL_ARRAY_SIZE(kAllGroups);
+}
diff --git a/crypto/fipsmodule/bcm.c b/crypto/fipsmodule/bcm.c
index 8231eee..9d9227d 100644
--- a/crypto/fipsmodule/bcm.c
+++ b/crypto/fipsmodule/bcm.c
@@ -211,7 +211,7 @@
 #endif
 
   assert_within(rodata_start, kPrimes, rodata_end);
-  assert_within(rodata_start, kP256Params, rodata_end);
+  assert_within(rodata_start, kP256Field, rodata_end);
   assert_within(rodata_start, kPKCS1SigPrefixes, rodata_end);
 
   uint8_t result[SHA256_DIGEST_LENGTH];
diff --git a/crypto/fipsmodule/bn/rsaz_exp.c b/crypto/fipsmodule/bn/rsaz_exp.c
index da25030..436055e 100644
--- a/crypto/fipsmodule/bn/rsaz_exp.c
+++ b/crypto/fipsmodule/bn/rsaz_exp.c
@@ -24,13 +24,13 @@
 #include "../../internal.h"
 
 
-// one is 1 in RSAZ's representation.
-alignas(64) static const BN_ULONG one[40] = {
+// rsaz_one is 1 in RSAZ's representation.
+alignas(64) static const BN_ULONG rsaz_one[40] = {
     1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-// two80 is 2^80 in RSAZ's representation. Note RSAZ uses base 2^29, so this is
+// rsaz_two80 is 2^80 in RSAZ's representation. Note RSAZ uses base 2^29, so this is
 // 2^(29*2 + 22) = 2^80, not 2^(64*2 + 22).
-alignas(64) static const BN_ULONG two80[40] = {
+alignas(64) static const BN_ULONG rsaz_two80[40] = {
     0, 0, 1 << 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0,       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
 
@@ -64,12 +64,12 @@
   // giving R = 2^(36*29) = 2^1044.
   rsaz_1024_mul_avx2(R2, R2, R2, m, k0);
   // R2 = 2^2048 * 2^2048 / 2^1044 = 2^3052
-  rsaz_1024_mul_avx2(R2, R2, two80, m, k0);
+  rsaz_1024_mul_avx2(R2, R2, rsaz_two80, m, k0);
   // R2 = 2^3052 * 2^80 / 2^1044 = 2^2088 = (2^1044)^2
 
   // table[0] = 1
   // table[1] = a_inv^1
-  rsaz_1024_mul_avx2(result, R2, one, m, k0);
+  rsaz_1024_mul_avx2(result, R2, rsaz_one, m, k0);
   rsaz_1024_mul_avx2(a_inv, a_inv, R2, m, k0);
   rsaz_1024_scatter5_avx2(table_s, result, 0);
   rsaz_1024_scatter5_avx2(table_s, a_inv, 1);
@@ -125,7 +125,7 @@
   rsaz_1024_mul_avx2(result, result, a_inv, m, k0);
 
   // Convert from Montgomery.
-  rsaz_1024_mul_avx2(result, result, one, m, k0);
+  rsaz_1024_mul_avx2(result, result, rsaz_one, m, k0);
 
   rsaz_1024_red2norm_avx2(result_norm, result);
   BN_ULONG scratch[16];
diff --git a/crypto/fipsmodule/ec/builtin_curves.h b/crypto/fipsmodule/ec/builtin_curves.h
new file mode 100644
index 0000000..0b489ab
--- /dev/null
+++ b/crypto/fipsmodule/ec/builtin_curves.h
@@ -0,0 +1,277 @@
+/* Copyright (c) 2023, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+// This file is generated by make_tables.go.
+
+// P-224
+OPENSSL_UNUSED static const uint64_t kP224FieldN0 = 0xffffffffffffffff;
+OPENSSL_UNUSED static const uint64_t kP224OrderN0 = 0xd6e242706a1fc2eb;
+#if defined(OPENSSL_64_BIT)
+OPENSSL_UNUSED static const uint64_t kP224Field[] = {
+    0x0000000000000001, 0xffffffff00000000, 0xffffffffffffffff,
+    0x00000000ffffffff};
+OPENSSL_UNUSED static const uint64_t kP224Order[] = {
+    0x13dd29455c5c2a3d, 0xffff16a2e0b8f03e, 0xffffffffffffffff,
+    0x00000000ffffffff};
+OPENSSL_UNUSED static const uint64_t kP224B[] = {
+    0x270b39432355ffb4, 0x5044b0b7d7bfd8ba, 0x0c04b3abf5413256,
+    0x00000000b4050a85};
+OPENSSL_UNUSED static const uint64_t kP224GX[] = {
+    0x343280d6115c1d21, 0x4a03c1d356c21122, 0x6bb4bf7f321390b9,
+    0x00000000b70e0cbd};
+OPENSSL_UNUSED static const uint64_t kP224GY[] = {
+    0x44d5819985007e34, 0xcd4375a05a074764, 0xb5f723fb4c22dfe6,
+    0x00000000bd376388};
+OPENSSL_UNUSED static const uint64_t kP224FieldR[] = {
+    0xffffffff00000000, 0xffffffffffffffff, 0x0000000000000000,
+    0x0000000000000000};
+OPENSSL_UNUSED static const uint64_t kP224FieldRR[] = {
+    0xffffffff00000001, 0xffffffff00000000, 0xfffffffe00000000,
+    0x00000000ffffffff};
+OPENSSL_UNUSED static const uint64_t kP224OrderRR[] = {
+    0x29947a695f517d15, 0xabc8ff5931d63f4b, 0x6ad15f7cd9714856,
+    0x00000000b1e97961};
+OPENSSL_UNUSED static const uint64_t kP224MontB[] = {
+    0xe768cdf663c059cd, 0x107ac2f3ccf01310, 0x3dceba98c8528151,
+    0x000000007fc02f93};
+OPENSSL_UNUSED static const uint64_t kP224MontGX[] = {
+    0xbc9052266d0a4aea, 0x852597366018bfaa, 0x6dd3af9bf96bec05,
+    0x00000000a21b5e60};
+OPENSSL_UNUSED static const uint64_t kP224MontGY[] = {
+    0x2edca1e5eff3ede8, 0xf8cd672b05335a6b, 0xaea9c5ae03dfe878,
+    0x00000000614786f1};
+#elif defined(OPENSSL_32_BIT)
+OPENSSL_UNUSED static const uint32_t kP224Field[] = {
+    0x00000001, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff,
+    0xffffffff};
+OPENSSL_UNUSED static const uint32_t kP224Order[] = {
+    0x5c5c2a3d, 0x13dd2945, 0xe0b8f03e, 0xffff16a2, 0xffffffff, 0xffffffff,
+    0xffffffff};
+OPENSSL_UNUSED static const uint32_t kP224B[] = {
+    0x2355ffb4, 0x270b3943, 0xd7bfd8ba, 0x5044b0b7, 0xf5413256, 0x0c04b3ab,
+    0xb4050a85};
+OPENSSL_UNUSED static const uint32_t kP224GX[] = {
+    0x115c1d21, 0x343280d6, 0x56c21122, 0x4a03c1d3, 0x321390b9, 0x6bb4bf7f,
+    0xb70e0cbd};
+OPENSSL_UNUSED static const uint32_t kP224GY[] = {
+    0x85007e34, 0x44d58199, 0x5a074764, 0xcd4375a0, 0x4c22dfe6, 0xb5f723fb,
+    0xbd376388};
+OPENSSL_UNUSED static const uint32_t kP224FieldR[] = {
+    0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000,
+    0x00000000};
+OPENSSL_UNUSED static const uint32_t kP224FieldRR[] = {
+    0x00000001, 0x00000000, 0x00000000, 0xfffffffe, 0xffffffff, 0xffffffff,
+    0x00000000};
+OPENSSL_UNUSED static const uint32_t kP224OrderRR[] = {
+    0x3ad01289, 0x6bdaae6c, 0x97a54552, 0x6ad09d91, 0xb1e97961, 0x1822bc47,
+    0xd4baa4cf};
+OPENSSL_UNUSED static const uint32_t kP224MontB[] = {
+    0xe768cdf7, 0xccf01310, 0x743b1cc0, 0xc8528150, 0x3dceba98, 0x7fc02f93,
+    0x9c3fa633};
+OPENSSL_UNUSED static const uint32_t kP224MontGX[] = {
+    0xbc905227, 0x6018bfaa, 0xf22fe220, 0xf96bec04, 0x6dd3af9b, 0xa21b5e60,
+    0x92f5b516};
+OPENSSL_UNUSED static const uint32_t kP224MontGY[] = {
+    0x2edca1e6, 0x05335a6b, 0xe8c15513, 0x03dfe878, 0xaea9c5ae, 0x614786f1,
+    0x100c1218};
+#else
+#error "unknown word size"
+#endif
+
+// P-256
+OPENSSL_UNUSED static const uint64_t kP256FieldN0 = 0x0000000000000001;
+OPENSSL_UNUSED static const uint64_t kP256OrderN0 = 0xccd1c8aaee00bc4f;
+#if defined(OPENSSL_64_BIT)
+OPENSSL_UNUSED static const uint64_t kP256Field[] = {
+    0xffffffffffffffff, 0x00000000ffffffff, 0x0000000000000000,
+    0xffffffff00000001};
+OPENSSL_UNUSED static const uint64_t kP256Order[] = {
+    0xf3b9cac2fc632551, 0xbce6faada7179e84, 0xffffffffffffffff,
+    0xffffffff00000000};
+OPENSSL_UNUSED static const uint64_t kP256FieldR[] = {
+    0x0000000000000001, 0xffffffff00000000, 0xffffffffffffffff,
+    0x00000000fffffffe};
+OPENSSL_UNUSED static const uint64_t kP256FieldRR[] = {
+    0x0000000000000003, 0xfffffffbffffffff, 0xfffffffffffffffe,
+    0x00000004fffffffd};
+OPENSSL_UNUSED static const uint64_t kP256OrderRR[] = {
+    0x83244c95be79eea2, 0x4699799c49bd6fa6, 0x2845b2392b6bec59,
+    0x66e12d94f3d95620};
+OPENSSL_UNUSED static const uint64_t kP256MontB[] = {
+    0xd89cdf6229c4bddf, 0xacf005cd78843090, 0xe5a220abf7212ed6,
+    0xdc30061d04874834};
+OPENSSL_UNUSED static const uint64_t kP256MontGX[] = {
+    0x79e730d418a9143c, 0x75ba95fc5fedb601, 0x79fb732b77622510,
+    0x18905f76a53755c6};
+OPENSSL_UNUSED static const uint64_t kP256MontGY[] = {
+    0xddf25357ce95560a, 0x8b4ab8e4ba19e45c, 0xd2e88688dd21f325,
+    0x8571ff1825885d85};
+#elif defined(OPENSSL_32_BIT)
+OPENSSL_UNUSED static const uint32_t kP256Field[] = {
+    0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000,
+    0x00000001, 0xffffffff};
+OPENSSL_UNUSED static const uint32_t kP256Order[] = {
+    0xfc632551, 0xf3b9cac2, 0xa7179e84, 0xbce6faad, 0xffffffff, 0xffffffff,
+    0x00000000, 0xffffffff};
+OPENSSL_UNUSED static const uint32_t kP256FieldR[] = {
+    0x00000001, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff,
+    0xfffffffe, 0x00000000};
+OPENSSL_UNUSED static const uint32_t kP256FieldRR[] = {
+    0x00000003, 0x00000000, 0xffffffff, 0xfffffffb, 0xfffffffe, 0xffffffff,
+    0xfffffffd, 0x00000004};
+OPENSSL_UNUSED static const uint32_t kP256OrderRR[] = {
+    0xbe79eea2, 0x83244c95, 0x49bd6fa6, 0x4699799c, 0x2b6bec59, 0x2845b239,
+    0xf3d95620, 0x66e12d94};
+OPENSSL_UNUSED static const uint32_t kP256MontB[] = {
+    0x29c4bddf, 0xd89cdf62, 0x78843090, 0xacf005cd, 0xf7212ed6, 0xe5a220ab,
+    0x04874834, 0xdc30061d};
+OPENSSL_UNUSED static const uint32_t kP256MontGX[] = {
+    0x18a9143c, 0x79e730d4, 0x5fedb601, 0x75ba95fc, 0x77622510, 0x79fb732b,
+    0xa53755c6, 0x18905f76};
+OPENSSL_UNUSED static const uint32_t kP256MontGY[] = {
+    0xce95560a, 0xddf25357, 0xba19e45c, 0x8b4ab8e4, 0xdd21f325, 0xd2e88688,
+    0x25885d85, 0x8571ff18};
+#else
+#error "unknown word size"
+#endif
+
+// P-384
+OPENSSL_UNUSED static const uint64_t kP384FieldN0 = 0x0000000100000001;
+OPENSSL_UNUSED static const uint64_t kP384OrderN0 = 0x6ed46089e88fdc45;
+#if defined(OPENSSL_64_BIT)
+OPENSSL_UNUSED static const uint64_t kP384Field[] = {
+    0x00000000ffffffff, 0xffffffff00000000, 0xfffffffffffffffe,
+    0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff};
+OPENSSL_UNUSED static const uint64_t kP384Order[] = {
+    0xecec196accc52973, 0x581a0db248b0a77a, 0xc7634d81f4372ddf,
+    0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff};
+OPENSSL_UNUSED static const uint64_t kP384FieldR[] = {
+    0xffffffff00000001, 0x00000000ffffffff, 0x0000000000000001,
+    0x0000000000000000, 0x0000000000000000, 0x0000000000000000};
+OPENSSL_UNUSED static const uint64_t kP384FieldRR[] = {
+    0xfffffffe00000001, 0x0000000200000000, 0xfffffffe00000000,
+    0x0000000200000000, 0x0000000000000001, 0x0000000000000000};
+OPENSSL_UNUSED static const uint64_t kP384OrderRR[] = {
+    0x2d319b2419b409a9, 0xff3d81e5df1aa419, 0xbc3e483afcb82947,
+    0xd40d49174aab1cc5, 0x3fb05b7a28266895, 0x0c84ee012b39bf21};
+OPENSSL_UNUSED static const uint64_t kP384MontB[] = {
+    0x081188719d412dcc, 0xf729add87a4c32ec, 0x77f2209b1920022e,
+    0xe3374bee94938ae2, 0xb62b21f41f022094, 0xcd08114b604fbff9};
+OPENSSL_UNUSED static const uint64_t kP384MontGX[] = {
+    0x3dd0756649c0b528, 0x20e378e2a0d6ce38, 0x879c3afc541b4d6e,
+    0x6454868459a30eff, 0x812ff723614ede2b, 0x4d3aadc2299e1513};
+OPENSSL_UNUSED static const uint64_t kP384MontGY[] = {
+    0x23043dad4b03a4fe, 0xa1bfa8bf7bb4a9ac, 0x8bade7562e83b050,
+    0xc6c3521968f4ffd9, 0xdd8002263969a840, 0x2b78abc25a15c5e9};
+#elif defined(OPENSSL_32_BIT)
+OPENSSL_UNUSED static const uint32_t kP384Field[] = {
+    0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xfffffffe, 0xffffffff,
+    0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff};
+OPENSSL_UNUSED static const uint32_t kP384Order[] = {
+    0xccc52973, 0xecec196a, 0x48b0a77a, 0x581a0db2, 0xf4372ddf, 0xc7634d81,
+    0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff};
+OPENSSL_UNUSED static const uint32_t kP384FieldR[] = {
+    0x00000001, 0xffffffff, 0xffffffff, 0x00000000, 0x00000001, 0x00000000,
+    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000};
+OPENSSL_UNUSED static const uint32_t kP384FieldRR[] = {
+    0x00000001, 0xfffffffe, 0x00000000, 0x00000002, 0x00000000, 0xfffffffe,
+    0x00000000, 0x00000002, 0x00000001, 0x00000000, 0x00000000, 0x00000000};
+OPENSSL_UNUSED static const uint32_t kP384OrderRR[] = {
+    0x19b409a9, 0x2d319b24, 0xdf1aa419, 0xff3d81e5, 0xfcb82947, 0xbc3e483a,
+    0x4aab1cc5, 0xd40d4917, 0x28266895, 0x3fb05b7a, 0x2b39bf21, 0x0c84ee01};
+OPENSSL_UNUSED static const uint32_t kP384MontB[] = {
+    0x9d412dcc, 0x08118871, 0x7a4c32ec, 0xf729add8, 0x1920022e, 0x77f2209b,
+    0x94938ae2, 0xe3374bee, 0x1f022094, 0xb62b21f4, 0x604fbff9, 0xcd08114b};
+OPENSSL_UNUSED static const uint32_t kP384MontGX[] = {
+    0x49c0b528, 0x3dd07566, 0xa0d6ce38, 0x20e378e2, 0x541b4d6e, 0x879c3afc,
+    0x59a30eff, 0x64548684, 0x614ede2b, 0x812ff723, 0x299e1513, 0x4d3aadc2};
+OPENSSL_UNUSED static const uint32_t kP384MontGY[] = {
+    0x4b03a4fe, 0x23043dad, 0x7bb4a9ac, 0xa1bfa8bf, 0x2e83b050, 0x8bade756,
+    0x68f4ffd9, 0xc6c35219, 0x3969a840, 0xdd800226, 0x5a15c5e9, 0x2b78abc2};
+#else
+#error "unknown word size"
+#endif
+
+// P-521
+OPENSSL_UNUSED static const uint64_t kP521FieldN0 = 0x0000000000000001;
+OPENSSL_UNUSED static const uint64_t kP521OrderN0 = 0x1d2f5ccd79a995c7;
+#if defined(OPENSSL_64_BIT)
+OPENSSL_UNUSED static const uint64_t kP521Field[] = {
+    0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff,
+    0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff,
+    0xffffffffffffffff, 0xffffffffffffffff, 0x00000000000001ff};
+OPENSSL_UNUSED static const uint64_t kP521Order[] = {
+    0xbb6fb71e91386409, 0x3bb5c9b8899c47ae, 0x7fcc0148f709a5d0,
+    0x51868783bf2f966b, 0xfffffffffffffffa, 0xffffffffffffffff,
+    0xffffffffffffffff, 0xffffffffffffffff, 0x00000000000001ff};
+OPENSSL_UNUSED static const uint64_t kP521FieldR[] = {
+    0x0080000000000000, 0x0000000000000000, 0x0000000000000000,
+    0x0000000000000000, 0x0000000000000000, 0x0000000000000000,
+    0x0000000000000000, 0x0000000000000000, 0x0000000000000000};
+OPENSSL_UNUSED static const uint64_t kP521FieldRR[] = {
+    0x0000000000000000, 0x0000400000000000, 0x0000000000000000,
+    0x0000000000000000, 0x0000000000000000, 0x0000000000000000,
+    0x0000000000000000, 0x0000000000000000, 0x0000000000000000};
+OPENSSL_UNUSED static const uint64_t kP521OrderRR[] = {
+    0x137cd04dcf15dd04, 0xf707badce5547ea3, 0x12a78d38794573ff,
+    0xd3721ef557f75e06, 0xdd6e23d82e49c7db, 0xcff3d142b7756e3e,
+    0x5bcc6d61a8e567bc, 0x2d8e03d1492d0d45, 0x000000000000003d};
+OPENSSL_UNUSED static const uint64_t kP521MontB[] = {
+    0x8014654fae586387, 0x78f7a28fea35a81f, 0x839ab9efc41e961a,
+    0xbd8b29605e9dd8df, 0xf0ab0c9ca8f63f49, 0xf9dc5a44c8c77884,
+    0x77516d392dccd98a, 0x0fc94d10d05b42a0, 0x000000000000004d};
+OPENSSL_UNUSED static const uint64_t kP521MontGX[] = {
+    0xb331a16381adc101, 0x4dfcbf3f18e172de, 0x6f19a459e0c2b521,
+    0x947f0ee093d17fd4, 0xdd50a5af3bf7f3ac, 0x90fc1457b035a69e,
+    0x214e32409c829fda, 0xe6cf1f65b311cada, 0x0000000000000074};
+OPENSSL_UNUSED static const uint64_t kP521MontGY[] = {
+    0x28460e4a5a9e268e, 0x20445f4a3b4fe8b3, 0xb09a9e3843513961,
+    0x2062a85c809fd683, 0x164bf7394caf7a13, 0x340bd7de8b939f33,
+    0xeccc7aa224abcda2, 0x022e452fda163e8d, 0x00000000000001e0};
+#elif defined(OPENSSL_32_BIT)
+OPENSSL_UNUSED static const uint32_t kP521Field[] = {
+    0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+    0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+    0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x000001ff};
+OPENSSL_UNUSED static const uint32_t kP521Order[] = {
+    0x91386409, 0xbb6fb71e, 0x899c47ae, 0x3bb5c9b8, 0xf709a5d0, 0x7fcc0148,
+    0xbf2f966b, 0x51868783, 0xfffffffa, 0xffffffff, 0xffffffff, 0xffffffff,
+    0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x000001ff};
+OPENSSL_UNUSED static const uint32_t kP521FieldR[] = {
+    0x00800000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000};
+OPENSSL_UNUSED static const uint32_t kP521FieldRR[] = {
+    0x00000000, 0x00004000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000};
+OPENSSL_UNUSED static const uint32_t kP521OrderRR[] = {
+    0x61c64ca7, 0x1163115a, 0x4374a642, 0x18354a56, 0x0791d9dc, 0x5d4dd6d3,
+    0xd3402705, 0x4fb35b72, 0xb7756e3a, 0xcff3d142, 0xa8e567bc, 0x5bcc6d61,
+    0x492d0d45, 0x2d8e03d1, 0x8c44383d, 0x5b5a3afe, 0x0000019a};
+OPENSSL_UNUSED static const uint32_t kP521MontB[] = {
+    0x8014654f, 0xea35a81f, 0x78f7a28f, 0xc41e961a, 0x839ab9ef, 0x5e9dd8df,
+    0xbd8b2960, 0xa8f63f49, 0xf0ab0c9c, 0xc8c77884, 0xf9dc5a44, 0x2dccd98a,
+    0x77516d39, 0xd05b42a0, 0x0fc94d10, 0xb0c70e4d, 0x0000015c};
+OPENSSL_UNUSED static const uint32_t kP521MontGX[] = {
+    0xb331a163, 0x18e172de, 0x4dfcbf3f, 0xe0c2b521, 0x6f19a459, 0x93d17fd4,
+    0x947f0ee0, 0x3bf7f3ac, 0xdd50a5af, 0xb035a69e, 0x90fc1457, 0x9c829fda,
+    0x214e3240, 0xb311cada, 0xe6cf1f65, 0x5b820274, 0x00000103};
+OPENSSL_UNUSED static const uint32_t kP521MontGY[] = {
+    0x28460e4a, 0x3b4fe8b3, 0x20445f4a, 0x43513961, 0xb09a9e38, 0x809fd683,
+    0x2062a85c, 0x4caf7a13, 0x164bf739, 0x8b939f33, 0x340bd7de, 0x24abcda2,
+    0xeccc7aa2, 0xda163e8d, 0x022e452f, 0x3c4d1de0, 0x000000b5};
+#else
+#error "unknown word size"
+#endif
diff --git a/crypto/fipsmodule/ec/ec.c b/crypto/fipsmodule/ec/ec.c
index fe77396..00587a1 100644
--- a/crypto/fipsmodule/ec/ec.c
+++ b/crypto/fipsmodule/ec/ec.c
@@ -80,233 +80,147 @@
 #include "../bn/internal.h"
 #include "../delocate.h"
 
+#include "builtin_curves.h"
+
 
 static void ec_point_free(EC_POINT *point, int free_group);
 
-static const uint8_t kP224Params[6 * 28] = {
-    // p = 2^224 - 2^96 + 1
-    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
-    0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x01,
-    // a
-    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
-    0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
-    0xFF, 0xFF, 0xFF, 0xFE,
-    // b
-    0xB4, 0x05, 0x0A, 0x85, 0x0C, 0x04, 0xB3, 0xAB, 0xF5, 0x41, 0x32, 0x56,
-    0x50, 0x44, 0xB0, 0xB7, 0xD7, 0xBF, 0xD8, 0xBA, 0x27, 0x0B, 0x39, 0x43,
-    0x23, 0x55, 0xFF, 0xB4,
-    // x
-    0xB7, 0x0E, 0x0C, 0xBD, 0x6B, 0xB4, 0xBF, 0x7F, 0x32, 0x13, 0x90, 0xB9,
-    0x4A, 0x03, 0xC1, 0xD3, 0x56, 0xC2, 0x11, 0x22, 0x34, 0x32, 0x80, 0xD6,
-    0x11, 0x5C, 0x1D, 0x21,
-    // y
-    0xbd, 0x37, 0x63, 0x88, 0xb5, 0xf7, 0x23, 0xfb, 0x4c, 0x22, 0xdf, 0xe6,
-    0xcd, 0x43, 0x75, 0xa0, 0x5a, 0x07, 0x47, 0x64, 0x44, 0xd5, 0x81, 0x99,
-    0x85, 0x00, 0x7e, 0x34,
-    // order
-    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
-    0xFF, 0xFF, 0x16, 0xA2, 0xE0, 0xB8, 0xF0, 0x3E, 0x13, 0xDD, 0x29, 0x45,
-    0x5C, 0x5C, 0x2A, 0x3D,
-};
+static void ec_group_init_static_mont(BN_MONT_CTX *mont, size_t num_words,
+                                      const BN_ULONG *modulus,
+                                      const BN_ULONG *rr, uint64_t n0) {
+  bn_set_static_words(&mont->N, modulus, num_words);
+  bn_set_static_words(&mont->RR, rr, num_words);
+#if defined(OPENSSL_64_BIT)
+  mont->n0[0] = n0;
+#elif defined(OPENSSL_32_BIT)
+  mont->n0[0] = (uint32_t)n0;
+  mont->n0[1] = (uint32_t)(n0 >> 32);
+#else
+#error "unknown word length"
+#endif
+}
 
-static const uint8_t kP256Params[6 * 32] = {
-    // p = 2^256 - 2^224 + 2^192 + 2^96 - 1
-    0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
-    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
-    // a
-    0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
-    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC,
-    // b
-    0x5A, 0xC6, 0x35, 0xD8, 0xAA, 0x3A, 0x93, 0xE7, 0xB3, 0xEB, 0xBD, 0x55,
-    0x76, 0x98, 0x86, 0xBC, 0x65, 0x1D, 0x06, 0xB0, 0xCC, 0x53, 0xB0, 0xF6,
-    0x3B, 0xCE, 0x3C, 0x3E, 0x27, 0xD2, 0x60, 0x4B,
-    // x
-    0x6B, 0x17, 0xD1, 0xF2, 0xE1, 0x2C, 0x42, 0x47, 0xF8, 0xBC, 0xE6, 0xE5,
-    0x63, 0xA4, 0x40, 0xF2, 0x77, 0x03, 0x7D, 0x81, 0x2D, 0xEB, 0x33, 0xA0,
-    0xF4, 0xA1, 0x39, 0x45, 0xD8, 0x98, 0xC2, 0x96,
-    // y
-    0x4f, 0xe3, 0x42, 0xe2, 0xfe, 0x1a, 0x7f, 0x9b, 0x8e, 0xe7, 0xeb, 0x4a,
-    0x7c, 0x0f, 0x9e, 0x16, 0x2b, 0xce, 0x33, 0x57, 0x6b, 0x31, 0x5e, 0xce,
-    0xcb, 0xb6, 0x40, 0x68, 0x37, 0xbf, 0x51, 0xf5,
-    // order
-    0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
-    0xFF, 0xFF, 0xFF, 0xFF, 0xBC, 0xE6, 0xFA, 0xAD, 0xA7, 0x17, 0x9E, 0x84,
-    0xF3, 0xB9, 0xCA, 0xC2, 0xFC, 0x63, 0x25, 0x51,
-};
+static void ec_group_set_a_minus3(EC_GROUP *group) {
+  const EC_FELEM *one = ec_felem_one(group);
+  group->a_is_minus3 = 1;
+  ec_felem_neg(group, &group->a, one);
+  ec_felem_sub(group, &group->a, &group->a, one);
+  ec_felem_sub(group, &group->a, &group->a, one);
+}
 
-static const uint8_t kP384Params[6 * 48] = {
-    // p = 2^384 - 2^128 - 2^96 + 2^32 - 1
-    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
-    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
-    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
-    // a
-    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
-    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
-    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFC,
-    // b
-    0xB3, 0x31, 0x2F, 0xA7, 0xE2, 0x3E, 0xE7, 0xE4, 0x98, 0x8E, 0x05, 0x6B,
-    0xE3, 0xF8, 0x2D, 0x19, 0x18, 0x1D, 0x9C, 0x6E, 0xFE, 0x81, 0x41, 0x12,
-    0x03, 0x14, 0x08, 0x8F, 0x50, 0x13, 0x87, 0x5A, 0xC6, 0x56, 0x39, 0x8D,
-    0x8A, 0x2E, 0xD1, 0x9D, 0x2A, 0x85, 0xC8, 0xED, 0xD3, 0xEC, 0x2A, 0xEF,
-    // x
-    0xAA, 0x87, 0xCA, 0x22, 0xBE, 0x8B, 0x05, 0x37, 0x8E, 0xB1, 0xC7, 0x1E,
-    0xF3, 0x20, 0xAD, 0x74, 0x6E, 0x1D, 0x3B, 0x62, 0x8B, 0xA7, 0x9B, 0x98,
-    0x59, 0xF7, 0x41, 0xE0, 0x82, 0x54, 0x2A, 0x38, 0x55, 0x02, 0xF2, 0x5D,
-    0xBF, 0x55, 0x29, 0x6C, 0x3A, 0x54, 0x5E, 0x38, 0x72, 0x76, 0x0A, 0xB7,
-    // y
-    0x36, 0x17, 0xde, 0x4a, 0x96, 0x26, 0x2c, 0x6f, 0x5d, 0x9e, 0x98, 0xbf,
-    0x92, 0x92, 0xdc, 0x29, 0xf8, 0xf4, 0x1d, 0xbd, 0x28, 0x9a, 0x14, 0x7c,
-    0xe9, 0xda, 0x31, 0x13, 0xb5, 0xf0, 0xb8, 0xc0, 0x0a, 0x60, 0xb1, 0xce,
-    0x1d, 0x7e, 0x81, 0x9d, 0x7a, 0x43, 0x1d, 0x7c, 0x90, 0xea, 0x0e, 0x5f,
-    // order
-    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
-    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
-    0xC7, 0x63, 0x4D, 0x81, 0xF4, 0x37, 0x2D, 0xDF, 0x58, 0x1A, 0x0D, 0xB2,
-    0x48, 0xB0, 0xA7, 0x7A, 0xEC, 0xEC, 0x19, 0x6A, 0xCC, 0xC5, 0x29, 0x73,
-};
+DEFINE_METHOD_FUNCTION(EC_GROUP, EC_group_p224) {
+  out->curve_name = NID_secp224r1;
+  out->comment = "NIST P-224";
+  // 1.3.132.0.33
+  static const uint8_t kOIDP224[] = {0x2b, 0x81, 0x04, 0x00, 0x21};
+  OPENSSL_memcpy(out->oid, kOIDP224, sizeof(kOIDP224));
+  out->oid_len = sizeof(kOIDP224);
 
-static const uint8_t kP521Params[6 * 66] = {
-    // p = 2^521 - 1
-    0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
-    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
-    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
-    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
-    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
-    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
-    // a
-    0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
-    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
-    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
-    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
-    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
-    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC,
-    // b
-    0x00, 0x51, 0x95, 0x3E, 0xB9, 0x61, 0x8E, 0x1C, 0x9A, 0x1F, 0x92, 0x9A,
-    0x21, 0xA0, 0xB6, 0x85, 0x40, 0xEE, 0xA2, 0xDA, 0x72, 0x5B, 0x99, 0xB3,
-    0x15, 0xF3, 0xB8, 0xB4, 0x89, 0x91, 0x8E, 0xF1, 0x09, 0xE1, 0x56, 0x19,
-    0x39, 0x51, 0xEC, 0x7E, 0x93, 0x7B, 0x16, 0x52, 0xC0, 0xBD, 0x3B, 0xB1,
-    0xBF, 0x07, 0x35, 0x73, 0xDF, 0x88, 0x3D, 0x2C, 0x34, 0xF1, 0xEF, 0x45,
-    0x1F, 0xD4, 0x6B, 0x50, 0x3F, 0x00,
-    // x
-    0x00, 0xC6, 0x85, 0x8E, 0x06, 0xB7, 0x04, 0x04, 0xE9, 0xCD, 0x9E, 0x3E,
-    0xCB, 0x66, 0x23, 0x95, 0xB4, 0x42, 0x9C, 0x64, 0x81, 0x39, 0x05, 0x3F,
-    0xB5, 0x21, 0xF8, 0x28, 0xAF, 0x60, 0x6B, 0x4D, 0x3D, 0xBA, 0xA1, 0x4B,
-    0x5E, 0x77, 0xEF, 0xE7, 0x59, 0x28, 0xFE, 0x1D, 0xC1, 0x27, 0xA2, 0xFF,
-    0xA8, 0xDE, 0x33, 0x48, 0xB3, 0xC1, 0x85, 0x6A, 0x42, 0x9B, 0xF9, 0x7E,
-    0x7E, 0x31, 0xC2, 0xE5, 0xBD, 0x66,
-    // y
-    0x01, 0x18, 0x39, 0x29, 0x6a, 0x78, 0x9a, 0x3b, 0xc0, 0x04, 0x5c, 0x8a,
-    0x5f, 0xb4, 0x2c, 0x7d, 0x1b, 0xd9, 0x98, 0xf5, 0x44, 0x49, 0x57, 0x9b,
-    0x44, 0x68, 0x17, 0xaf, 0xbd, 0x17, 0x27, 0x3e, 0x66, 0x2c, 0x97, 0xee,
-    0x72, 0x99, 0x5e, 0xf4, 0x26, 0x40, 0xc5, 0x50, 0xb9, 0x01, 0x3f, 0xad,
-    0x07, 0x61, 0x35, 0x3c, 0x70, 0x86, 0xa2, 0x72, 0xc2, 0x40, 0x88, 0xbe,
-    0x94, 0x76, 0x9f, 0xd1, 0x66, 0x50,
-    // order
-    0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
-    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
-    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFA, 0x51, 0x86,
-    0x87, 0x83, 0xBF, 0x2F, 0x96, 0x6B, 0x7F, 0xCC, 0x01, 0x48, 0xF7, 0x09,
-    0xA5, 0xD0, 0x3B, 0xB5, 0xC9, 0xB8, 0x89, 0x9C, 0x47, 0xAE, 0xBB, 0x6F,
-    0xB7, 0x1E, 0x91, 0x38, 0x64, 0x09,
-};
+  ec_group_init_static_mont(&out->field, OPENSSL_ARRAY_SIZE(kP224Field),
+                            kP224Field, kP224FieldRR, kP224FieldN0);
+  ec_group_init_static_mont(&out->order, OPENSSL_ARRAY_SIZE(kP224Order),
+                            kP224Order, kP224OrderRR, kP224OrderN0);
 
-DEFINE_METHOD_FUNCTION(struct built_in_curves, OPENSSL_built_in_curves) {
-  // 1.3.132.0.35
-  static const uint8_t kOIDP521[] = {0x2b, 0x81, 0x04, 0x00, 0x23};
-  out->curves[0].nid = NID_secp521r1;
-  out->curves[0].oid = kOIDP521;
-  out->curves[0].oid_len = sizeof(kOIDP521);
-  out->curves[0].comment = "NIST P-521";
-  out->curves[0].param_len = 66;
-  out->curves[0].params = kP521Params;
-  out->curves[0].method = EC_GFp_mont_method();
+#if defined(BORINGSSL_HAS_UINT128) && !defined(OPENSSL_SMALL)
+  out->meth = EC_GFp_nistp224_method();
+  OPENSSL_memcpy(out->generator.raw.X.words, kP224GX, sizeof(kP224GX));
+  OPENSSL_memcpy(out->generator.raw.Y.words, kP224GY, sizeof(kP224GY));
+  out->generator.raw.Z.words[0] = 1;
+  OPENSSL_memcpy(out->b.words, kP224B, sizeof(kP224B));
+#else
+  out->meth = EC_GFp_mont_method();
+  OPENSSL_memcpy(out->generator.raw.X.words, kP224MontGX, sizeof(kP224MontGX));
+  OPENSSL_memcpy(out->generator.raw.Y.words, kP224MontGY, sizeof(kP224MontGY));
+  OPENSSL_memcpy(out->generator.raw.Z.words, kP224FieldR, sizeof(kP224FieldR));
+  OPENSSL_memcpy(out->b.words, kP224MontB, sizeof(kP224MontB));
+#endif
+  out->generator.group = out;
 
-  // 1.3.132.0.34
-  static const uint8_t kOIDP384[] = {0x2b, 0x81, 0x04, 0x00, 0x22};
-  out->curves[1].nid = NID_secp384r1;
-  out->curves[1].oid = kOIDP384;
-  out->curves[1].oid_len = sizeof(kOIDP384);
-  out->curves[1].comment = "NIST P-384";
-  out->curves[1].param_len = 48;
-  out->curves[1].params = kP384Params;
-  out->curves[1].method = EC_GFp_mont_method();
+  ec_group_set_a_minus3(out);
+  out->has_order = 1;
+  out->field_greater_than_order = 1;
+}
 
+DEFINE_METHOD_FUNCTION(EC_GROUP, EC_group_p256) {
+  out->curve_name = NID_X9_62_prime256v1;
+  out->comment = "NIST P-256";
   // 1.2.840.10045.3.1.7
   static const uint8_t kOIDP256[] = {0x2a, 0x86, 0x48, 0xce,
                                      0x3d, 0x03, 0x01, 0x07};
-  out->curves[2].nid = NID_X9_62_prime256v1;
-  out->curves[2].oid = kOIDP256;
-  out->curves[2].oid_len = sizeof(kOIDP256);
-  out->curves[2].comment = "NIST P-256";
-  out->curves[2].param_len = 32;
-  out->curves[2].params = kP256Params;
-  out->curves[2].method =
-#if !defined(OPENSSL_NO_ASM) && \
-    (defined(OPENSSL_X86_64) || defined(OPENSSL_AARCH64)) &&   \
+  OPENSSL_memcpy(out->oid, kOIDP256, sizeof(kOIDP256));
+  out->oid_len = sizeof(kOIDP256);
+
+  ec_group_init_static_mont(&out->field, OPENSSL_ARRAY_SIZE(kP256Field),
+                            kP256Field, kP256FieldRR, kP256FieldN0);
+  ec_group_init_static_mont(&out->order, OPENSSL_ARRAY_SIZE(kP256Order),
+                            kP256Order, kP256OrderRR, kP256OrderN0);
+
+#if !defined(OPENSSL_NO_ASM) &&                              \
+    (defined(OPENSSL_X86_64) || defined(OPENSSL_AARCH64)) && \
     !defined(OPENSSL_SMALL)
-      EC_GFp_nistz256_method();
+  out->meth = EC_GFp_nistz256_method();
 #else
-      EC_GFp_nistp256_method();
+  out->meth = EC_GFp_nistp256_method();
 #endif
+  out->generator.group = out;
+  OPENSSL_memcpy(out->generator.raw.X.words, kP256MontGX, sizeof(kP256MontGX));
+  OPENSSL_memcpy(out->generator.raw.Y.words, kP256MontGY, sizeof(kP256MontGY));
+  OPENSSL_memcpy(out->generator.raw.Z.words, kP256FieldR, sizeof(kP256FieldR));
+  OPENSSL_memcpy(out->b.words, kP256MontB, sizeof(kP256MontB));
 
-  // 1.3.132.0.33
-  static const uint8_t kOIDP224[] = {0x2b, 0x81, 0x04, 0x00, 0x21};
-  out->curves[3].nid = NID_secp224r1;
-  out->curves[3].oid = kOIDP224;
-  out->curves[3].oid_len = sizeof(kOIDP224);
-  out->curves[3].comment = "NIST P-224";
-  out->curves[3].param_len = 28;
-  out->curves[3].params = kP224Params;
-  out->curves[3].method =
-#if defined(BORINGSSL_HAS_UINT128) && !defined(OPENSSL_SMALL)
-      EC_GFp_nistp224_method();
-#else
-      EC_GFp_mont_method();
-#endif
+  ec_group_set_a_minus3(out);
+  out->has_order = 1;
+  out->field_greater_than_order = 1;
 }
 
-EC_GROUP *ec_group_new(const EC_METHOD *meth, const BIGNUM *p, const BIGNUM *a,
-                       const BIGNUM *b, BN_CTX *ctx) {
-  EC_GROUP *ret = OPENSSL_malloc(sizeof(EC_GROUP));
-  if (ret == NULL) {
-    return NULL;
-  }
-  OPENSSL_memset(ret, 0, sizeof(EC_GROUP));
+DEFINE_METHOD_FUNCTION(EC_GROUP, EC_group_p384) {
+  out->curve_name = NID_secp384r1;
+  out->comment = "NIST P-384";
+  // 1.3.132.0.34
+  static const uint8_t kOIDP384[] = {0x2b, 0x81, 0x04, 0x00, 0x22};
+  OPENSSL_memcpy(out->oid, kOIDP384, sizeof(kOIDP384));
+  out->oid_len = sizeof(kOIDP384);
 
-  ret->references = 1;
-  ret->meth = meth;
-  bn_mont_ctx_init(&ret->field);
-  bn_mont_ctx_init(&ret->order);
+  ec_group_init_static_mont(&out->field, OPENSSL_ARRAY_SIZE(kP384Field),
+                            kP384Field, kP384FieldRR, kP384FieldN0);
+  ec_group_init_static_mont(&out->order, OPENSSL_ARRAY_SIZE(kP384Order),
+                            kP384Order, kP384OrderRR, kP384OrderN0);
 
-  ret->generator.group = ret;
+  out->meth = EC_GFp_mont_method();
+  out->generator.group = out;
+  OPENSSL_memcpy(out->generator.raw.X.words, kP384MontGX, sizeof(kP384MontGX));
+  OPENSSL_memcpy(out->generator.raw.Y.words, kP384MontGY, sizeof(kP384MontGY));
+  OPENSSL_memcpy(out->generator.raw.Z.words, kP384FieldR, sizeof(kP384FieldR));
+  OPENSSL_memcpy(out->b.words, kP384MontB, sizeof(kP384MontB));
 
-  if (!ec_GFp_simple_group_set_curve(ret, p, a, b, ctx)) {
-    EC_GROUP_free(ret);
-    return NULL;
-  }
-
-  return ret;
+  ec_group_set_a_minus3(out);
+  out->has_order = 1;
+  out->field_greater_than_order = 1;
 }
 
-static int ec_group_set_generator(EC_GROUP *group, const EC_AFFINE *generator,
-                                  const BIGNUM *order) {
-  assert(!group->has_order);
+DEFINE_METHOD_FUNCTION(EC_GROUP, EC_group_p521) {
+  out->curve_name = NID_secp521r1;
+  out->comment = "NIST P-521";
+  // 1.3.132.0.35
+  static const uint8_t kOIDP521[] = {0x2b, 0x81, 0x04, 0x00, 0x23};
+  OPENSSL_memcpy(out->oid, kOIDP521, sizeof(kOIDP521));
+  out->oid_len = sizeof(kOIDP521);
 
-  if (!BN_MONT_CTX_set(&group->order, order, NULL)) {
-    return 0;
-  }
+  ec_group_init_static_mont(&out->field, OPENSSL_ARRAY_SIZE(kP521Field),
+                            kP521Field, kP521FieldRR, kP521FieldN0);
+  ec_group_init_static_mont(&out->order, OPENSSL_ARRAY_SIZE(kP521Order),
+                            kP521Order, kP521OrderRR, kP521OrderN0);
 
-  group->field_greater_than_order = BN_cmp(&group->field.N, order) > 0;
-  group->generator.raw.X = generator->X;
-  group->generator.raw.Y = generator->Y;
-  // |raw.Z| was set to 1 by |ec_GFp_simple_group_set_curve|.
-  group->has_order = 1;
-  return 1;
+  out->meth = EC_GFp_mont_method();
+  out->generator.group = out;
+  OPENSSL_memcpy(out->generator.raw.X.words, kP521MontGX, sizeof(kP521MontGX));
+  OPENSSL_memcpy(out->generator.raw.Y.words, kP521MontGY, sizeof(kP521MontGY));
+  OPENSSL_memcpy(out->generator.raw.Z.words, kP521FieldR, sizeof(kP521FieldR));
+  OPENSSL_memcpy(out->b.words, kP521MontB, sizeof(kP521MontB));
+
+  ec_group_set_a_minus3(out);
+  out->has_order = 1;
+  out->field_greater_than_order = 1;
 }
 
 EC_GROUP *EC_GROUP_new_curve_GFp(const BIGNUM *p, const BIGNUM *a,
@@ -336,8 +250,19 @@
     goto err;
   }
 
-  ret = ec_group_new(EC_GFp_mont_method(), p, a_reduced, b_reduced, ctx);
+  ret = OPENSSL_malloc(sizeof(EC_GROUP));
   if (ret == NULL) {
+    return NULL;
+  }
+  OPENSSL_memset(ret, 0, sizeof(EC_GROUP));
+  ret->references = 1;
+  ret->meth = EC_GFp_mont_method();
+  bn_mont_ctx_init(&ret->field);
+  bn_mont_ctx_init(&ret->order);
+  ret->generator.group = ret;
+  if (!ec_GFp_simple_group_set_curve(ret, p, a_reduced, b_reduced, ctx)) {
+    EC_GROUP_free(ret);
+    ret = NULL;
     goto err;
   }
 
@@ -388,10 +313,15 @@
 
   EC_AFFINE affine;
   if (!ec_jacobian_to_affine(group, &affine, &generator->raw) ||
-      !ec_group_set_generator(group, &affine, order)) {
+      !BN_MONT_CTX_set(&group->order, order, NULL)) {
     goto err;
   }
 
+  group->field_greater_than_order = BN_cmp(&group->field.N, order) > 0;
+  group->generator.raw.X = affine.X;
+  group->generator.raw.Y = affine.Y;
+  // |raw.Z| was set to 1 by |EC_GROUP_new_curve_GFp|.
+  group->has_order = 1;
   ret = 1;
 
 err:
@@ -399,110 +329,20 @@
   return ret;
 }
 
-static EC_GROUP *ec_group_new_from_data(const struct built_in_curve *curve) {
-  EC_GROUP *group = NULL;
-  BIGNUM *p = NULL, *a = NULL, *b = NULL, *order = NULL;
-  int ok = 0;
-
-  BN_CTX *ctx = BN_CTX_new();
-  if (ctx == NULL) {
-    goto err;
-  }
-
-  const unsigned param_len = curve->param_len;
-  const uint8_t *params = curve->params;
-
-  if (!(p = BN_bin2bn(params + 0 * param_len, param_len, NULL)) ||
-      !(a = BN_bin2bn(params + 1 * param_len, param_len, NULL)) ||
-      !(b = BN_bin2bn(params + 2 * param_len, param_len, NULL)) ||
-      !(order = BN_bin2bn(params + 5 * param_len, param_len, NULL))) {
-    OPENSSL_PUT_ERROR(EC, ERR_R_BN_LIB);
-    goto err;
-  }
-
-  group = ec_group_new(curve->method, p, a, b, ctx);
-  if (group == NULL) {
-    OPENSSL_PUT_ERROR(EC, ERR_R_EC_LIB);
-    goto err;
-  }
-
-  EC_AFFINE G;
-  EC_FELEM x, y;
-  if (!ec_felem_from_bytes(group, &x, params + 3 * param_len, param_len) ||
-      !ec_felem_from_bytes(group, &y, params + 4 * param_len, param_len) ||
-      !ec_point_set_affine_coordinates(group, &G, &x, &y) ||
-      !ec_group_set_generator(group, &G, order)) {
-    goto err;
-  }
-
-  ok = 1;
-
-err:
-  if (!ok) {
-    EC_GROUP_free(group);
-    group = NULL;
-  }
-  BN_CTX_free(ctx);
-  BN_free(p);
-  BN_free(a);
-  BN_free(b);
-  BN_free(order);
-  return group;
-}
-
-// Built-in groups are allocated lazily and static once allocated.
-// TODO(davidben): Make these actually static. https://crbug.com/boringssl/20.
-struct built_in_groups_st {
-  EC_GROUP *groups[OPENSSL_NUM_BUILT_IN_CURVES];
-};
-DEFINE_BSS_GET(struct built_in_groups_st, built_in_groups)
-DEFINE_STATIC_MUTEX(built_in_groups_lock)
-
 EC_GROUP *EC_GROUP_new_by_curve_name(int nid) {
-  struct built_in_groups_st *groups = built_in_groups_bss_get();
-  EC_GROUP **group_ptr = NULL;
-  const struct built_in_curves *const curves = OPENSSL_built_in_curves();
-  const struct built_in_curve *curve = NULL;
-  for (size_t i = 0; i < OPENSSL_NUM_BUILT_IN_CURVES; i++) {
-    if (curves->curves[i].nid == nid) {
-      curve = &curves->curves[i];
-      group_ptr = &groups->groups[i];
-      break;
-    }
+  switch (nid) {
+    case NID_secp224r1:
+      return (EC_GROUP *)EC_group_p224();
+    case NID_X9_62_prime256v1:
+      return (EC_GROUP *)EC_group_p256();
+    case NID_secp384r1:
+      return (EC_GROUP *)EC_group_p384();
+    case NID_secp521r1:
+      return (EC_GROUP *)EC_group_p521();
+    default:
+      OPENSSL_PUT_ERROR(EC, EC_R_UNKNOWN_GROUP);
+      return NULL;
   }
-
-  if (curve == NULL) {
-    OPENSSL_PUT_ERROR(EC, EC_R_UNKNOWN_GROUP);
-    return NULL;
-  }
-
-  CRYPTO_MUTEX_lock_read(built_in_groups_lock_bss_get());
-  EC_GROUP *ret = *group_ptr;
-  CRYPTO_MUTEX_unlock_read(built_in_groups_lock_bss_get());
-  if (ret != NULL) {
-    return ret;
-  }
-
-  ret = ec_group_new_from_data(curve);
-  if (ret == NULL) {
-    return NULL;
-  }
-
-  EC_GROUP *to_free = NULL;
-  CRYPTO_MUTEX_lock_write(built_in_groups_lock_bss_get());
-  if (*group_ptr == NULL) {
-    *group_ptr = ret;
-    // Filling in |ret->curve_name| makes |EC_GROUP_free| and |EC_GROUP_dup|
-    // into no-ops. At this point, |ret| is considered static.
-    ret->curve_name = nid;
-  } else {
-    to_free = ret;
-    ret = *group_ptr;
-  }
-  CRYPTO_MUTEX_unlock_write(built_in_groups_lock_bss_get());
-
-  EC_GROUP_free(to_free);
-  return ret;
 }
 
 void EC_GROUP_free(EC_GROUP *group) {
@@ -1202,16 +1042,3 @@
     abort();
   }
 }
-
-size_t EC_get_builtin_curves(EC_builtin_curve *out_curves,
-                             size_t max_num_curves) {
-  const struct built_in_curves *const curves = OPENSSL_built_in_curves();
-
-  for (size_t i = 0; i < max_num_curves && i < OPENSSL_NUM_BUILT_IN_CURVES;
-       i++) {
-    out_curves[i].comment = curves->curves[i].comment;
-    out_curves[i].nid = curves->curves[i].nid;
-  }
-
-  return OPENSSL_NUM_BUILT_IN_CURVES;
-}
diff --git a/crypto/fipsmodule/ec/internal.h b/crypto/fipsmodule/ec/internal.h
index 77c3dd6..f2cb69b 100644
--- a/crypto/fipsmodule/ec/internal.h
+++ b/crypto/fipsmodule/ec/internal.h
@@ -611,7 +611,12 @@
 
   EC_FELEM a, b;  // Curve coefficients.
 
+  // comment is a human-readable string describing the curve.
+  const char *comment;
+
   int curve_name;  // optional NID for named curve
+  uint8_t oid[9];
+  uint8_t oid_len;
 
   // a_is_minus3 is one if |a| is -3 mod |field| and zero otherwise. Point
   // arithmetic is optimized for -3.
@@ -743,31 +748,6 @@
   CRYPTO_EX_DATA ex_data;
 } /* EC_KEY */;
 
-struct built_in_curve {
-  int nid;
-  const uint8_t *oid;
-  uint8_t oid_len;
-  // comment is a human-readable string describing the curve.
-  const char *comment;
-  // param_len is the number of bytes needed to store a field element.
-  uint8_t param_len;
-  // params points to an array of 6*|param_len| bytes which hold the field
-  // elements of the following (in big-endian order): prime, a, b, generator x,
-  // generator y, order.
-  const uint8_t *params;
-  const EC_METHOD *method;
-};
-
-#define OPENSSL_NUM_BUILT_IN_CURVES 4
-
-struct built_in_curves {
-  struct built_in_curve curves[OPENSSL_NUM_BUILT_IN_CURVES];
-};
-
-// OPENSSL_built_in_curves returns a pointer to static information about
-// standard curves. The array is terminated with an entry where |nid| is
-// |NID_undef|.
-const struct built_in_curves *OPENSSL_built_in_curves(void);
 
 #if defined(__cplusplus)
 }  // extern C
diff --git a/crypto/fipsmodule/ec/make_tables.go b/crypto/fipsmodule/ec/make_tables.go
index 120c40b..30535f5 100644
--- a/crypto/fipsmodule/ec/make_tables.go
+++ b/crypto/fipsmodule/ec/make_tables.go
@@ -17,14 +17,21 @@
 package main
 
 import (
+	"bytes"
 	"crypto/elliptic"
 	"fmt"
 	"io"
 	"math/big"
 	"os"
+	"strings"
 )
 
 func main() {
+	if err := writeBuiltinCurves("builtin_curves.h"); err != nil {
+		fmt.Fprintf(os.Stderr, "Error writing builtin_curves.h: %s\n", err)
+		os.Exit(1)
+	}
+
 	if err := writeP256NistzTable("p256-nistz-table.h"); err != nil {
 		fmt.Fprintf(os.Stderr, "Error writing p256-nistz-table.h: %s\n", err)
 		os.Exit(1)
@@ -36,6 +43,120 @@
 	}
 }
 
+func writeBuiltinCurves(path string) error {
+	f, err := os.Create(path)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+	w := &columnWriter{w: f}
+
+	const fileHeader = `/* Copyright (c) 2023, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+// This file is generated by make_tables.go.
+`
+	if _, err := io.WriteString(w, fileHeader); err != nil {
+		return err
+	}
+	// P-224 is the only curve where we have a non-Montgomery implementation.
+	if err := writeCurveData(w, elliptic.P224(), true); err != nil {
+		return err
+	}
+	if err := writeCurveData(w, elliptic.P256(), false); err != nil {
+		return err
+	}
+	if err := writeCurveData(w, elliptic.P384(), false); err != nil {
+		return err
+	}
+	if err := writeCurveData(w, elliptic.P521(), false); err != nil {
+		return err
+	}
+	return nil
+}
+
+func writeCurveData(w *columnWriter, curve elliptic.Curve, includeNonMontgomery bool) error {
+	params := curve.Params()
+	if _, err := fmt.Fprintf(w, "\n// %s\n", params.Name); err != nil {
+		return err
+	}
+
+	cName := strings.Replace(params.Name, "-", "", -1)
+	writeDecls := func(bits int) error {
+		if err := writeDecl(w, curve, bits, fmt.Sprintf("k%sField", cName), params.P); err != nil {
+			return err
+		}
+		if err := writeDecl(w, curve, bits, fmt.Sprintf("k%sOrder", cName), params.N); err != nil {
+			return err
+		}
+		if includeNonMontgomery {
+			if err := writeDecl(w, curve, bits, fmt.Sprintf("k%sB", cName), params.B); err != nil {
+				return err
+			}
+			if err := writeDecl(w, curve, bits, fmt.Sprintf("k%sGX", cName), params.Gx); err != nil {
+				return err
+			}
+			if err := writeDecl(w, curve, bits, fmt.Sprintf("k%sGY", cName), params.Gy); err != nil {
+				return err
+			}
+		}
+		if err := writeDecl(w, curve, bits, fmt.Sprintf("k%sFieldR", cName), montgomeryR(params.P, bits)); err != nil {
+			return err
+		}
+		if err := writeDecl(w, curve, bits, fmt.Sprintf("k%sFieldRR", cName), montgomeryRR(params.P, bits)); err != nil {
+			return err
+		}
+		if err := writeDecl(w, curve, bits, fmt.Sprintf("k%sOrderRR", cName), montgomeryRR(params.N, bits)); err != nil {
+			return err
+		}
+		if err := writeDecl(w, curve, bits, fmt.Sprintf("k%sMontB", cName), toMontgomery(params.B, params.P, bits)); err != nil {
+			return err
+		}
+		if err := writeDecl(w, curve, bits, fmt.Sprintf("k%sMontGX", cName), toMontgomery(params.Gx, params.P, bits)); err != nil {
+			return err
+		}
+		if err := writeDecl(w, curve, bits, fmt.Sprintf("k%sMontGY", cName), toMontgomery(params.Gy, params.P, bits)); err != nil {
+			return err
+		}
+		return nil
+	}
+
+	if _, err := fmt.Fprintf(w, "OPENSSL_UNUSED static const uint64_t k%sFieldN0 = 0x%016x;\n", cName, montgomeryN0(params.P)); err != nil {
+		return err
+	}
+	if _, err := fmt.Fprintf(w, "OPENSSL_UNUSED static const uint64_t k%sOrderN0 = 0x%016x;\n", cName, montgomeryN0(params.N)); err != nil {
+		return err
+	}
+
+	if _, err := io.WriteString(w, "#if defined(OPENSSL_64_BIT)\n"); err != nil {
+		return err
+	}
+	if err := writeDecls(64); err != nil {
+		return err
+	}
+	if _, err := io.WriteString(w, "#elif defined(OPENSSL_32_BIT)\n"); err != nil {
+		return err
+	}
+	if err := writeDecls(32); err != nil {
+		return err
+	}
+	if _, err := io.WriteString(w, "#else\n#error \"unknown word size\"\n#endif\n"); err != nil {
+		return err
+	}
+	return nil
+}
+
 func writeP256NistzTable(path string) error {
 	curve := elliptic.P256()
 	tables := make([][][2]*big.Int, 0, 37)
@@ -49,6 +170,7 @@
 		return err
 	}
 	defer f.Close()
+	w := &columnWriter{w: f}
 
 	const fileHeader = `/*
  * Copyright 2014-2016 The OpenSSL Project Authors. All Rights Reserved.
@@ -75,13 +197,13 @@
 // This file is generated by make_tables.go.
 
 static const alignas(4096) PRECOMP256_ROW ecp_nistz256_precomputed[37] = `
-	if _, err := f.WriteString(fileHeader); err != nil {
+	if _, err := io.WriteString(w, fileHeader); err != nil {
 		return err
 	}
-	if err := writeTables(f, curve, tables, true, 4, writeBNMont); err != nil {
+	if err := writeTables(w, curve, tables, writeBNMont); err != nil {
 		return err
 	}
-	if _, err := f.WriteString(";\n"); err != nil {
+	if _, err := io.WriteString(w, ";\n"); err != nil {
 		return err
 	}
 
@@ -100,6 +222,7 @@
 		return err
 	}
 	defer f.Close()
+	w := &columnWriter{w: f}
 
 	const fileHeader = `/* Copyright (c) 2020, Google Inc.
  *
@@ -155,19 +278,19 @@
 // fiat_p256_g_pre_comp is the table of precomputed base points
 #if defined(OPENSSL_64_BIT)
 static const fiat_p256_felem fiat_p256_g_pre_comp[2][15][2] = `
-	if _, err := f.WriteString(fileHeader); err != nil {
+	if _, err := io.WriteString(w, fileHeader); err != nil {
 		return err
 	}
-	if err := writeTables(f, curve, tables, true, 4, writeU64Mont); err != nil {
+	if err := writeTables(w, curve, tables, writeU64Mont); err != nil {
 		return err
 	}
-	if _, err := f.WriteString(";\n#else\nstatic const fiat_p256_felem fiat_p256_g_pre_comp[2][15][2] = "); err != nil {
+	if _, err := io.WriteString(w, ";\n#else\nstatic const fiat_p256_felem fiat_p256_g_pre_comp[2][15][2] = "); err != nil {
 		return err
 	}
-	if err := writeTables(f, curve, tables, true, 4, writeU32Mont); err != nil {
+	if err := writeTables(w, curve, tables, writeU32Mont); err != nil {
 		return err
 	}
-	if _, err := f.WriteString(";\n#endif\n"); err != nil {
+	if _, err := io.WriteString(w, ";\n#endif\n"); err != nil {
 		return err
 	}
 
@@ -223,20 +346,38 @@
 	return ret
 }
 
-// toMontgomery sets n to be n×R mod p, where R is the Montgomery factor.
-func toMontgomery(curve elliptic.Curve, n *big.Int) *big.Int {
-	params := curve.Params()
+func montgomeryR(p *big.Int, wordSize int) *big.Int {
 	// R is the bit width of p, rounded up to word size.
-	rounded64 := 64 * ((params.BitSize + 63) / 64)
-	rounded32 := 32 * ((params.BitSize + 31) / 32)
-	if rounded64 != rounded32 {
-		panic(fmt.Sprintf("Montgomery form for %s is inconsistent between 32-bit and 64-bit", params.Name))
-	}
+	rounded := wordSize * ((p.BitLen() + wordSize - 1) / wordSize)
 	R := new(big.Int).SetInt64(1)
-	R.Lsh(R, uint(rounded64))
+	R.Lsh(R, uint(rounded))
+	R.Mod(R, p)
+	return R
+}
 
-	ret := new(big.Int).Mul(n, R)
-	ret.Mod(ret, params.P)
+func montgomeryRR(p *big.Int, wordSize int) *big.Int {
+	R := montgomeryR(p, wordSize)
+	R.Mul(R, R)
+	R.Mod(R, p)
+	return R
+}
+
+func montgomeryN0(p *big.Int) uint64 {
+	two64 := new(big.Int)
+	two64 = two64.SetBit(two64, 64, 1)
+	n0 := new(big.Int).Neg(p)
+	n0 = n0.ModInverse(n0, two64)
+	if !n0.IsUint64() {
+		panic("n0 should fit in uint64")
+	}
+	return n0.Uint64()
+}
+
+// toMontgomery returns n×R mod p, where R is the Montgomery factor.
+func toMontgomery(n, p *big.Int, wordSize int) *big.Int {
+	ret := montgomeryR(p, wordSize)
+	ret.Mul(ret, n)
+	ret.Mod(ret, p)
 	return ret
 }
 
@@ -251,17 +392,35 @@
 	return ret
 }
 
-func bigIntToU32s(curve elliptic.Curve, n *big.Int) []uint64 {
+func bigIntToU32s(curve elliptic.Curve, n *big.Int) []uint32 {
 	words := (curve.Params().BitSize + 31) / 32
-	ret := make([]uint64, words)
+	ret := make([]uint32, words)
 	bytes := n.Bytes()
 	for i, b := range bytes {
 		i = len(bytes) - i - 1
-		ret[i/4] |= uint64(b) << (8 * (i % 4))
+		ret[i/4] |= uint32(b) << (8 * (i % 4))
 	}
 	return ret
 }
 
+// A columnWriter is an io.Writer that tracks the number of columns in the
+// current line.
+type columnWriter struct {
+	w      io.Writer
+	column int
+}
+
+func (c *columnWriter) Write(p []byte) (n int, err error) {
+	n, err = c.w.Write(p)
+	idx := bytes.LastIndexByte(p[:n], '\n')
+	if idx < 0 {
+		c.column += n
+	} else {
+		c.column = n - idx - 1
+	}
+	return
+}
+
 func writeIndent(w io.Writer, indent int) error {
 	for i := 0; i < indent; i++ {
 		if _, err := io.WriteString(w, " "); err != nil {
@@ -271,17 +430,29 @@
 	return nil
 }
 
-func writeWords(w io.Writer, words []uint64, wrap, indent int, format func(uint64) string) error {
+func writeWordsBraced[Word any](w *columnWriter, words []Word, format func(Word) string) error {
 	if _, err := io.WriteString(w, "{"); err != nil {
 		return err
 	}
+	if err := writeWords(w, words, format); err != nil {
+		return err
+	}
+	if _, err := io.WriteString(w, "}"); err != nil {
+		return err
+	}
+	return nil
+}
+
+func writeWords[Word any](w *columnWriter, words []Word, format func(Word) string) error {
+	indent := w.column
 	for i, word := range words {
+		str := format(word)
 		if i > 0 {
-			if i%wrap == 0 {
+			if w.column+1+len(str) > 80 {
 				if _, err := io.WriteString(w, ",\n"); err != nil {
 					return err
 				}
-				if err := writeIndent(w, indent+1); err != nil {
+				if err := writeIndent(w, indent); err != nil {
 					return err
 				}
 			} else {
@@ -290,56 +461,72 @@
 				}
 			}
 		}
-		if _, err := io.WriteString(w, format(word)); err != nil {
+		if _, err := io.WriteString(w, str); err != nil {
 			return err
 		}
 	}
-	if _, err := io.WriteString(w, "}"); err != nil {
+	return nil
+}
+
+func writeDecl(w *columnWriter, curve elliptic.Curve, bits int, decl string, n *big.Int) error {
+	if _, err := fmt.Fprintf(w, "OPENSSL_UNUSED static const uint%d_t %s[] = {\n    ", bits, decl); err != nil {
+		return err
+	}
+	if bits == 32 {
+		if err := writeWords(w, bigIntToU32s(curve, n), formatU32); err != nil {
+			return err
+		}
+	} else if bits == 64 {
+		if err := writeWords(w, bigIntToU64s(curve, n), formatU64); err != nil {
+			return err
+		}
+	} else {
+		panic("unknown bit count")
+	}
+	if _, err := fmt.Fprintf(w, "};\n"); err != nil {
 		return err
 	}
 	return nil
 }
 
-func writeBNMont(w io.Writer, curve elliptic.Curve, n *big.Int, indent int) error {
-	n = toMontgomery(curve, n)
-	return writeWords(w, bigIntToU64s(curve, n), 2, indent, func(word uint64) string {
-		return fmt.Sprintf("TOBN(0x%08x, 0x%08x)", uint32(word>>32), uint32(word))
-	})
+func formatBN(word uint64) string {
+	return fmt.Sprintf("TOBN(0x%08x, 0x%08x)", uint32(word>>32), uint32(word))
 }
 
-func writeU64Mont(w io.Writer, curve elliptic.Curve, n *big.Int, indent int) error {
-	n = toMontgomery(curve, n)
-	return writeWords(w, bigIntToU64s(curve, n), 3, indent, func(word uint64) string {
-		return fmt.Sprintf("0x%016x", word)
-	})
+func formatU64(word uint64) string {
+	return fmt.Sprintf("0x%016x", word)
 }
 
-func writeU32Mont(w io.Writer, curve elliptic.Curve, n *big.Int, indent int) error {
-	n = toMontgomery(curve, n)
-	return writeWords(w, bigIntToU32s(curve, n), 6, indent, func(word uint64) string {
-		if word >= 1<<32 {
-			panic(fmt.Sprintf("word too large: 0x%x", word))
-		}
-		return fmt.Sprintf("0x%08x", word)
-	})
+func formatU32(word uint32) string {
+	return fmt.Sprintf("0x%08x", word)
 }
 
-type writeBigIntFunc func(w io.Writer, curve elliptic.Curve, n *big.Int, indent int) error
+func writeBNMont(w *columnWriter, curve elliptic.Curve, n *big.Int) error {
+	n32 := toMontgomery(n, curve.Params().P, 32)
+	n64 := toMontgomery(n, curve.Params().P, 64)
+	if n32.Cmp(n64) != 0 {
+		panic(fmt.Sprintf("Montgomery form for %s is inconsistent between 32-bit and 64-bit", curve.Params().Name))
+	}
+	return writeWordsBraced(w, bigIntToU64s(curve, n64), formatBN)
+}
 
-func writeTable(w io.Writer, curve elliptic.Curve, table [][2]*big.Int, isRoot bool, indent int, writeBigInt writeBigIntFunc) error {
+func writeU64Mont(w *columnWriter, curve elliptic.Curve, n *big.Int) error {
+	n = toMontgomery(n, curve.Params().P, 64)
+	return writeWordsBraced(w, bigIntToU64s(curve, n), formatU64)
+}
+
+func writeU32Mont(w *columnWriter, curve elliptic.Curve, n *big.Int) error {
+	n = toMontgomery(n, curve.Params().P, 32)
+	return writeWordsBraced(w, bigIntToU32s(curve, n), formatU32)
+}
+
+type writeBigIntFunc func(w *columnWriter, curve elliptic.Curve, n *big.Int) error
+
+func writeTable(w *columnWriter, curve elliptic.Curve, table [][2]*big.Int, writeBigInt writeBigIntFunc) error {
 	if _, err := io.WriteString(w, "{"); err != nil {
 		return err
 	}
-	if isRoot {
-		if _, err := io.WriteString(w, "\n"); err != nil {
-			return err
-		}
-		if err := writeIndent(w, indent); err != nil {
-			return err
-		}
-	} else {
-		indent++
-	}
+	indent := w.column
 	for i, point := range table {
 		if i != 0 {
 			if _, err := io.WriteString(w, ",\n"); err != nil {
@@ -352,7 +539,7 @@
 		if _, err := io.WriteString(w, "{"); err != nil {
 			return err
 		}
-		if err := writeBigInt(w, curve, point[0], indent+1); err != nil {
+		if err := writeBigInt(w, curve, point[0]); err != nil {
 			return err
 		}
 		if _, err := io.WriteString(w, ",\n"); err != nil {
@@ -361,7 +548,7 @@
 		if err := writeIndent(w, indent+1); err != nil {
 			return err
 		}
-		if err := writeBigInt(w, curve, point[1], indent+1); err != nil {
+		if err := writeBigInt(w, curve, point[1]); err != nil {
 			return err
 		}
 		if _, err := io.WriteString(w, "}"); err != nil {
@@ -374,20 +561,11 @@
 	return nil
 }
 
-func writeTables(w io.Writer, curve elliptic.Curve, tables [][][2]*big.Int, isRoot bool, indent int, writeBigInt writeBigIntFunc) error {
-	if _, err := io.WriteString(w, "{"); err != nil {
+func writeTables(w *columnWriter, curve elliptic.Curve, tables [][][2]*big.Int, writeBigInt writeBigIntFunc) error {
+	if _, err := io.WriteString(w, "{\n    "); err != nil {
 		return err
 	}
-	if isRoot {
-		if _, err := io.WriteString(w, "\n"); err != nil {
-			return err
-		}
-		if err := writeIndent(w, indent); err != nil {
-			return err
-		}
-	} else {
-		indent++
-	}
+	indent := w.column
 	for i, table := range tables {
 		if i != 0 {
 			if _, err := io.WriteString(w, ",\n"); err != nil {
@@ -397,7 +575,7 @@
 				return err
 			}
 		}
-		if err := writeTable(w, curve, table, false, indent, writeBigInt); err != nil {
+		if err := writeTable(w, curve, table, writeBigInt); err != nil {
 			return err
 		}
 	}
diff --git a/include/openssl/ec.h b/include/openssl/ec.h
index dd5259b..f1a77b2 100644
--- a/include/openssl/ec.h
+++ b/include/openssl/ec.h
@@ -101,8 +101,24 @@
 
 // Elliptic curve groups.
 
-// EC_GROUP_new_by_curve_name returns a fresh EC_GROUP object for the elliptic
-// curve specified by |nid|, or NULL on unsupported NID or allocation failure.
+// EC_group_p224 returns an |EC_GROUP| for P-224, also known as secp224r1.
+OPENSSL_EXPORT const EC_GROUP *EC_group_p224(void);
+
+// EC_group_p256 returns an |EC_GROUP| for P-256, also known as secp256r1 or
+// prime256v1.
+OPENSSL_EXPORT const EC_GROUP *EC_group_p256(void);
+
+// EC_group_p384 returns an |EC_GROUP| for P-384, also known as secp384r1.
+OPENSSL_EXPORT const EC_GROUP *EC_group_p384(void);
+
+// EC_group_p521 returns an |EC_GROUP| for P-521, also known as secp521r1.
+OPENSSL_EXPORT const EC_GROUP *EC_group_p521(void);
+
+// EC_GROUP_new_by_curve_name returns the |EC_GROUP| object for the elliptic
+// curve specified by |nid|, or NULL on unsupported NID.  For OpenSSL
+// compatibility, this function returns a non-const pointer which may be passed
+// to |EC_GROUP_free|. However, the resulting object is actually static and
+// calling |EC_GROUP_free| is optional.
 //
 // The supported NIDs are:
 //   NID_secp224r1 (P-224),
@@ -110,6 +126,9 @@
 //   NID_secp384r1 (P-384),
 //   NID_secp521r1 (P-521)
 //
+// Calling this function causes all four curves to be linked into the binary.
+// Prefer calling |EC_group_*| to allow the static linker to drop unused curves.
+//
 // If in doubt, use |NID_X9_62_prime256v1|, or see the curve25519.h header for
 // more modern primitives.
 OPENSSL_EXPORT EC_GROUP *EC_GROUP_new_by_curve_name(int nid);
diff --git a/include/openssl/ec_key.h b/include/openssl/ec_key.h
index 00986cf..b7bc74c 100644
--- a/include/openssl/ec_key.h
+++ b/include/openssl/ec_key.h
@@ -259,8 +259,15 @@
                                               unsigned enc_flags);
 
 // EC_KEY_parse_curve_name parses a DER-encoded OBJECT IDENTIFIER as a curve
-// name from |cbs| and advances |cbs|. It returns a newly-allocated |EC_GROUP|
-// or NULL on error.
+// name from |cbs| and advances |cbs|. It returns the decoded |EC_GROUP| or NULL
+// on error.
+//
+// This function returns a non-const pointer which may be passed to
+// |EC_GROUP_free|. However, the resulting object is actually static and calling
+// |EC_GROUP_free| is optional.
+//
+// TODO(davidben): Make this return a const pointer, if it does not break too
+// many callers.
 OPENSSL_EXPORT EC_GROUP *EC_KEY_parse_curve_name(CBS *cbs);
 
 // EC_KEY_marshal_curve_name marshals |group| as a DER-encoded OBJECT IDENTIFIER
@@ -269,10 +276,16 @@
 OPENSSL_EXPORT int EC_KEY_marshal_curve_name(CBB *cbb, const EC_GROUP *group);
 
 // EC_KEY_parse_parameters parses a DER-encoded ECParameters structure (RFC
-// 5480) from |cbs| and advances |cbs|. It returns a newly-allocated |EC_GROUP|
-// or NULL on error. It supports the namedCurve and specifiedCurve options, but
-// use of specifiedCurve is deprecated. Use |EC_KEY_parse_curve_name|
-// instead.
+// 5480) from |cbs| and advances |cbs|. It returns the resulting |EC_GROUP| or
+// NULL on error. It supports the namedCurve and specifiedCurve options, but use
+// of specifiedCurve is deprecated. Use |EC_KEY_parse_curve_name| instead.
+//
+// This function returns a non-const pointer which may be passed to
+// |EC_GROUP_free|. However, the resulting object is actually static and calling
+// |EC_GROUP_free| is optional.
+//
+// TODO(davidben): Make this return a const pointer, if it does not break too
+// many callers.
 OPENSSL_EXPORT EC_GROUP *EC_KEY_parse_parameters(CBS *cbs);