Don't store a redundant copy of the EC_GROUP field modulus

One less value to initialize statically. Also this simplifies EC_GROUP
initialization. While I'm here, reorder EC_GROUP to pad better.

This lets us simplify the init bits slightly. It does mean p224-64.c,
the one EC_GROUP that doesn't use Montgomery reduction, carries around a
wasted Montgomery context, but it'll make generating the tables
statically much easier. Also once the data is pre-generated, the cost is
minimal.

Bug: 20
Change-Id: Ib66e655ce5a0902ab3ed6695fcbb46aa87683885
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/60928
Reviewed-by: Adam Langley <agl@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/crypto/ec_extra/hash_to_curve.c b/crypto/ec_extra/hash_to_curve.c
index 433205c..380bc2e 100644
--- a/crypto/ec_extra/hash_to_curve.c
+++ b/crypto/ec_extra/hash_to_curve.c
@@ -179,12 +179,12 @@
                           size_t msg_len) {
   size_t L;
   uint8_t buf[4 * EC_MAX_BYTES];
-  if (!num_bytes_to_derive(&L, &group->field, k) ||
+  if (!num_bytes_to_derive(&L, &group->field->N, k) ||
       !expand_message_xmd(md, buf, 2 * L, msg, msg_len, dst, dst_len)) {
     return 0;
   }
   BN_ULONG words[2 * EC_MAX_WORDS];
-  size_t num_words = 2 * group->field.width;
+  size_t num_words = 2 * group->field->N.width;
   big_endian_to_words(words, num_words, buf, L);
   group->meth->felem_reduce(group, out1, words, num_words);
   big_endian_to_words(words, num_words, buf + L, L);
@@ -231,7 +231,7 @@
 }
 
 OPENSSL_UNUSED static int is_3mod4(const EC_GROUP *group) {
-  return group->field.width > 0 && (group->field.d[0] & 3) == 3;
+  return group->field->N.width > 0 && (group->field->N.d[0] & 3) == 3;
 }
 
 // sqrt_ratio_3mod4 implements the operation described in appendix F.2.1.2
@@ -354,8 +354,8 @@
 
   // Compute |c1| = (p - 3) / 4.
   BN_ULONG c1[EC_MAX_WORDS];
-  size_t num_c1 = group->field.width;
-  if (!bn_copy_words(c1, num_c1, &group->field)) {
+  size_t num_c1 = group->field->N.width;
+  if (!bn_copy_words(c1, num_c1, &group->field->N)) {
     return 0;
   }
   bn_rshift_words(c1, c1, /*shift=*/2, /*num=*/num_c1);
@@ -371,7 +371,7 @@
 
 static int felem_from_u8(const EC_GROUP *group, EC_FELEM *out, uint8_t a) {
   uint8_t bytes[EC_MAX_BYTES] = {0};
-  size_t len = BN_num_bytes(&group->field);
+  size_t len = BN_num_bytes(&group->field->N);
   bytes[len - 1] = a;
   return ec_felem_from_bytes(group, out, bytes, len);
 }
diff --git a/crypto/fipsmodule/ec/ec.c b/crypto/fipsmodule/ec/ec.c
index 6f2a44c..1537491 100644
--- a/crypto/fipsmodule/ec/ec.c
+++ b/crypto/fipsmodule/ec/ec.c
@@ -270,20 +270,9 @@
 #endif
 }
 
-EC_GROUP *ec_group_new(const EC_METHOD *meth) {
-  EC_GROUP *ret;
-
-  if (meth == NULL) {
-    OPENSSL_PUT_ERROR(EC, EC_R_SLOT_FULL);
-    return NULL;
-  }
-
-  if (meth->group_init == 0) {
-    OPENSSL_PUT_ERROR(EC, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
-    return NULL;
-  }
-
-  ret = OPENSSL_malloc(sizeof(EC_GROUP));
+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;
   }
@@ -292,8 +281,8 @@
   ret->references = 1;
   ret->meth = meth;
 
-  if (!meth->group_init(ret)) {
-    OPENSSL_free(ret);
+  if (!ec_GFp_simple_group_set_curve(ret, p, a, b, ctx)) {
+    EC_GROUP_free(ret);
     return NULL;
   }
 
@@ -310,7 +299,7 @@
     return 0;
   }
 
-  group->field_greater_than_order = BN_cmp(&group->field, order) > 0;
+  group->field_greater_than_order = BN_cmp(&group->field->N, order) > 0;
 
   group->generator = EC_POINT_new(group);
   if (group->generator == NULL) {
@@ -355,11 +344,8 @@
     goto err;
   }
 
-  ret = ec_group_new(EC_GFp_mont_method());
-  if (ret == NULL ||
-      !ret->meth->group_set_curve(ret, p, a_reduced, b_reduced, ctx)) {
-    EC_GROUP_free(ret);
-    ret = NULL;
+  ret = ec_group_new(EC_GFp_mont_method(), p, a_reduced, b_reduced, ctx);
+  if (ret == NULL) {
     goto err;
   }
 
@@ -403,7 +389,7 @@
       !BN_lshift1(tmp, order)) {
     goto err;
   }
-  if (BN_cmp(tmp, &group->field) <= 0) {
+  if (BN_cmp(tmp, &group->field->N) <= 0) {
     OPENSSL_PUT_ERROR(EC, EC_R_INVALID_GROUP_ORDER);
     goto err;
   }
@@ -442,9 +428,8 @@
     goto err;
   }
 
-  group = ec_group_new(curve->method);
-  if (group == NULL ||
-      !group->meth->group_set_curve(group, p, a, b, ctx)) {
+  group = ec_group_new(curve->method, p, a, b, ctx);
+  if (group == NULL) {
     OPENSSL_PUT_ERROR(EC, ERR_R_EC_LIB);
     goto err;
   }
@@ -453,11 +438,8 @@
   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)) {
-    goto err;
-  }
-
-  if (!ec_group_set_generator(group, &G, order)) {
+      !ec_point_set_affine_coordinates(group, &G, &x, &y) ||
+      !ec_group_set_generator(group, &G, order)) {
     goto err;
   }
 
@@ -539,13 +521,9 @@
     return;
   }
 
