Introduce an EC_AFFINE abstraction.

PMBTokens ends up converting the same point to affine coordinates
repeatedly. Additionally, it converts many affine coordinates at once,
which we can batch. Introduce an EC_AFFINE type to store affine points
and move the inversion to the Jacobian -> affine conversion.

This does mean we lose the (negligible) Montgomery reduction
optimization in EC_GFp_mont. point_get_affine_coordinates no longer
breaks the EC_FELEM abstraction around Montgomery form.

Unfortunately, this complicates hardening of the callers not checking
return values because EC_AFFINE cannot represent the point at infinity
and, due to OpenSSL's API limitations, groups may not have generators
available and the generator is not affine at the type level. (EC_AFFINE
cannot represent the point at infinity.) Thus this CL:

- Tidies up some duplicate code in setting up the generator and ensures
  it always has Z = 1.
- ec_point_set_affine_coordinates hardens against unused results if the
  generator is configured. But this is ultimately an internal function.
- Retains the hardening on the public APIs by adding calls to
  ec_set_to_safe_point in two places.

This CL does not apply the optimization to Trust Tokens, only introduces
the EC_AFFINE abstraction. It additionally continues to store EC_POINTs
(used in ECDH and ECDSA) in Jacobian form. See
https://crbug.com/boringssl/326#c4 for a discussion on why this is
tricky. Those protocols are hopefully simple enough that they don't need
complexity around inversions.

Having an EC_AFFINE type will also be useful for computing custom tables
for Trust Token public keys, which gives a nice speedup.

Bug: 326
Change-Id: I11b010a33f36a15bac9939351df5205bd35cc665
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/41084
Commit-Queue: Steven Valdez <svaldez@google.com>
Reviewed-by: Steven Valdez <svaldez@google.com>
diff --git a/crypto/ecdh_extra/ecdh_extra.c b/crypto/ecdh_extra/ecdh_extra.c
index b8a099a..237d973 100644
--- a/crypto/ecdh_extra/ecdh_extra.c
+++ b/crypto/ecdh_extra/ecdh_extra.c
@@ -96,8 +96,8 @@
   uint8_t buf[EC_MAX_BYTES];
   size_t buf_len;
   if (!ec_point_mul_scalar(group, &shared_point, &pub_key->raw, priv) ||
-      !ec_point_get_affine_coordinate_bytes(group, buf, NULL, &buf_len,
-                                            sizeof(buf), &shared_point)) {
+      !ec_get_x_coordinate_as_bytes(group, buf, &buf_len, sizeof(buf),
+                                    &shared_point)) {
     OPENSSL_PUT_ERROR(ECDH, ECDH_R_POINT_ARITHMETIC_FAILURE);
     return -1;
   }
diff --git a/crypto/fipsmodule/ec/ec.c b/crypto/fipsmodule/ec/ec.c
index 186a2fe..56a9e1d 100644
--- a/crypto/fipsmodule/ec/ec.c
+++ b/crypto/fipsmodule/ec/ec.c
@@ -301,17 +301,49 @@
   return ret;
 }
 