-  if (group->meth->group_finish != NULL) {
-    group->meth->group_finish(group);
-  }
-
   ec_point_free(group->generator, 0 /* don't free group */);
   BN_MONT_CTX_free(group->order);
-
+  BN_MONT_CTX_free(group->field);
   OPENSSL_free(group);
 }
 
@@ -584,7 +562,7 @@
          a->generator == NULL ||
          b->generator == NULL ||
          BN_cmp(&a->order->N, &b->order->N) != 0 ||
-         BN_cmp(&a->field, &b->field) != 0 ||
+         BN_cmp(&a->field->N, &b->field->N) != 0 ||
          !ec_felem_equal(a, &a->a, &b->a) ||
          !ec_felem_equal(a, &a->b, &b->b) ||
          !ec_GFp_simple_points_equal(a, &a->generator->raw, &b->generator->raw);
@@ -624,7 +602,7 @@
 int EC_GROUP_get_curve_name(const EC_GROUP *group) { return group->curve_name; }
 
 unsigned EC_GROUP_get_degree(const EC_GROUP *group) {
-  return BN_num_bits(&group->field);
+  return BN_num_bits(&group->field->N);
 }
 
 const char *EC_curve_nid2nist(int nid) {
@@ -1184,7 +1162,7 @@
 int ec_get_x_coordinate_as_bytes(const EC_GROUP *group, uint8_t *out,
                                  size_t *out_len, size_t max_out,
                                  const EC_JACOBIAN *p) {
-  size_t len = BN_num_bytes(&group->field);
+  size_t len = BN_num_bytes(&group->field->N);
   assert(len <= EC_MAX_BYTES);
   if (max_out < len) {
     OPENSSL_PUT_ERROR(EC, EC_R_BUFFER_TOO_SMALL);
diff --git a/crypto/fipsmodule/ec/ec_montgomery.c b/crypto/fipsmodule/ec/ec_montgomery.c
index 8d99238..74c71be 100644
--- a/crypto/fipsmodule/ec/ec_montgomery.c
+++ b/crypto/fipsmodule/ec/ec_montgomery.c
@@ -76,67 +76,35 @@
 #include "internal.h"
 
 
-int ec_GFp_mont_group_init(EC_GROUP *group) {
-  int ok;
-
-  ok = ec_GFp_simple_group_init(group);
-  group->mont = NULL;
-  return ok;
-}
-
-void ec_GFp_mont_group_finish(EC_GROUP *group) {
-  BN_MONT_CTX_free(group->mont);
-  group->mont = NULL;
-  ec_GFp_simple_group_finish(group);
-}
-
-int ec_GFp_mont_group_set_curve(EC_GROUP *group, const BIGNUM *p,
-                                const BIGNUM *a, const BIGNUM *b, BN_CTX *ctx) {
-  BN_MONT_CTX_free(group->mont);
-  group->mont = BN_MONT_CTX_new_for_modulus(p, ctx);
-  if (group->mont == NULL) {
-    OPENSSL_PUT_ERROR(EC, ERR_R_BN_LIB);
-    return 0;
-  }
-
-  if (!ec_GFp_simple_group_set_curve(group, p, a, b, ctx)) {
-    BN_MONT_CTX_free(group->mont);
-    group->mont = NULL;
-    return 0;
-  }
-
-  return 1;
-}
-
 static void ec_GFp_mont_felem_to_montgomery(const EC_GROUP *group,
                                             EC_FELEM *out, const EC_FELEM *in) {
-  bn_to_montgomery_small(out->words, in->words, group->field.width,
-                         group->mont);
+  bn_to_montgomery_small(out->words, in->words, group->field->N.width,
+                         group->field);
 }
 
 static void ec_GFp_mont_felem_from_montgomery(const EC_GROUP *group,
                                               EC_FELEM *out,
                                               const EC_FELEM *in) {
-  bn_from_montgomery_small(out->words, group->field.width, in->words,
-                           group->field.width, group->mont);
+  bn_from_montgomery_small(out->words, group->field->N.width, in->words,
+                           group->field->N.width, group->field);
 }
 
 static void ec_GFp_mont_felem_inv0(const EC_GROUP *group, EC_FELEM *out,
                                    const EC_FELEM *a) {
-  bn_mod_inverse0_prime_mont_small(out->words, a->words, group->field.width,
-                                   group->mont);
+  bn_mod_inverse0_prime_mont_small(out->words, a->words, group->field->N.width,
+                                   group->field);
 }
 
 void ec_GFp_mont_felem_mul(const EC_GROUP *group, EC_FELEM *r,
                            const EC_FELEM *a, const EC_FELEM *b) {
-  bn_mod_mul_montgomery_small(r->words, a->words, b->words, group->field.width,
-                              group->mont);
+  bn_mod_mul_montgomery_small(r->words, a->words, b->words,
+                              group->field->N.width, group->field);
 }
 
 void ec_GFp_mont_felem_sqr(const EC_GROUP *group, EC_FELEM *r,
                            const EC_FELEM *a) {
-  bn_mod_mul_montgomery_small(r->words, a->words, a->words, group->field.width,
-                              group->mont);
+  bn_mod_mul_montgomery_small(r->words, a->words, a->words,
+                              group->field->N.width, group->field);
 }
 
 void ec_GFp_mont_felem_to_bytes(const EC_GROUP *group, uint8_t *out,
@@ -159,8 +127,8 @@
 void ec_GFp_mont_felem_reduce(const EC_GROUP *group, EC_FELEM *out,
                               const BN_ULONG *words, size_t num) {
   // Convert "from" Montgomery form so the value is reduced mod p.
-  bn_from_montgomery_small(out->words, group->field.width, words, num,
-                           group->mont);
+  bn_from_montgomery_small(out->words, group->field->N.width, words, num,
+                           group->field);
   // Convert "to" Montgomery form to remove the R^-1 factor added.
   ec_GFp_mont_felem_to_montgomery(group, out, out);
   // Convert to Montgomery form to match this implementation's representation.
@@ -170,8 +138,8 @@
 void ec_GFp_mont_felem_exp(const EC_GROUP *group, EC_FELEM *out,
                            const EC_FELEM *a, const BN_ULONG *exp,
                            size_t num_exp) {
-  bn_mod_exp_mont_small(out->words, a->words, group->field.width, exp, num_exp,
-                        group->mont);
+  bn_mod_exp_mont_small(out->words, a->words, group->field->N.width, exp,
+                        num_exp, group->field);
 }
 
 static int ec_GFp_mont_point_get_affine_coordinates(const EC_GROUP *group,
@@ -457,7 +425,7 @@
                                         const EC_JACOBIAN *p,
                                         const EC_SCALAR *r) {
   if (!group->field_greater_than_order ||
-      group->field.width != group->order->N.width) {
+      group->field->N.width != group->order->N.width) {
     // Do not bother optimizing this case. p > order in all commonly-used
     // curves.
     return ec_GFp_simple_cmp_x_coordinate(group, p, r);
@@ -473,7 +441,8 @@
   EC_FELEM r_Z2, Z2_mont, X;
   ec_GFp_mont_felem_mul(group, &Z2_mont, &p->Z, &p->Z);
   // r < order < p, so this is valid.
-  OPENSSL_memcpy(r_Z2.words, r->words, group->field.width * sizeof(BN_ULONG));
+  OPENSSL_memcpy(r_Z2.words, r->words,
+                 group->field->N.width * sizeof(BN_ULONG));
   ec_GFp_mont_felem_mul(group, &r_Z2, &r_Z2, &Z2_mont);
   ec_GFp_mont_felem_from_montgomery(group, &X, &p->X);
 
@@ -485,10 +454,10 @@
   // Therefore there is a small possibility, less than 1/2^128, that group_order
   // < p.x < P. in that case we need not only to compare against |r| but also to
   // compare against r+group_order.
-  BN_ULONG carry =
-      bn_add_words(r_Z2.words, r->words, group->order->N.d, group->field.width);
-  if (carry == 0 &&
-      bn_less_than_words(r_Z2.words, group->field.d, group->field.width)) {
+  BN_ULONG carry = bn_add_words(r_Z2.words, r->words, group->order->N.d,
+                                group->field->N.width);
+  if (carry == 0 && bn_less_than_words(r_Z2.words, group->field->N.d,
+                                       group->field->N.width)) {
     // r + group_order < p, so compare (r + group_order) * Z^2 against X.
     ec_GFp_mont_felem_mul(group, &r_Z2, &r_Z2, &Z2_mont);
     if (ec_felem_equal(group, &r_Z2, &X)) {
@@ -500,9 +469,6 @@
 }
 
 DEFINE_METHOD_FUNCTION(EC_METHOD, EC_GFp_mont_method) {
-  out->group_init = ec_GFp_mont_group_init;
-  out->group_finish = ec_GFp_mont_group_finish;
-  out->group_set_curve = ec_GFp_mont_group_set_curve;
   out->point_get_affine_coordinates = ec_GFp_mont_point_get_affine_coordinates;
   out->jacobian_to_affine_batch = ec_GFp_mont_jacobian_to_affine_batch;
   out->add = ec_GFp_mont_add;
diff --git a/crypto/fipsmodule/ec/felem.c b/crypto/fipsmodule/ec/felem.c
index e462514..e211f69 100644
--- a/crypto/fipsmodule/ec/felem.c
+++ b/crypto/fipsmodule/ec/felem.c
@@ -25,10 +25,10 @@
 
 int ec_bignum_to_felem(const EC_GROUP *group, EC_FELEM *out, const BIGNUM *in) {
   uint8_t bytes[EC_MAX_BYTES];
-  size_t len = BN_num_bytes(&group->field);
+  size_t len = BN_num_bytes(&group->field->N);
   assert(sizeof(bytes) >= len);
   if (BN_is_negative(in) ||
-      BN_cmp(in, &group->field) >= 0 ||
+      BN_cmp(in, &group->field->N) >= 0 ||
       !BN_bn2bin_padded(bytes, len, in)) {
     OPENSSL_PUT_ERROR(EC, EC_R_COORDINATES_OUT_OF_RANGE);
     return 0;
@@ -57,11 +57,11 @@
 void ec_felem_neg(const EC_GROUP *group, EC_FELEM *out, const EC_FELEM *a) {
   // -a is zero if a is zero and p-a otherwise.
   BN_ULONG mask = ec_felem_non_zero_mask(group, a);
-  BN_ULONG borrow =
-      bn_sub_words(out->words, group->field.d, a->words, group->field.width);
+  BN_ULONG borrow = bn_sub_words(out->words, group->field->N.d, a->words,
+                                 group->field->N.width);
   assert(borrow == 0);
   (void)borrow;
-  for (int i = 0; i < group->field.width; i++) {
+  for (int i = 0; i < group->field->N.width; i++) {
     out->words[i] &= mask;
   }
 }
@@ -69,20 +69,20 @@
 void ec_felem_add(const EC_GROUP *group, EC_FELEM *out, const EC_FELEM *a,
                   const EC_FELEM *b) {
   EC_FELEM tmp;
-  bn_mod_add_words(out->words, a->words, b->words, group->field.d, tmp.words,
-                   group->field.width);
+  bn_mod_add_words(out->words, a->words, b->words, group->field->N.d, tmp.words,
+                   group->field->N.width);
 }
 
 void ec_felem_sub(const EC_GROUP *group, EC_FELEM *out, const EC_FELEM *a,
                   const EC_FELEM *b) {
   EC_FELEM tmp;
-  bn_mod_sub_words(out->words, a->words, b->words, group->field.d, tmp.words,
-                   group->field.width);
+  bn_mod_sub_words(out->words, a->words, b->words, group->field->N.d, tmp.words,
+                   group->field->N.width);
 }
 
 BN_ULONG ec_felem_non_zero_mask(const EC_GROUP *group, const EC_FELEM *a) {
   BN_ULONG mask = 0;
-  for (int i = 0; i < group->field.width; i++) {
+  for (int i = 0; i < group->field->N.width; i++) {
     mask |= a->words[i];
   }
   return ~constant_time_is_zero_w(mask);
@@ -90,11 +90,11 @@
 
 void ec_felem_select(const EC_GROUP *group, EC_FELEM *out, BN_ULONG mask,
                      const EC_FELEM *a, const EC_FELEM *b) {
-  bn_select_words(out->words, mask, a->words, b->words, group->field.width);
+  bn_select_words(out->words, mask, a->words, b->words, group->field->N.width);
 }
 
 int ec_felem_equal(const EC_GROUP *group, const EC_FELEM *a,
                    const EC_FELEM *b) {
   return CRYPTO_memcmp(a->words, b->words,
-                       group->field.width * sizeof(BN_ULONG)) == 0;
+                       group->field->N.width * sizeof(BN_ULONG)) == 0;
 }
diff --git a/crypto/fipsmodule/ec/internal.h b/crypto/fipsmodule/ec/internal.h
index 4676aa5..dc86684 100644
--- a/crypto/fipsmodule/ec/internal.h
+++ b/crypto/fipsmodule/ec/internal.h
@@ -472,11 +472,6 @@
 // Implementation details.
 
 struct ec_method_st {
-  int (*group_init)(EC_GROUP *);
-  void (*group_finish)(EC_GROUP *);
-  int (*group_set_curve)(EC_GROUP *, const BIGNUM *p, const BIGNUM *a,
-                         const BIGNUM *b, BN_CTX *);
-
   // point_get_affine_coordinates sets |*x| and |*y| to the affine coordinates
   // of |p|. Either |x| or |y| may be NULL to omit it. It returns one on success
   // and zero if |p| is the point at infinity. It leaks whether |p| was the
@@ -596,16 +591,13 @@
   // and Y are suitable for use as an |EC_AFFINE|.
   EC_POINT *generator;
 
-  int curve_name;  // optional NID for named curve
-
   BN_MONT_CTX *order;
-
-  // The following members are handled by the method functions,
-  // even if they appear generic
-
-  BIGNUM field;  // For curves over GF(p), this is the modulus.
+  BN_MONT_CTX *field;
 
   EC_FELEM a, b;  // Curve coefficients.
+  EC_FELEM one;  // The value one.
+
+  int curve_name;  // optional NID for named curve
 
   // a_is_minus3 is one if |a| is -3 mod |field| and zero otherwise. Point
   // arithmetic is optimized for -3.
@@ -616,10 +608,6 @@
   int field_greater_than_order;
 
   CRYPTO_refcount_t references;
-
-  BN_MONT_CTX *mont;  // Montgomery structure.
-
-  EC_FELEM one;  // The value one.
 } /* EC_GROUP */;
 
 struct ec_point_st {
@@ -633,7 +621,8 @@
   EC_JACOBIAN raw;
 } /* EC_POINT */;
 
-EC_GROUP *ec_group_new(const EC_METHOD *meth);
+EC_GROUP *ec_group_new(const EC_METHOD *meth, const BIGNUM *p, const BIGNUM *a,
+                       const BIGNUM *b, BN_CTX *ctx);
 
 void ec_GFp_mont_mul(const EC_GROUP *group, EC_JACOBIAN *r,
                      const EC_JACOBIAN *p, const EC_SCALAR *scalar);
@@ -672,8 +661,6 @@
                                  const EC_SCALAR *scalars, size_t num);
 
 // method functions in simple.c
-int ec_GFp_simple_group_init(EC_GROUP *);
-void ec_GFp_simple_group_finish(EC_GROUP *);
 int ec_GFp_simple_group_set_curve(EC_GROUP *, const BIGNUM *p, const BIGNUM *a,
                                   const BIGNUM *b, BN_CTX *);
 int ec_GFp_simple_group_get_curve(const EC_GROUP *, BIGNUM *p, BIGNUM *a,
@@ -705,10 +692,6 @@
                                    const uint8_t *in, size_t len);
 
 // method functions in montgomery.c
-int ec_GFp_mont_group_init(EC_GROUP *);
-int ec_GFp_mont_group_set_curve(EC_GROUP *, const BIGNUM *p, const BIGNUM *a,
-                                const BIGNUM *b, BN_CTX *);
-void ec_GFp_mont_group_finish(EC_GROUP *);
 void ec_GFp_mont_felem_mul(const EC_GROUP *, EC_FELEM *r, const EC_FELEM *a,
                            const EC_FELEM *b);
 void ec_GFp_mont_felem_sqr(const EC_GROUP *, EC_FELEM *r, const EC_FELEM *a);
diff --git a/crypto/fipsmodule/ec/oct.c b/crypto/fipsmodule/ec/oct.c
index eb77643..70e8956 100644
--- a/crypto/fipsmodule/ec/oct.c
+++ b/crypto/fipsmodule/ec/oct.c
@@ -80,7 +80,7 @@
     return 0;
   }
 
-  const size_t field_len = BN_num_bytes(&group->field);
+  const size_t field_len = BN_num_bytes(&group->field->N);
   size_t output_len = 1 /* type byte */ + field_len;
   if (form == POINT_CONVERSION_UNCOMPRESSED) {
     // Uncompressed points have a second coordinate.
@@ -100,11 +100,11 @@
 
   size_t field_len;
   ec_felem_to_bytes(group, buf + 1, &field_len, &point->X);
-  assert(field_len == BN_num_bytes(&group->field));
+  assert(field_len == BN_num_bytes(&group->field->N));
 
   if (form == POINT_CONVERSION_UNCOMPRESSED) {
     ec_felem_to_bytes(group, buf + 1 + field_len, &field_len, &point->Y);
-    assert(field_len == BN_num_bytes(&group->field));
+    assert(field_len == BN_num_bytes(&group->field->N));
     buf[0] = form;
   } else {
     uint8_t y_buf[EC_MAX_BYTES];
@@ -117,7 +117,7 @@
 
 int ec_point_from_uncompressed(const EC_GROUP *group, EC_AFFINE *out,
                                const uint8_t *in, size_t len) {
-  const size_t field_len = BN_num_bytes(&group->field);
+  const size_t field_len = BN_num_bytes(&group->field->N);
   if (len != 1 + 2 * field_len || in[0] != POINT_CONVERSION_UNCOMPRESSED) {
     OPENSSL_PUT_ERROR(EC, EC_R_INVALID_ENCODING);
     return 0;
@@ -155,7 +155,7 @@
   }
 
   const int y_bit = form & 1;
-  const size_t field_len = BN_num_bytes(&group->field);
+  const size_t field_len = BN_num_bytes(&group->field->N);
   form = form & ~1u;
   if (form != POINT_CONVERSION_COMPRESSED ||
       len != 1 /* type byte */ + field_len) {
@@ -182,7 +182,7 @@
   if (x == NULL || !BN_bin2bn(buf + 1, field_len, x)) {
     goto err;
   }
-  if (BN_ucmp(x, &group->field) >= 0) {
+  if (BN_ucmp(x, &group->field->N) >= 0) {
     OPENSSL_PUT_ERROR(EC, EC_R_INVALID_ENCODING);
     goto err;
   }
@@ -260,7 +260,8 @@
     return 0;
   }
 
-  if (BN_is_negative(x) || BN_cmp(x, &group->field) >= 0) {
+  const BIGNUM *field = &group->field->N;
+  if (BN_is_negative(x) || BN_cmp(x, field) >= 0) {
     OPENSSL_PUT_ERROR(EC, EC_R_INVALID_COMPRESSED_POINT);
     return 0;
   }
@@ -295,31 +296,31 @@
   // so  y  is one of the square roots of  x^3 + a*x + b.
 
   // tmp1 := x^3
-  if (!BN_mod_sqr(tmp2, x, &group->field, ctx) ||
-      !BN_mod_mul(tmp1, tmp2, x, &group->field, ctx)) {
+  if (!BN_mod_sqr(tmp2, x, field, ctx) ||
+      !BN_mod_mul(tmp1, tmp2, x, field, ctx)) {
     goto err;
   }
 
   // tmp1 := tmp1 + a*x
   if (group->a_is_minus3) {
-    if (!bn_mod_lshift1_consttime(tmp2, x, &group->field, ctx) ||
-        !bn_mod_add_consttime(tmp2, tmp2, x, &group->field, ctx) ||
-        !bn_mod_sub_consttime(tmp1, tmp1, tmp2, &group->field, ctx)) {
+    if (!bn_mod_lshift1_consttime(tmp2, x, field, ctx) ||
+        !bn_mod_add_consttime(tmp2, tmp2, x, field, ctx) ||
+        !bn_mod_sub_consttime(tmp1, tmp1, tmp2, field, ctx)) {
       goto err;
     }
   } else {
-    if (!BN_mod_mul(tmp2, a, x, &group->field, ctx) ||
-        !bn_mod_add_consttime(tmp1, tmp1, tmp2, &group->field, ctx)) {
+    if (!BN_mod_mul(tmp2, a, x, field, ctx) ||
+        !bn_mod_add_consttime(tmp1, tmp1, tmp2, field, ctx)) {
       goto err;
     }
   }
 
   // tmp1 := tmp1 + b
-  if (!bn_mod_add_consttime(tmp1, tmp1, b, &group->field, ctx)) {
+  if (!bn_mod_add_consttime(tmp1, tmp1, b, field, ctx)) {
     goto err;
   }
 
-  if (!BN_mod_sqrt(y, tmp1, &group->field, ctx)) {
+  if (!BN_mod_sqrt(y, tmp1, field, ctx)) {
     uint32_t err = ERR_peek_last_error();
     if (ERR_GET_LIB(err) == ERR_LIB_BN &&
         ERR_GET_REASON(err) == BN_R_NOT_A_SQUARE) {
@@ -336,7 +337,7 @@
       OPENSSL_PUT_ERROR(EC, EC_R_INVALID_COMPRESSION_BIT);
       goto err;
     }
-    if (!BN_usub(y, &group->field, y)) {
+    if (!BN_usub(y, field, y)) {
       goto err;
     }
   }
diff --git a/crypto/fipsmodule/ec/p224-64.c b/crypto/fipsmodule/ec/p224-64.c
index b646e82..9d0242c 100644
--- a/crypto/fipsmodule/ec/p224-64.c
+++ b/crypto/fipsmodule/ec/p224-64.c
@@ -1142,9 +1142,6 @@
 }
 
 DEFINE_METHOD_FUNCTION(EC_METHOD, EC_GFp_nistp224_method) {
-  out->group_init = ec_GFp_simple_group_init;
-  out->group_finish = ec_GFp_simple_group_finish;
-  out->group_set_curve = ec_GFp_simple_group_set_curve;
   out->point_get_affine_coordinates =
       ec_GFp_nistp224_point_get_affine_coordinates;
   out->add = ec_GFp_nistp224_add;
diff --git a/crypto/fipsmodule/ec/p256-nistz.c b/crypto/fipsmodule/ec/p256-nistz.c
index 8936a9c..9993941 100644
--- a/crypto/fipsmodule/ec/p256-nistz.c
+++ b/crypto/fipsmodule/ec/p256-nistz.c
@@ -191,7 +191,7 @@
                                       const EC_SCALAR *p_scalar) {
   assert(p != NULL);
   assert(p_scalar != NULL);
-  assert(group->field.width == P256_LIMBS);
+  assert(group->field->N.width == P256_LIMBS);
 
   static const size_t kWindowSize = 5;
   static const crypto_word_t kMask = (1 << (5 /* kWindowSize */ + 1)) - 1;
@@ -208,7 +208,7 @@
   // not stored. All other values are actually stored with an offset of -1 in
   // table.
   P256_POINT *row = table;
-  assert(group->field.width == P256_LIMBS);
+  assert(group->field->N.width == P256_LIMBS);
   OPENSSL_memcpy(row[1 - 1].X, p->X.words, P256_LIMBS * sizeof(BN_ULONG));
   OPENSSL_memcpy(row[1 - 1].Y, p->Y.words, P256_LIMBS * sizeof(BN_ULONG));
   OPENSSL_memcpy(row[1 - 1].Z, p->Z.words, P256_LIMBS * sizeof(BN_ULONG));
@@ -305,7 +305,7 @@
   alignas(32) P256_POINT out;
   ecp_nistz256_windowed_mul(group, &out, p, scalar);
 
-  assert(group->field.width == P256_LIMBS);
+  assert(group->field->N.width == P256_LIMBS);
   OPENSSL_memcpy(r->X.words, out.X, P256_LIMBS * sizeof(BN_ULONG));
   OPENSSL_memcpy(r->Y.words, out.Y, P256_LIMBS * sizeof(BN_ULONG));
   OPENSSL_memcpy(r->Z.words, out.Z, P256_LIMBS * sizeof(BN_ULONG));
@@ -349,7 +349,7 @@
     ecp_nistz256_point_add_affine(&p, &p, &t);
   }
 
-  assert(group->field.width == P256_LIMBS);
+  assert(group->field->N.width == P256_LIMBS);
   OPENSSL_memcpy(r->X.words, p.X, P256_LIMBS * sizeof(BN_ULONG));
   OPENSSL_memcpy(r->Y.words, p.Y, P256_LIMBS * sizeof(BN_ULONG));
   OPENSSL_memcpy(r->Z.words, p.Z, P256_LIMBS * sizeof(BN_ULONG));
@@ -413,7 +413,7 @@
   ecp_nistz256_windowed_mul(group, &tmp, p_, p_scalar);
   ecp_nistz256_point_add(&p, &p, &tmp);
 
-  assert(group->field.width == P256_LIMBS);
+  assert(group->field->N.width == P256_LIMBS);
   OPENSSL_memcpy(r->X.words, p.X, P256_LIMBS * sizeof(BN_ULONG));
   OPENSSL_memcpy(r->Y.words, p.Y, P256_LIMBS * sizeof(BN_ULONG));
   OPENSSL_memcpy(r->Z.words, p.Z, P256_LIMBS * sizeof(BN_ULONG));
@@ -429,7 +429,7 @@
   }
 
   BN_ULONG z_inv2[P256_LIMBS];
-  assert(group->field.width == P256_LIMBS);
+  assert(group->field->N.width == P256_LIMBS);
   ecp_nistz256_mod_inverse_sqr_mont(z_inv2, point->Z.words);
 
   if (x != NULL) {
@@ -581,7 +581,7 @@
   }
 
   assert(group->order->N.width == P256_LIMBS);
-  assert(group->field.width == P256_LIMBS);
+  assert(group->field->N.width == P256_LIMBS);
 
   // We wish to compare X/Z^2 with r. This is equivalent to comparing X with
   // r*Z^2. Note that X and Z are represented in Montgomery form, while r is
@@ -600,7 +600,7 @@
   // < p.x < P. in that case we need not only to compare against |r| but also to
   // compare against r+group_order.
   BN_ULONG carry = bn_add_words(r_Z2, r->words, group->order->N.d, P256_LIMBS);
-  if (carry == 0 && bn_less_than_words(r_Z2, group->field.d, P256_LIMBS)) {
+  if (carry == 0 && bn_less_than_words(r_Z2, group->field->N.d, P256_LIMBS)) {
     // r + group_order < p, so compare (r + group_order) * Z^2 against X.
     ecp_nistz256_mul_mont(r_Z2, r_Z2, Z2_mont);
     if (OPENSSL_memcmp(r_Z2, X, sizeof(r_Z2)) == 0) {
@@ -612,9 +612,6 @@
 }
 
 DEFINE_METHOD_FUNCTION(EC_METHOD, EC_GFp_nistz256_method) {
-  out->group_init = ec_GFp_mont_group_init;
-  out->group_finish = ec_GFp_mont_group_finish;
-  out->group_set_curve = ec_GFp_mont_group_set_curve;
   out->point_get_affine_coordinates = ecp_nistz256_get_affine;
   out->add = ecp_nistz256_add;
   out->dbl = ecp_nistz256_dbl;
diff --git a/crypto/fipsmodule/ec/p256.c b/crypto/fipsmodule/ec/p256.c
index ce9a57f..35b9d45 100644
--- a/crypto/fipsmodule/ec/p256.c
+++ b/crypto/fipsmodule/ec/p256.c
@@ -710,12 +710,12 @@
   // Therefore there is a small possibility, less than 1/2^128, that group_order
   // < p.x < P. in that case we need not only to compare against |r| but also to
   // compare against r+group_order.
-  assert(group->field.width == group->order->N.width);
+  assert(group->field->N.width == group->order->N.width);
   EC_FELEM tmp;
-  BN_ULONG carry =
-      bn_add_words(tmp.words, r->words, group->order->N.d, group->field.width);
+  BN_ULONG carry = bn_add_words(tmp.words, r->words, group->order->N.d,
+                                group->field->N.width);
   if (carry == 0 &&
-      bn_less_than_words(tmp.words, group->field.d, group->field.width)) {
+      bn_less_than_words(tmp.words, group->field->N.d, group->field->N.width)) {
     fiat_p256_from_generic(r_Z2, &tmp);
     fiat_p256_mul(r_Z2, r_Z2, Z2_mont);
     if (OPENSSL_memcmp(&r_Z2, &X, sizeof(r_Z2)) == 0) {
@@ -727,9 +727,6 @@
 }
 
 DEFINE_METHOD_FUNCTION(EC_METHOD, EC_GFp_nistp256_method) {
-  out->group_init = ec_GFp_mont_group_init;
-  out->group_finish = ec_GFp_mont_group_finish;
-  out->group_set_curve = ec_GFp_mont_group_set_curve;
   out->point_get_affine_coordinates =
       ec_GFp_nistp256_point_get_affine_coordinates;
   out->add = ec_GFp_nistp256_add;
diff --git a/crypto/fipsmodule/ec/simple.c b/crypto/fipsmodule/ec/simple.c
index 6498bdc..0b27040 100644
--- a/crypto/fipsmodule/ec/simple.c
+++ b/crypto/fipsmodule/ec/simple.c
@@ -88,16 +88,6 @@
 // used, it is a Montgomery representation (i.e. 'encoding' means multiplying
 // by some factor R).
 
-int ec_GFp_simple_group_init(EC_GROUP *group) {
-  BN_init(&group->field);
-  group->a_is_minus3 = 0;
-  return 1;
-}
-
-void ec_GFp_simple_group_finish(EC_GROUP *group) {
-  BN_free(&group->field);
-}
-
 int ec_GFp_simple_group_set_curve(EC_GROUP *group, const BIGNUM *p,
                                   const BIGNUM *a, const BIGNUM *b,
                                   BN_CTX *ctx) {
@@ -114,13 +104,10 @@
     goto err;
   }
 
-  // group->field
-  if (!BN_copy(&group->field, p)) {
+  group->field = BN_MONT_CTX_new_for_modulus(p, ctx);
+  if (group->field == NULL) {
     goto err;
   }
-  BN_set_negative(&group->field, 0);
-  // Store the field in minimal form, so it can be used with |BN_ULONG| arrays.
-  bn_set_minimal_width(&group->field);
 
   if (!ec_bignum_to_felem(group, &group->a, a) ||
       !ec_bignum_to_felem(group, &group->b, b) ||
@@ -133,7 +120,7 @@
       !BN_add_word(tmp, 3)) {
     goto err;
   }
-  group->a_is_minus3 = (0 == BN_cmp(tmp, &group->field));
+  group->a_is_minus3 = (0 == BN_cmp(tmp, &group->field->N));
 
   ret = 1;
 
@@ -144,7 +131,7 @@
 
 int ec_GFp_simple_group_get_curve(const EC_GROUP *group, BIGNUM *p, BIGNUM *a,
                                   BIGNUM *b) {
-  if ((p != NULL && !BN_copy(p, &group->field)) ||
+  if ((p != NULL && !BN_copy(p, &group->field->N)) ||
       (a != NULL && !ec_felem_to_bignum(group, a, &group->a)) ||
       (b != NULL && !ec_felem_to_bignum(group, b, &group->b))) {
     return 0;
@@ -329,21 +316,22 @@
 
 void ec_GFp_simple_felem_to_bytes(const EC_GROUP *group, uint8_t *out,
                                   size_t *out_len, const EC_FELEM *in) {
-  size_t len = BN_num_bytes(&group->field);
-  bn_words_to_big_endian(out, len, in->words, group->field.width);
+  size_t len = BN_num_bytes(&group->field->N);
+  bn_words_to_big_endian(out, len, in->words, group->field->N.width);
   *out_len = len;
 }
 
 int ec_GFp_simple_felem_from_bytes(const EC_GROUP *group, EC_FELEM *out,
                                    const uint8_t *in, size_t len) {
-  if (len != BN_num_bytes(&group->field)) {
+  if (len != BN_num_bytes(&group->field->N)) {
     OPENSSL_PUT_ERROR(EC, EC_R_DECODE_ERROR);
     return 0;
   }
 
-  bn_big_endian_to_words(out->words, group->field.width, in, len);
+  bn_big_endian_to_words(out->words, group->field->N.width, in, len);
 
-  if (!bn_less_than_words(out->words, group->field.d, group->field.width)) {
+  if (!bn_less_than_words(out->words, group->field->N.d,
+                          group->field->N.width)) {
     OPENSSL_PUT_ERROR(EC, EC_R_DECODE_ERROR);
     return 0;
   }
diff --git a/crypto/fipsmodule/ec/simple_mul.c b/crypto/fipsmodule/ec/simple_mul.c
index 62786e6..ce5b317 100644
--- a/crypto/fipsmodule/ec/simple_mul.c
+++ b/crypto/fipsmodule/ec/simple_mul.c
@@ -169,7 +169,7 @@
 }
 
 static unsigned ec_GFp_mont_comb_stride(const EC_GROUP *group) {
-  return (BN_num_bits(&group->field) + EC_MONT_PRECOMP_COMB_SIZE - 1) /
+  return (EC_GROUP_get_degree(group) + EC_MONT_PRECOMP_COMB_SIZE - 1) /
          EC_MONT_PRECOMP_COMB_SIZE;
 }
 
diff --git a/crypto/trust_token/pmbtoken.c b/crypto/trust_token/pmbtoken.c
index 93a0191..be093fc 100644
--- a/crypto/trust_token/pmbtoken.c
+++ b/crypto/trust_token/pmbtoken.c
@@ -160,7 +160,7 @@
       return 0;
     }
   } else {
-    size_t plen = 1 + 2 * BN_num_bytes(&group->field);
+    size_t plen = ec_point_byte_len(group, POINT_CONVERSION_UNCOMPRESSED);
     if (!CBS_get_bytes(cbs, &child, plen)) {
       return 0;
     }
@@ -912,7 +912,7 @@
   }
 
   // Skip over any unused requests.
-  size_t point_len = 1 + 2 * BN_num_bytes(&group->field);
+  size_t point_len = ec_point_byte_len(group, POINT_CONVERSION_UNCOMPRESSED);
   size_t token_len = point_len;
   if (method->prefix_point) {
     token_len += 2;
@@ -1015,7 +1015,7 @@
     // Serialize the token. Include |key_id| to avoid an extra copy in the layer
     // above.
     CBB token_cbb;
-    size_t point_len = 1 + 2 * BN_num_bytes(&group->field);
+    size_t point_len = ec_point_byte_len(group, POINT_CONVERSION_UNCOMPRESSED);
     if (!CBB_init(&token_cbb,
                   4 + TRUST_TOKEN_NONCE_SIZE + 3 * (2 + point_len)) ||
         !CBB_add_u32(&token_cbb, key_id) ||
diff --git a/crypto/trust_token/trust_token_test.cc b/crypto/trust_token/trust_token_test.cc
index df679b6..37fdc85 100644
--- a/crypto/trust_token/trust_token_test.cc
+++ b/crypto/trust_token/trust_token_test.cc
@@ -1080,15 +1080,14 @@
   ASSERT_TRUE(CBS_get_u32(&real_response, &parsed_public_metadata));
   ASSERT_TRUE(CBB_add_u32(bad_response.get(), parsed_public_metadata));
 
-  const EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp384r1);
-  size_t token_length =
-      TRUST_TOKEN_NONCE_SIZE + 2 * (1 + 2 * BN_num_bytes(&group->field));
+  const size_t kP384PointLen = 1 + 2 * (384 / 8);
+  size_t token_length = TRUST_TOKEN_NONCE_SIZE + 2 * kP384PointLen;
   if (method() == TRUST_TOKEN_experiment_v1()) {
     token_length += 4;
   }
   if (method() == TRUST_TOKEN_experiment_v2_voprf() ||
       method() == TRUST_TOKEN_pst_v1_voprf()) {
-    token_length = 1 + 2 * BN_num_bytes(&group->field);
+    token_length = kP384PointLen;
   }
   for (size_t i = 0; i < count; i++) {
     ASSERT_TRUE(CBB_add_bytes(bad_response.get(), CBS_data(&real_response),
@@ -1149,15 +1148,14 @@
   ASSERT_TRUE(CBS_get_u32(&real_response, &parsed_public_metadata));
   ASSERT_TRUE(CBB_add_u32(bad_response.get(), parsed_public_metadata));
 
-  const EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp384r1);
-  size_t token_length =
-      TRUST_TOKEN_NONCE_SIZE + 2 * (1 + 2 * BN_num_bytes(&group->field));
+  const size_t kP384PointLen = 1 + 2 * (384 / 8);
+  size_t token_length = TRUST_TOKEN_NONCE_SIZE + 2 * kP384PointLen;
   if (method() == TRUST_TOKEN_experiment_v1()) {
     token_length += 4;
   }
   if (method() == TRUST_TOKEN_experiment_v2_voprf() ||
       method() == TRUST_TOKEN_pst_v1_voprf()) {
-    token_length = 1 + 2 * BN_num_bytes(&group->field);
+    token_length = kP384PointLen;
   }
   for (size_t i = 0; i < count; i++) {
     ASSERT_TRUE(CBB_add_bytes(bad_response.get(), CBS_data(&real_response),
diff --git a/crypto/trust_token/voprf.c b/crypto/trust_token/voprf.c
index e62aca5..15939d3 100644
--- a/crypto/trust_token/voprf.c
+++ b/crypto/trust_token/voprf.c
@@ -83,7 +83,7 @@
 
 static int cbs_get_point(CBS *cbs, const EC_GROUP *group, EC_AFFINE *out) {
   CBS child;
-  size_t plen = 1 + 2 * BN_num_bytes(&group->field);
+  size_t plen = ec_point_byte_len(group, POINT_CONVERSION_UNCOMPRESSED);
   if (!CBS_get_bytes(cbs, &child, plen) ||
       !ec_point_from_uncompressed(group, out, CBS_data(&child),
                                   CBS_len(&child))) {
@@ -567,7 +567,7 @@
   }
 
   // Skip over any unused requests.
-  size_t point_len = 1 + 2 * BN_num_bytes(&group->field);
+  size_t point_len = ec_point_byte_len(group, POINT_CONVERSION_UNCOMPRESSED);
   if (!CBS_skip(cbs, point_len * (num_requested - num_to_issue))) {
     OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
     goto err;
@@ -645,7 +645,7 @@
     // Serialize the token. Include |key_id| to avoid an extra copy in the layer
     // above.
     CBB token_cbb;
-    size_t point_len = 1 + 2 * BN_num_bytes(&group->field);
+    size_t point_len = ec_point_byte_len(group, POINT_CONVERSION_UNCOMPRESSED);
     if (!CBB_init(&token_cbb, 4 + TRUST_TOKEN_NONCE_SIZE + (2 + point_len)) ||
         !CBB_add_u32(&token_cbb, key_id) ||
         !CBB_add_bytes(&token_cbb, pretoken->salt, TRUST_TOKEN_NONCE_SIZE) ||
@@ -944,7 +944,7 @@
   }
 
   // Skip over any unused requests.
-  size_t point_len = 1 + 2 * BN_num_bytes(&group->field);
+  size_t point_len = ec_point_byte_len(group, POINT_CONVERSION_UNCOMPRESSED);
   if (!CBS_skip(cbs, point_len * (num_requested - num_to_issue))) {
     OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
     goto err;
@@ -1044,7 +1044,7 @@
     // Serialize the token. Include |key_id| to avoid an extra copy in the layer
     // above.
     CBB token_cbb;
-    size_t point_len = 1 + 2 * BN_num_bytes(&group->field);
+    size_t point_len = ec_point_byte_len(group, POINT_CONVERSION_UNCOMPRESSED);
     if (!CBB_init(&token_cbb, 4 + TRUST_TOKEN_NONCE_SIZE + (2 + point_len)) ||
         !CBB_add_u32(&token_cbb, key_id) ||
         !CBB_add_bytes(&token_cbb, pretoken->salt, TRUST_TOKEN_NONCE_SIZE) ||