-static void ec_group_set0_generator(EC_GROUP *group, EC_POINT *generator) {
+static int ec_group_set_generator(EC_GROUP *group, const EC_AFFINE *generator,
+                                  const BIGNUM *order) {
   assert(group->generator == NULL);
-  assert(group == generator->group);
+
+  if (!BN_copy(&group->order, order)) {
+    return 0;
+  }
+  // Store the order in minimal form, so it can be used with |BN_ULONG| arrays.
+  bn_set_minimal_width(&group->order);
+
+  BN_MONT_CTX_free(group->order_mont);
+  group->order_mont = BN_MONT_CTX_new_for_modulus(&group->order, NULL);
+  if (group->order_mont == NULL) {
+    return 0;
+  }
+
+  group->field_greater_than_order = BN_cmp(&group->field, order) > 0;
+  if (group->field_greater_than_order) {
+    BIGNUM tmp;
+    BN_init(&tmp);
+    int ok =
+        BN_sub(&tmp, &group->field, order) &&
+        bn_copy_words(group->field_minus_order.words, group->field.width, &tmp);
+    BN_free(&tmp);
+    if (!ok) {
+      return 0;
+    }
+  }
+
+  group->generator = EC_POINT_new(group);
+  if (group->generator == NULL) {
+    return 0;
+  }
+  ec_affine_to_jacobian(group, &group->generator->raw, generator);
+  assert(ec_felem_equal(group, &group->one, &group->generator->raw.Z));
 
   // Avoid a reference cycle. |group->generator| does not maintain an owning
   // pointer to |group|.
-  group->generator = generator;
   int is_zero = CRYPTO_refcount_dec_and_test_zero(&group->references);
 
   assert(!is_zero);
   (void)is_zero;
+  return 1;
 }
 
 EC_GROUP *EC_GROUP_new_curve_GFp(const BIGNUM *p, const BIGNUM *a,
@@ -384,7 +416,6 @@
   // tiny prime (less than 17). See the proof in |field_element_to_scalar| in
   // the ECDSA implementation.
   int ret = 0;
-  EC_POINT *copy = NULL;
   BIGNUM *tmp = BN_new();
   if (tmp == NULL ||
       !BN_lshift1(tmp, order)) {
@@ -395,44 +426,22 @@
     goto err;
   }
 
-  copy = EC_POINT_new(group);
-  if (copy == NULL ||
-      !EC_POINT_copy(copy, generator) ||
-      !BN_copy(&group->order, order)) {
-    goto err;
-  }
-  // Store the order in minimal form, so it can be used with |BN_ULONG| arrays.
-  bn_set_minimal_width(&group->order);
-
-  BN_MONT_CTX_free(group->order_mont);
-  group->order_mont = BN_MONT_CTX_new_for_modulus(&group->order, NULL);
-  if (group->order_mont == NULL) {
+  EC_AFFINE affine;
+  if (!ec_jacobian_to_affine(group, &affine, &generator->raw) ||
+      !ec_group_set_generator(group, &affine, order)) {
     goto err;
   }
 
-  group->field_greater_than_order = BN_cmp(&group->field, &group->order) > 0;
-  if (group->field_greater_than_order) {
-    if (!BN_sub(tmp, &group->field, &group->order) ||
-        !bn_copy_words(group->field_minus_order.words, group->field.width,
-                       tmp)) {
-      goto err;
-    }
-  }
-
-  ec_group_set0_generator(group, copy);
-  copy = NULL;
   ret = 1;
 
 err:
-  EC_POINT_free(copy);
   BN_free(tmp);
   return ret;
 }
 
 static EC_GROUP *ec_group_new_from_data(const struct built_in_curve *curve) {
   EC_GROUP *group = NULL;
-  EC_POINT *P = NULL;
-  BIGNUM *p = NULL, *a = NULL, *b = NULL, *x = NULL, *y = NULL;
+  BIGNUM *p = NULL, *a = NULL, *b = NULL, *order = NULL;
   int ok = 0;
 
   BN_CTX *ctx = BN_CTX_new();
@@ -446,7 +455,8 @@
 
   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))) {
+      !(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;
   }
@@ -458,42 +468,18 @@
     goto err;
   }
 
-  if ((P = EC_POINT_new(group)) == NULL) {
-    OPENSSL_PUT_ERROR(EC, ERR_R_EC_LIB);
+  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)) {
     goto err;
   }
 
-  if (!(x = BN_bin2bn(params + 3 * param_len, param_len, NULL)) ||
-      !(y = BN_bin2bn(params + 4 * param_len, param_len, NULL))) {
-    OPENSSL_PUT_ERROR(EC, ERR_R_BN_LIB);
+  if (!ec_group_set_generator(group, &G, order)) {
     goto err;
   }
 
-  if (!EC_POINT_set_affine_coordinates_GFp(group, P, x, y, ctx)) {
-    OPENSSL_PUT_ERROR(EC, ERR_R_EC_LIB);
-    goto err;
-  }
-  if (!BN_bin2bn(params + 5 * param_len, param_len, &group->order)) {
-    OPENSSL_PUT_ERROR(EC, ERR_R_BN_LIB);
-    goto err;
-  }
-
-  group->field_greater_than_order = BN_cmp(&group->field, &group->order) > 0;
-  if (group->field_greater_than_order) {
-    if (!BN_sub(p, &group->field, &group->order) ||
-        !bn_copy_words(group->field_minus_order.words, group->field.width, p)) {
-      goto err;
-    }
-  }
-
-  group->order_mont = BN_MONT_CTX_new_for_modulus(&group->order, ctx);
-  if (group->order_mont == NULL) {
-    OPENSSL_PUT_ERROR(EC, ERR_R_BN_LIB);
-    goto err;
-  }
-
-  ec_group_set0_generator(group, P);
-  P = NULL;
   ok = 1;
 
 err:
@@ -501,13 +487,11 @@
     EC_GROUP_free(group);
     group = NULL;
   }
-  EC_POINT_free(P);
   BN_CTX_free(ctx);
   BN_free(p);
   BN_free(a);
   BN_free(b);
-  BN_free(x);
-  BN_free(y);
+  BN_free(order);
   return group;
 }
 
@@ -806,14 +790,26 @@
   if (!group->meth->point_get_affine_coordinates(group, &point->raw,
                                                  x == NULL ? NULL : &x_felem,
                                                  y == NULL ? NULL : &y_felem) ||
-      (x != NULL && !bn_set_words(x, x_felem.words, group->field.width)) ||
-      (y != NULL && !bn_set_words(y, y_felem.words, group->field.width))) {
+      (x != NULL && !ec_felem_to_bignum(group, x, &x_felem)) ||
+      (y != NULL && !ec_felem_to_bignum(group, y, &y_felem))) {
     return 0;
   }
   return 1;
 }
 
-int ec_point_set_affine_coordinates(const EC_GROUP *group, EC_RAW_POINT *out,
+void ec_affine_to_jacobian(const EC_GROUP *group, EC_RAW_POINT *out,
+                           const EC_AFFINE *p) {
+  out->X = p->X;
+  out->Y = p->Y;
+  out->Z = group->one;
+}
+
+int ec_jacobian_to_affine(const EC_GROUP *group, EC_AFFINE *out,
+                          const EC_RAW_POINT *p) {
+  return group->meth->point_get_affine_coordinates(group, p, &out->X, &out->Y);
+}
+
+int ec_point_set_affine_coordinates(const EC_GROUP *group, EC_AFFINE *out,
                                     const EC_FELEM *x, const EC_FELEM *y) {
   void (*const felem_mul)(const EC_GROUP *, EC_FELEM *r, const EC_FELEM *a,
                           const EC_FELEM *b) = group->meth->felem_mul;
@@ -828,24 +824,21 @@
   felem_mul(group, &rhs, &rhs, x);             // rhs = x^3 + ax
   ec_felem_add(group, &rhs, &rhs, &group->b);  // rhs = x^3 + ax + b
   if (!ec_felem_equal(group, &lhs, &rhs)) {
-    // In the event of an error, defend against the caller not checking the
-    // return value by setting a known safe value: the base point.
-    const EC_POINT *generator = EC_GROUP_get0_generator(group);
-    if (generator) {
-      ec_GFp_simple_point_copy(out, &generator->raw);
-    } else {
-      // The generator can be missing if the caller is in the process of
-      // constructing an arbitrary group. In this case, we give up and use the
-      // point at infinity.
-      ec_GFp_simple_point_set_to_infinity(group, out);
-    }
     OPENSSL_PUT_ERROR(EC, EC_R_POINT_IS_NOT_ON_CURVE);
+    // In the event of an error, defend against the caller not checking the
+    // return value by setting a known safe value. Note this may not be possible
+    // if the caller is in the process of constructing an arbitrary group and
+    // the generator is missing.
+    if (group->generator != NULL) {
+      assert(ec_felem_equal(group, &group->one, &group->generator->raw.Z));
+      out->X = group->generator->raw.X;
+      out->Y = group->generator->raw.Y;
+    }
     return 0;
   }
 
   out->X = *x;
   out->Y = *y;
-  out->Z = group->one;
   return 1;
 }
 
@@ -863,13 +856,17 @@
   }
 
   EC_FELEM x_felem, y_felem;
+  EC_AFFINE affine;
   if (!ec_bignum_to_felem(group, &x_felem, x) ||
       !ec_bignum_to_felem(group, &y_felem, y) ||
-      !ec_point_set_affine_coordinates(group, &point->raw, &x_felem,
-                                       &y_felem)) {
+      !ec_point_set_affine_coordinates(group, &affine, &x_felem, &y_felem)) {
+    // In the event of an error, defend against the caller not checking the
+    // return value by setting a known safe value.
+    ec_set_to_safe_point(group, &point->raw);
     return 0;
   }
 
+  ec_affine_to_jacobian(group, &point->raw, &affine);
   return 1;
 }
 
@@ -1056,14 +1053,19 @@
 
 int ec_get_x_coordinate_as_scalar(const EC_GROUP *group, EC_SCALAR *out,
                                   const EC_RAW_POINT *p) {
-  EC_FELEM x;
-  // For simplicity, in case of width mismatches between |group->field| and
-  // |group->order|, zero any untouched words in |x|.
-  OPENSSL_memset(&x, 0, sizeof(x));
-  if (!group->meth->point_get_affine_coordinates(group, p, &x, NULL)) {
+  uint8_t bytes[EC_MAX_BYTES];
+  size_t len;
+  if (!ec_get_x_coordinate_as_bytes(group, bytes, &len, sizeof(bytes), p)) {
     return 0;
   }
 
+  // For simplicity, in case of width mismatches between |group->field| and
+  // |group->order|, zero any untouched words in |out|.
+  OPENSSL_memset(out, 0, sizeof(EC_SCALAR));
+  for (size_t i = 0; i < len; i++) {
+    out->bytes[len - i - 1] = bytes[i];
+  }
+
   // We must have p < 2×order, assuming p is not tiny (p >= 17). Thus rather we
   // can reduce by performing at most one subtraction.
   //
@@ -1082,17 +1084,17 @@
 
   // The above does not guarantee |group->field| is not one word larger than
   // |group->order|, so read one extra carry word.
+  BN_ULONG tmp[EC_MAX_WORDS];
   BN_ULONG carry =
-      group->order.width < EC_MAX_WORDS ? x.words[group->order.width] : 0;
-  bn_reduce_once(out->words, x.words, carry, group->order.d,
-                 group->order.width);
+      group->order.width < EC_MAX_WORDS ? out->words[group->order.width] : 0;
+  bn_reduce_once_in_place(out->words, carry, group->order.d, tmp,
+                          group->order.width);
   return 1;
 }
 
-int ec_point_get_affine_coordinate_bytes(const EC_GROUP *group, uint8_t *out_x,
-                                         uint8_t *out_y, size_t *out_len,
-                                         size_t max_out,
-                                         const EC_RAW_POINT *p) {
+int ec_get_x_coordinate_as_bytes(const EC_GROUP *group, uint8_t *out,
+                                 size_t *out_len, size_t max_out,
+                                 const EC_RAW_POINT *p) {
   size_t len = BN_num_bytes(&group->field);
   assert(len <= EC_MAX_BYTES);
   if (max_out < len) {
@@ -1100,26 +1102,27 @@
     return 0;
   }
 
-  EC_FELEM x, y;
-  if (!group->meth->point_get_affine_coordinates(
-          group, p, out_x == NULL ? NULL : &x, out_y == NULL ? NULL : &y)) {
+  EC_FELEM x;
+  if (!group->meth->point_get_affine_coordinates(group, p, &x, NULL)) {
     return 0;
   }
 
-  if (out_x != NULL) {
-    for (size_t i = 0; i < len; i++) {
-      out_x[i] = x.bytes[len - i - 1];
-    }
-  }
-  if (out_y != NULL) {
-    for (size_t i = 0; i < len; i++) {
-      out_y[i] = y.bytes[len - i - 1];
-    }
-  }
+  ec_felem_to_bytes(group, out, out_len, &x);
   *out_len = len;
   return 1;
 }
 
+void ec_set_to_safe_point(const EC_GROUP *group, EC_RAW_POINT *out) {
+  if (group->generator != NULL) {
+    ec_GFp_simple_point_copy(out, &group->generator->raw);
+  } else {
+    // The generator can be missing if the caller is in the process of
+    // constructing an arbitrary group. In this case, we give up and use the
+    // point at infinity.
+    ec_GFp_simple_point_set_to_infinity(group, out);
+  }
+}
+
 void EC_GROUP_set_asn1_flag(EC_GROUP *group, int flag) {}
 
 const EC_METHOD *EC_GROUP_method_of(const EC_GROUP *group) {
diff --git a/crypto/fipsmodule/ec/ec_montgomery.c b/crypto/fipsmodule/ec/ec_montgomery.c
index 233de7f..02bd66c 100644
--- a/crypto/fipsmodule/ec/ec_montgomery.c
+++ b/crypto/fipsmodule/ec/ec_montgomery.c
@@ -188,12 +188,6 @@
   ec_GFp_mont_felem_inv0(group, &z2, &point->Z);
   ec_GFp_mont_felem_sqr(group, &z1, &z2);
 
-  // Instead of using |ec_GFp_mont_felem_from_montgomery| to convert the |x|
-  // coordinate and then calling |ec_GFp_mont_felem_from_montgomery| again to
-  // convert the |y| coordinate below, convert the common factor |z1| once now,
-  // saving one reduction.
-  ec_GFp_mont_felem_from_montgomery(group, &z1, &z1);
-
   if (x != NULL) {
     ec_GFp_mont_felem_mul(group, x, &point->X, &z1);
   }
diff --git a/crypto/fipsmodule/ec/internal.h b/crypto/fipsmodule/ec/internal.h
index 6333d43..ef6014c 100644
--- a/crypto/fipsmodule/ec/internal.h
+++ b/crypto/fipsmodule/ec/internal.h
@@ -239,21 +239,54 @@
 
 
 // Points.
+//
+// Points may represented in affine coordinates as |EC_AFFINE| or Jacobian
+// coordinates as |EC_RAW_POINT|. Affine coordinates directly represent a
+// point on the curve, but point addition over affine coordinates requires
+// costly field inversions, so arithmetic is done in Jacobian coordinates.
+// Converting from affine to Jacobian is cheap, while converting from Jacobian
+// to affine costs a field inversion. (Jacobian coordinates amortize the field
+// inversions needed in a sequence of point operations.)
+//
+// TODO(davidben): Rename |EC_RAW_POINT| to |EC_JACOBIAN|.
 
-// An EC_RAW_POINT represents an elliptic curve point. Unlike |EC_POINT|, it is
-// a plain struct which can be stack-allocated and needs no cleanup. It is
-// specific to an |EC_GROUP| and must not be mixed between groups.
+// An EC_RAW_POINT represents an elliptic curve point in Jacobian coordinates.
+// Unlike |EC_POINT|, it is a plain struct which can be stack-allocated and
+// needs no cleanup. It is specific to an |EC_GROUP| and must not be mixed
+// between groups.
 typedef struct {
-  EC_FELEM X, Y, Z;
   // X, Y, and Z are Jacobian projective coordinates. They represent
   // (X/Z^2, Y/Z^3) if Z != 0 and the point at infinity otherwise.
+  EC_FELEM X, Y, Z;
 } EC_RAW_POINT;
 
+// An EC_AFFINE represents an elliptic curve point in affine coordinates.
+// coordinates. Note the point at infinity cannot be represented in affine
+// coordinates.
+typedef struct {
+  EC_FELEM X, Y;
+} EC_AFFINE;
+
+// ec_affine_to_jacobian converts |p| to Jacobian form and writes the result to
+// |*out|. This operation is very cheap and only costs a few copies.
+void ec_affine_to_jacobian(const EC_GROUP *group, EC_RAW_POINT *out,
+                           const EC_AFFINE *p);
+
+// ec_jacobian_to_affine converts |p| to affine form and writes the result to
+// |*out|. It returns one on success and zero if |p| was the point at infinity.
+// This operation performs a field inversion and should only be done once per
+// point.
+//
+// If only extracting the x-coordinate, use |ec_get_x_coordinate_*| which is
+// slightly faster.
+int ec_jacobian_to_affine(const EC_GROUP *group, EC_AFFINE *out,
+                          const EC_RAW_POINT *p);
+
 // ec_point_set_affine_coordinates sets |out|'s to a point with affine
 // coordinates |x| and |y|. It returns one if the point is on the curve and
 // zero otherwise. If the point is not on the curve, the value of |out| is
 // undefined.
-int ec_point_set_affine_coordinates(const EC_GROUP *group, EC_RAW_POINT *out,
+int ec_point_set_affine_coordinates(const EC_GROUP *group, EC_AFFINE *out,
                                     const EC_FELEM *x, const EC_FELEM *y);
 
 // ec_point_mul_scalar sets |r| to |p| * |scalar|. Both inputs are considered
@@ -292,30 +325,31 @@
 int ec_get_x_coordinate_as_scalar(const EC_GROUP *group, EC_SCALAR *out,
                                   const EC_RAW_POINT *p);
 
-// ec_point_get_affine_coordinate_bytes writes |p|'s affine coordinates to
-// |out_x| and |out_y|, each of which must have at must |max_out| bytes. It sets
-// |*out_len| to the number of bytes written in each buffer. Coordinates are
-// written big-endian and zero-padded to the size of the field.
-//
-// Either of |out_x| or |out_y| may be NULL to omit that coordinate. This
-// function returns one on success and zero on failure.
-int ec_point_get_affine_coordinate_bytes(const EC_GROUP *group, uint8_t *out_x,
-                                         uint8_t *out_y, size_t *out_len,
-                                         size_t max_out, const EC_RAW_POINT *p);
+// ec_get_x_coordinate_as_bytes writes |p|'s affine x-coordinate to |out|, which
+// must have at must |max_out| bytes. It sets |*out_len| to the number of bytes
+// written. The value is written big-endian and zero-padded to the size of the
+// field. This function returns one on success and zero on failure.
+int ec_get_x_coordinate_as_bytes(const EC_GROUP *group, uint8_t *out,
+                                 size_t *out_len, size_t max_out,
+                                 const EC_RAW_POINT *p);
 
 // ec_point_to_bytes behaves like |EC_POINT_point2oct| but takes an
-// |EC_RAW_POINT|.
-OPENSSL_EXPORT size_t ec_point_to_bytes(const EC_GROUP *group,
-                                        const EC_RAW_POINT *point,
-                                        point_conversion_form_t form,
-                                        uint8_t *buf, size_t len);
+// |EC_AFFINE|.
+size_t ec_point_to_bytes(const EC_GROUP *group, const EC_AFFINE *point,
+                         point_conversion_form_t form, uint8_t *buf,
+                         size_t len);
 
 // ec_point_from_uncompressed parses |in| as a point in uncompressed form and
 // sets the result to |out|. It returns one on success and zero if the input was
 // invalid.
-int ec_point_from_uncompressed(const EC_GROUP *group, EC_RAW_POINT *out,
+int ec_point_from_uncompressed(const EC_GROUP *group, EC_AFFINE *out,
                                const uint8_t *in, size_t len);
 
+// ec_set_to_safe_point sets |out| to an arbitrary point on |group|, either the
+// generator or the point at infinity. This is used to guard against callers of
+// external APIs not checking the return value.
+void ec_set_to_safe_point(const EC_GROUP *group, EC_RAW_POINT *out);
+
 
 // Implementation details.
 
@@ -328,9 +362,6 @@
   // 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.
-  //
-  // Note: unlike |EC_FELEM|s used as intermediate values internal to the
-  // |EC_METHOD|, |*x| and |*y| are not encoded in Montgomery form.
   int (*point_get_affine_coordinates)(const EC_GROUP *, const EC_RAW_POINT *p,
                                       EC_FELEM *x, EC_FELEM *y);
 
@@ -412,7 +443,8 @@
   const EC_METHOD *meth;
 
   // Unlike all other |EC_POINT|s, |generator| does not own |generator->group|
-  // to avoid a reference cycle.
+  // to avoid a reference cycle. Additionally, Z is guaranteed to be one, so X
+  // and Y are suitable for use as an |EC_AFFINE|.
   EC_POINT *generator;
   BIGNUM order;
 
@@ -548,6 +580,9 @@
 struct ec_key_st {
   EC_GROUP *group;
 
+  // Ideally |pub_key| would be an |EC_AFFINE| so serializing it does not pay an
+  // inversion each time, but the |EC_KEY_get0_public_key| API implies public
+  // keys are stored in an |EC_POINT|-compatible form.
   EC_POINT *pub_key;
   EC_WRAPPED_SCALAR *priv_key;
 
diff --git a/crypto/fipsmodule/ec/oct.c b/crypto/fipsmodule/ec/oct.c
index a2a0fc8..ddd0f37 100644
--- a/crypto/fipsmodule/ec/oct.c
+++ b/crypto/fipsmodule/ec/oct.c
@@ -73,7 +73,7 @@
 #include "internal.h"
 
 
-size_t ec_point_to_bytes(const EC_GROUP *group, const EC_RAW_POINT *point,
+size_t ec_point_to_bytes(const EC_GROUP *group, const EC_AFFINE *point,
                          point_conversion_form_t form, uint8_t *buf,
                          size_t len) {
   if (form != POINT_CONVERSION_COMPRESSED &&
@@ -82,11 +82,6 @@
     return 0;
   }
 
-  if (ec_GFp_simple_is_at_infinity(group, point)) {
-    OPENSSL_PUT_ERROR(EC, EC_R_POINT_AT_INFINITY);
-    return 0;
-  }
-
   const size_t field_len = BN_num_bytes(&group->field);
   size_t output_len = 1 /* type byte */ + field_len;
   if (form == POINT_CONVERSION_UNCOMPRESSED) {
@@ -101,31 +96,25 @@
       return 0;
     }
 
-    uint8_t y_buf[EC_MAX_BYTES];
     size_t field_len_out;
-    if (!ec_point_get_affine_coordinate_bytes(
-            group, buf + 1 /* x */,
-            form == POINT_CONVERSION_COMPRESSED ? y_buf : buf + 1 + field_len,
-            &field_len_out, field_len, point)) {
-      return 0;
-    }
+    ec_felem_to_bytes(group, buf + 1, &field_len_out, &point->X);
+    assert(field_len_out == field_len);
 
-    if (field_len_out != field_len) {
-      OPENSSL_PUT_ERROR(EC, ERR_R_INTERNAL_ERROR);
-      return 0;
-    }
-
-    if (form == POINT_CONVERSION_COMPRESSED) {
-      buf[0] = form + (y_buf[field_len - 1] & 1);
-    } else {
+    if (form == POINT_CONVERSION_UNCOMPRESSED) {
+      ec_felem_to_bytes(group, buf + 1 + field_len, &field_len_out, &point->Y);
+      assert(field_len_out == field_len);
       buf[0] = form;
+    } else {
+      uint8_t y_buf[EC_MAX_BYTES];
+      ec_felem_to_bytes(group, y_buf, &field_len_out, &point->Y);
+      buf[0] = form + (y_buf[field_len_out - 1] & 1);
     }
   }
 
   return output_len;
 }
 
-int ec_point_from_uncompressed(const EC_GROUP *group, EC_RAW_POINT *out,
+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);
   if (len != 1 + 2 * field_len || in[0] != POINT_CONVERSION_UNCOMPRESSED) {
@@ -153,7 +142,15 @@
 
   point_conversion_form_t form = buf[0];
   if (form == POINT_CONVERSION_UNCOMPRESSED) {
-    return ec_point_from_uncompressed(group, &point->raw, buf, len);
+    EC_AFFINE affine;
+    if (!ec_point_from_uncompressed(group, &affine, buf, len)) {
+      // In the event of an error, defend against the caller not checking the
+      // return value by setting a known safe value.
+      ec_set_to_safe_point(group, &point->raw);
+      return 0;
+    }
+    ec_affine_to_jacobian(group, &point->raw, &affine);
+    return 1;
   }
 
   const int y_bit = form & 1;
@@ -165,6 +162,11 @@
     return 0;
   }
 
+  // TODO(davidben): Integrate compressed coordinates with the lower-level EC
+  // abstractions. This requires a way to compute square roots, which is tricky
+  // for primes which are not 3 (mod 4), namely P-224 and custom curves. P-224's
+  // prime is particularly inconvenient for compressed coordinates. See
+  // https://cr.yp.to/papers/sqroot.pdf
   BN_CTX *new_ctx = NULL;
   if (ctx == NULL) {
     ctx = new_ctx = BN_CTX_new();
@@ -212,7 +214,11 @@
     OPENSSL_PUT_ERROR(EC, EC_R_INCOMPATIBLE_OBJECTS);
     return 0;
   }
-  return ec_point_to_bytes(group, &point->raw, form, buf, len);
+  EC_AFFINE affine;
+  if (!ec_jacobian_to_affine(group, &affine, &point->raw)) {
+    return 0;
+  }
+  return ec_point_to_bytes(group, &affine, form, buf, len);
 }
 
 int EC_POINT_set_compressed_coordinates_GFp(const EC_GROUP *group,
diff --git a/crypto/fipsmodule/ec/p256-x86_64.c b/crypto/fipsmodule/ec/p256-x86_64.c
index cb82852..973130f 100644
--- a/crypto/fipsmodule/ec/p256-x86_64.c
+++ b/crypto/fipsmodule/ec/p256-x86_64.c
@@ -432,14 +432,12 @@
 
   if (x != NULL) {
     ecp_nistz256_mul_mont(x->words, z_inv2, point->X.words);
-    ecp_nistz256_from_mont(x->words, x->words);
   }
 
   if (y != NULL) {
     ecp_nistz256_sqr_mont(z_inv2, z_inv2);                            // z^-4
     ecp_nistz256_mul_mont(y->words, point->Y.words, point->Z.words);  // y * z
     ecp_nistz256_mul_mont(y->words, y->words, z_inv2);  // y * z^-3
-    ecp_nistz256_from_mont(y->words, y->words);
   }
 
   return 1;
diff --git a/crypto/fipsmodule/ec/p256.c b/crypto/fipsmodule/ec/p256.c
index bf9e64e..542550f 100644
--- a/crypto/fipsmodule/ec/p256.c
+++ b/crypto/fipsmodule/ec/p256.c
@@ -752,7 +752,6 @@
     fiat_p256_felem x;
     fiat_p256_from_generic(x, &point->X);
     fiat_p256_mul(x, x, z2);
-    fiat_p256_from_montgomery(x, x);
     fiat_p256_to_generic(x_out, x);
   }
 
@@ -762,7 +761,6 @@
     fiat_p256_square(z2, z2);  // z^-4
     fiat_p256_mul(y, y, z1);   // y * z
     fiat_p256_mul(y, y, z2);   // y * z^-3
-    fiat_p256_from_montgomery(y, y);
     fiat_p256_to_generic(y_out, y);
   }
 
diff --git a/crypto/fipsmodule/ecdh/ecdh.c b/crypto/fipsmodule/ecdh/ecdh.c
index a7b2f08..4e6d0bf 100644
--- a/crypto/fipsmodule/ecdh/ecdh.c
+++ b/crypto/fipsmodule/ecdh/ecdh.c
@@ -94,8 +94,8 @@
   uint8_t buf[EC_MAX_BYTES];
   size_t buflen;
   if (!ec_point_mul_scalar(group, &shared_point, &pub_key->raw, priv) ||
-      !ec_point_get_affine_coordinate_bytes(group, buf, NULL, &buflen,
-                                            sizeof(buf), &shared_point)) {
+      !ec_get_x_coordinate_as_bytes(group, buf, &buflen, sizeof(buf),
+                                    &shared_point)) {
     OPENSSL_PUT_ERROR(ECDH, ECDH_R_POINT_ARITHMETIC_FAILURE);
     return 0;
   }
diff --git a/crypto/trust_token/pmbtoken.c b/crypto/trust_token/pmbtoken.c
index 3117b65..b3764b6 100644
--- a/crypto/trust_token/pmbtoken.c
+++ b/crypto/trust_token/pmbtoken.c
@@ -113,23 +113,32 @@
 
 static int point_to_cbb(CBB *out, const EC_GROUP *group,
                         const EC_RAW_POINT *point) {
+  EC_AFFINE affine;
+  if (!ec_jacobian_to_affine(group, &affine, point)) {
+    return 0;
+  }
   size_t len =
-      ec_point_to_bytes(group, point, POINT_CONVERSION_UNCOMPRESSED, NULL, 0);
+      ec_point_to_bytes(group, &affine, POINT_CONVERSION_UNCOMPRESSED, NULL, 0);
   if (len == 0) {
     return 0;
   }
   uint8_t *p;
   return CBB_add_space(out, &p, len) &&
-         ec_point_to_bytes(group, point, POINT_CONVERSION_UNCOMPRESSED, p,
+         ec_point_to_bytes(group, &affine, POINT_CONVERSION_UNCOMPRESSED, p,
                            len) == len;
 }
 
 static int cbs_get_prefixed_point(CBS *cbs, const EC_GROUP *group,
                                   EC_RAW_POINT *out) {
   CBS child;
-  return CBS_get_u16_length_prefixed(cbs, &child) &&
-         ec_point_from_uncompressed(group, out, CBS_data(&child),
-                                    CBS_len(&child));
+  EC_AFFINE affine;
+  if (!CBS_get_u16_length_prefixed(cbs, &child) ||
+      !ec_point_from_uncompressed(group, &affine, CBS_data(&child),
+                                  CBS_len(&child))) {
+    return 0;
+  }
+  ec_affine_to_jacobian(group, out, &affine);
+  return 1;
 }
 
 void PMBTOKEN_PRETOKEN_free(PMBTOKEN_PRETOKEN *pretoken) {
@@ -918,7 +927,12 @@
       0xcd,
   };
 
-  return ec_point_from_uncompressed(method->group, &method->h, kH, sizeof(kH));
+  EC_AFFINE h;
+  if (!ec_point_from_uncompressed(method->group, &h, kH, sizeof(kH))) {
+    return 0;
+  }
+  ec_affine_to_jacobian(method->group, &method->h, &h);
+  return 1;
 }
 
 int pmbtoken_exp0_generate_key(CBB *out_private, CBB *out_public) {
@@ -1057,7 +1071,12 @@
       0x87, 0xc3, 0x95, 0xd0, 0x13, 0xb7, 0x0b, 0x5c, 0xc7,
   };
 
-  return ec_point_from_uncompressed(method->group, &method->h, kH, sizeof(kH));
+  EC_AFFINE h;
+  if (!ec_point_from_uncompressed(method->group, &h, kH, sizeof(kH))) {
+    return 0;
+  }
+  ec_affine_to_jacobian(method->group, &method->h, &h);
+  return 1;
 }
 
 int pmbtoken_exp1_generate_key(CBB *out_private, CBB *out_public) {
@@ -1134,6 +1153,8 @@
   if (!pmbtoken_exp1_init_method(&method)) {
     return 0;
   }
-  return ec_point_to_bytes(method.group, &method.h,
-                           POINT_CONVERSION_UNCOMPRESSED, out, 97) == 97;
+  EC_AFFINE h;
+  return ec_jacobian_to_affine(method.group, &h, &method.h) &&
+         ec_point_to_bytes(method.group, &h, POINT_CONVERSION_UNCOMPRESSED, out,
+                           97) == 97;
 }
diff --git a/crypto/trust_token/trust_token_test.cc b/crypto/trust_token/trust_token_test.cc
index 50ef729..50be906 100644
--- a/crypto/trust_token/trust_token_test.cc
+++ b/crypto/trust_token/trust_token_test.cc
@@ -75,13 +75,14 @@
   const uint8_t kHGen[] = "generator";
   const uint8_t kHLabel[] = "PMBTokens Experiment V1 HashH";
 
-  EC_RAW_POINT expected_h;
+  bssl::UniquePtr<EC_POINT> expected_h(EC_POINT_new(group));
+  ASSERT_TRUE(expected_h);
   ASSERT_TRUE(ec_hash_to_curve_p384_xmd_sha512_sswu_draft07(
-      group, &expected_h, kHLabel, sizeof(kHLabel), kHGen, sizeof(kHGen)));
+      group, &expected_h->raw, kHLabel, sizeof(kHLabel), kHGen, sizeof(kHGen)));
   uint8_t expected_bytes[1 + 2 * EC_MAX_BYTES];
   size_t expected_len =
-      ec_point_to_bytes(group, &expected_h, POINT_CONVERSION_UNCOMPRESSED,
-                        expected_bytes, sizeof(expected_bytes));
+      EC_POINT_point2oct(group, expected_h.get(), POINT_CONVERSION_UNCOMPRESSED,
+                         expected_bytes, sizeof(expected_bytes), nullptr);
 
   uint8_t h[97];
   ASSERT_TRUE(pmbtoken_exp1_get_h_for_testing(h));