Implement DLEQ checks for Trust Token.

Change-Id: I6f263b775aafad6616b31af59096c3b4229fe3e1
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/40684
Commit-Queue: Steven Valdez <svaldez@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/crypto/err/trust_token.errordata b/crypto/err/trust_token.errordata
index d7d9946..7e612dc 100644
--- a/crypto/err/trust_token.errordata
+++ b/crypto/err/trust_token.errordata
@@ -5,6 +5,7 @@
 TRUST_TOKEN,109,INVALID_KEY_ID
 TRUST_TOKEN,106,INVALID_METADATA
 TRUST_TOKEN,113,INVALID_METADATA_KEY
+TRUST_TOKEN,114,INVALID_PROOF
 TRUST_TOKEN,110,INVALID_TOKEN
 TRUST_TOKEN,100,KEYGEN_FAILURE
 TRUST_TOKEN,108,NO_KEYS_CONFIGURED
diff --git a/crypto/fipsmodule/ec/ec.c b/crypto/fipsmodule/ec/ec.c
index e16a379..3a7d4d5 100644
--- a/crypto/fipsmodule/ec/ec.c
+++ b/crypto/fipsmodule/ec/ec.c
@@ -1042,6 +1042,13 @@
   return 1;
 }
 
+void ec_point_select(const EC_GROUP *group, EC_RAW_POINT *out, BN_ULONG mask,
+                      const EC_RAW_POINT *a, const EC_RAW_POINT *b) {
+  ec_felem_select(group, &out->X, mask, &a->X, &b->X);
+  ec_felem_select(group, &out->Y, mask, &a->Y, &b->Y);
+  ec_felem_select(group, &out->Z, mask, &a->Z, &b->Z);
+}
+
 int ec_cmp_x_coordinate(const EC_GROUP *group, const EC_RAW_POINT *p,
                         const EC_SCALAR *r) {
   return group->meth->cmp_x_coordinate(group, p, r);
diff --git a/crypto/fipsmodule/ec/internal.h b/crypto/fipsmodule/ec/internal.h
index ebae837..47ccdbd 100644
--- a/crypto/fipsmodule/ec/internal.h
+++ b/crypto/fipsmodule/ec/internal.h
@@ -145,6 +145,10 @@
 void ec_scalar_add(const EC_GROUP *group, EC_SCALAR *r, const EC_SCALAR *a,
                    const EC_SCALAR *b);
 
+// ec_scalar_sub sets |r| to |a| - |b|.
+void ec_scalar_sub(const EC_GROUP *group, EC_SCALAR *r, const EC_SCALAR *a,
+                   const EC_SCALAR *b);
+
 // ec_scalar_to_montgomery sets |r| to |a| in Montgomery form.
 void ec_scalar_to_montgomery(const EC_GROUP *group, EC_SCALAR *r,
                              const EC_SCALAR *a);
@@ -270,6 +274,11 @@
                                               const EC_RAW_POINT *p,
                                               const EC_SCALAR *p_scalar);
 
+// ec_point_select, in constant time, sets |out| to |a| if |mask| is all ones
+// and |b| if |mask| is all zeros.
+void ec_point_select(const EC_GROUP *group, EC_RAW_POINT *out, BN_ULONG mask,
+                     const EC_RAW_POINT *a, const EC_RAW_POINT *b);
+
 // ec_cmp_x_coordinate compares the x (affine) coordinate of |p|, mod the group
 // order, with |r|. It returns one if the values match and zero if |p| is the
 // point at infinity of the values do not match.
diff --git a/crypto/fipsmodule/ec/scalar.c b/crypto/fipsmodule/ec/scalar.c
index af055fc..3b4a7d8 100644
--- a/crypto/fipsmodule/ec/scalar.c
+++ b/crypto/fipsmodule/ec/scalar.c
@@ -98,6 +98,14 @@
   OPENSSL_cleanse(tmp, sizeof(tmp));
 }
 
+void ec_scalar_sub(const EC_GROUP *group, EC_SCALAR *r, const EC_SCALAR *a,
+                   const EC_SCALAR *b) {
+  const BIGNUM *order = &group->order;
+  BN_ULONG tmp[EC_MAX_WORDS];
+  bn_mod_sub_words(r->words, a->words, b->words, order->d, tmp, order->width);
+  OPENSSL_cleanse(tmp, sizeof(tmp));
+}
+
 void ec_scalar_select(const EC_GROUP *group, EC_SCALAR *out, BN_ULONG mask,
                       const EC_SCALAR *a, const EC_SCALAR *b) {
   const BIGNUM *order = &group->order;
diff --git a/crypto/fipsmodule/ec/simple_mul.c b/crypto/fipsmodule/ec/simple_mul.c
index 4ed6c48..9a43120 100644
--- a/crypto/fipsmodule/ec/simple_mul.c
+++ b/crypto/fipsmodule/ec/simple_mul.c
@@ -60,9 +60,7 @@
       OPENSSL_memset(&tmp, 0, sizeof(EC_RAW_POINT));
       for (size_t j = 0; j < OPENSSL_ARRAY_SIZE(precomp); j++) {
         BN_ULONG mask = constant_time_eq_w(j, window);
-        ec_felem_select(group, &tmp.X, mask, &precomp[j].X, &tmp.X);
-        ec_felem_select(group, &tmp.Y, mask, &precomp[j].Y, &tmp.Y);
-        ec_felem_select(group, &tmp.Z, mask, &precomp[j].Z, &tmp.Z);
+        ec_point_select(group, &tmp, mask, &precomp[j], &tmp);
       }
 
       if (r_is_at_infinity) {
diff --git a/crypto/trust_token/internal.h b/crypto/trust_token/internal.h
index 92be6ee..6edd111 100644
--- a/crypto/trust_token/internal.h
+++ b/crypto/trust_token/internal.h
@@ -34,6 +34,29 @@
 // protocol.
 #define PMBTOKEN_NONCE_SIZE 64
 
+// Structure representing a single Trust Token public key with the specified ID.
+struct trust_token_client_key_st {
+  uint32_t id;
+  EC_RAW_POINT pub0;
+  EC_RAW_POINT pub1;
+  EC_RAW_POINT pubs;
+};
+
+// Structure representing a single Trust Token private key with the specified
+// ID.
+struct trust_token_issuer_key_st {
+  uint32_t id;
+  EC_SCALAR x0;
+  EC_SCALAR y0;
+  EC_SCALAR x1;
+  EC_SCALAR y1;
+  EC_SCALAR xs;
+  EC_SCALAR ys;
+  EC_RAW_POINT pub0;
+  EC_RAW_POINT pub1;
+  EC_RAW_POINT pubs;
+};
+
 // PMBTOKEN_PRETOKEN represents the intermediate state a client keeps during a
 // PMBToken issuance operation.
 typedef struct pmb_pretoken_st {
@@ -60,6 +83,10 @@
 // PMBTOKEN_TOKEN_free releases the memory associated with |token|.
 void PMBTOKEN_TOKEN_free(PMBTOKEN_TOKEN *token);
 
+// pmbtoken_compute_public computes the public keypairs from the private
+// keypairs in |key|. It returns one on success and zero on failure.
+int pmbtoken_compute_public(struct trust_token_issuer_key_st *key);
+
 // pmbtoken_blind generates a new blinded pretoken based on the configuration of
 // |ctx| as per the first stage of the AT.Usr operation and returns the
 // resulting pretoken.
@@ -68,19 +95,24 @@
 // pmbtoken_sign signs a blinded point based on the configuration of |ctx|
 // and the key specified by |key_id| with a private metadata value of
 // |private_metadata| as per the AT.Sig operation and stores the resulting nonce
-// and points in |*out_s|, |*out_Wp|, and |*out_Wsp|. It returns one on success
+// and points in |*out_s|, |*out_Wp|, and |*out_Wsp| and the resulting DLEQ
+// proof in |*out_proof|. The caller takes ownership of |*out_proof| and is
+// responsible for freeing it using |OPENSSL_free|. It returns one on success
 // and zero on failure.
 int pmbtoken_sign(const TRUST_TOKEN_ISSUER *ctx,
                   uint8_t out_s[PMBTOKEN_NONCE_SIZE], EC_RAW_POINT *out_Wp,
-                  EC_RAW_POINT *out_Wsp, const EC_RAW_POINT *Tp,
+                  EC_RAW_POINT *out_Wsp, uint8_t **out_proof,
+                  size_t *out_proof_len, const EC_RAW_POINT *Tp,
                   uint32_t key_id, uint8_t private_metadata);
 
 // pmbtoken_unblind unblinds the result of an AT.Sig operation as per the final
 // stage of the AT.Usr operation and sets |*out_token| to the resulting token.
 // It returns one on success and zero on failure.
 int pmbtoken_unblind(PMBTOKEN_TOKEN *out_token,
+                     const struct trust_token_client_key_st *key,
                      const uint8_t s[PMBTOKEN_NONCE_SIZE],
                      const EC_RAW_POINT *Wp, const EC_RAW_POINT *Wsp,
+                     const uint8_t *proof, size_t proof_len,
                      const PMBTOKEN_PRETOKEN *pretoken);
 
 // pmbtoken_read verifies the validity of a PMBToken |token| using the key
@@ -90,26 +122,6 @@
 int pmbtoken_read(const TRUST_TOKEN_ISSUER *ctx, uint8_t *out_private_metadata,
                   const PMBTOKEN_TOKEN *token, uint32_t key_id);
 
-// Structure representing a single Trust Token public key with the specified ID.
-struct trust_token_client_key_st {
-  uint32_t id;
-  EC_RAW_POINT pub0;
-  EC_RAW_POINT pub1;
-  EC_RAW_POINT pubs;
-};
-
-// Structure representing a single Trust Token private key with the specified
-// ID.
-struct trust_token_issuer_key_st {
-  uint32_t id;
-  EC_SCALAR x0;
-  EC_SCALAR y0;
-  EC_SCALAR x1;
-  EC_SCALAR y1;
-  EC_SCALAR xs;
-  EC_SCALAR ys;
-};
-
 struct trust_token_client_st {
   // max_batchsize is the maximum supported batchsize.
   uint16_t max_batchsize;
diff --git a/crypto/trust_token/pmbtoken.c b/crypto/trust_token/pmbtoken.c
index a7a87c8..080368a 100644
--- a/crypto/trust_token/pmbtoken.c
+++ b/crypto/trust_token/pmbtoken.c
@@ -122,9 +122,9 @@
   return ec_point_from_uncompressed(group, out_h, kH, sizeof(kH));
 }
 
-static int mul_g_and_p(const EC_GROUP *group, EC_RAW_POINT *out,
-                       const EC_RAW_POINT *g, const EC_SCALAR *g_scalar,
-                       const EC_RAW_POINT *p, const EC_SCALAR *p_scalar) {
+static int mul_twice(const EC_GROUP *group, EC_RAW_POINT *out,
+                     const EC_RAW_POINT *g, const EC_SCALAR *g_scalar,
+                     const EC_RAW_POINT *p, const EC_SCALAR *p_scalar) {
   EC_RAW_POINT tmp1, tmp2;
   if (!ec_point_mul_scalar(group, &tmp1, g, g_scalar) ||
       !ec_point_mul_scalar(group, &tmp2, p, p_scalar)) {
@@ -135,22 +135,54 @@
   return 1;
 }
 
+static int mul_twice_base(const EC_GROUP *group, EC_RAW_POINT *out,
+                          const EC_SCALAR *base_scalar, const EC_RAW_POINT *p,
+                          const EC_SCALAR *p_scalar) {
+  EC_RAW_POINT tmp1, tmp2;
+  if (!ec_point_mul_scalar_base(group, &tmp1, base_scalar) ||
+      !ec_point_mul_scalar(group, &tmp2, p, p_scalar)) {
+    return 0;
+  }
+
+  group->meth->add(group, out, &tmp1, &tmp2);
+  return 1;
+}
+
+// (v0;v1) = p_scalar*(G;p1) + q_scalar*(q0;q1) - r_scalar*(r0;r1)
+static int mul_add_and_sub(const EC_GROUP *group, EC_RAW_POINT *out_v0,
+                           EC_RAW_POINT *out_v1, const EC_RAW_POINT *p1,
+                           const EC_SCALAR *p_scalar, const EC_RAW_POINT *q0,
+                           const EC_RAW_POINT *q1, const EC_SCALAR *q_scalar,
+                           const EC_RAW_POINT *r0, const EC_RAW_POINT *r1,
+                           const EC_SCALAR *r_scalar) {
+  EC_RAW_POINT tmp0, tmp1, v0, v1;
+  if (!mul_twice_base(group, &v0, p_scalar, q0, q_scalar) ||
+      !mul_twice(group, &v1, p1, p_scalar, q1, q_scalar) ||
+      !ec_point_mul_scalar(group, &tmp0, r0, r_scalar) ||
+      !ec_point_mul_scalar(group, &tmp1, r1, r_scalar)) {
+    return 0;
+  }
+  ec_GFp_simple_invert(group, &tmp0);
+  ec_GFp_simple_invert(group, &tmp1);
+  group->meth->add(group, out_v0, &v0, &tmp0);
+  group->meth->add(group, out_v1, &v1, &tmp1);
+  return 1;
+}
+
 // generate_keypair generates a keypair for the PMBTokens construction.
 // |out_x| and |out_y| are set to the secret half of the keypair, while
 // |*out_pub| is set to the public half of the keypair. It returns one on
 // success and zero on failure.
 static int generate_keypair(EC_SCALAR *out_x, EC_SCALAR *out_y,
                             EC_RAW_POINT *out_pub, const EC_GROUP *group) {
-  EC_RAW_POINT h, tmp1, tmp2;
+  EC_RAW_POINT h;
   if (!get_h(&h) ||
       !ec_random_nonzero_scalar(group, out_x, kDefaultAdditionalData) ||
       !ec_random_nonzero_scalar(group, out_y, kDefaultAdditionalData) ||
-      !ec_point_mul_scalar_base(group, &tmp1, out_x) ||
-      !ec_point_mul_scalar(group, &tmp2, &h, out_y)) {
+      !mul_twice_base(group, out_pub, out_x, &h, out_y)) {
     OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
     return 0;
   }
-  group->meth->add(group, out_pub, &tmp1, &tmp2);
   return 1;
 }
 
@@ -239,6 +271,23 @@
   OPENSSL_free(token);
 }
 
+int pmbtoken_compute_public(struct trust_token_issuer_key_st *key) {
+  EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp521r1);
+  if (group == NULL) {
+    return 0;
+  }
+
+  EC_RAW_POINT h;
+  if (!get_h(&h) ||
+      !mul_twice_base(group, &key->pubs, &key->xs, &h, &key->ys) ||
+      !mul_twice_base(group, &key->pub0, &key->x0, &h, &key->y0) ||
+      !mul_twice_base(group, &key->pub1, &key->x1, &h, &key->y1)) {
+    return 0;
+  }
+
+  return 1;
+}
+
 // hash_t implements the H_t operation in PMBTokens. It returns on on success
 // and zero on error.
 static int hash_t(EC_GROUP *group, EC_RAW_POINT *out,
@@ -314,9 +363,367 @@
   return NULL;
 }
 
+static int hash_c(const EC_GROUP *group, EC_SCALAR *out, uint8_t *buf,
+                  size_t len) {
+  const uint8_t kHashCLabel[] = "PMBTokensV0 HashC";
+  return ec_hash_to_scalar_p521_xmd_sha512(group, out, kHashCLabel,
+                                           sizeof(kHashCLabel), buf, len);
+}
+
+static int scalar_to_cbb(CBB *out, const EC_GROUP *group,
+                         const EC_SCALAR *scalar) {
+  uint8_t *buf;
+  size_t scalar_len = BN_num_bytes(&group->order);
+  if (!CBB_add_space(out, &buf, scalar_len)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    return 0;
+  }
+  ec_scalar_to_bytes(group, buf, &scalar_len, scalar);
+  return 1;
+}
+
+static int scalar_from_cbs(CBS *cbs, const EC_GROUP *group, EC_SCALAR *out) {
+  size_t scalar_len = BN_num_bytes(&group->order);
+  CBS tmp;
+  if (!CBS_get_bytes(cbs, &tmp, scalar_len)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
+    return 0;
+  }
+
+  ec_scalar_from_bytes(group, out, CBS_data(&tmp), CBS_len(&tmp));
+  return 1;
+}
+
+static int hash_c_dleq(const EC_GROUP *group, EC_SCALAR *out,
+                       const EC_RAW_POINT *X, const EC_RAW_POINT *T,
+                       const EC_RAW_POINT *S, const EC_RAW_POINT *W,
+                       const EC_RAW_POINT *K0, const EC_RAW_POINT *K1) {
+  static const uint8_t kDLEQ2Label[] = "DLEQ2";
+
+  int ok = 0;
+  CBB cbb;
+  CBB_zero(&cbb);
+  uint8_t *buf = NULL;
+  size_t len;
+  if (!CBB_init(&cbb, 0) ||
+      !CBB_add_bytes(&cbb, kDLEQ2Label, sizeof(kDLEQ2Label)) ||
+      !point_to_cbb(&cbb, group, X) ||
+      !point_to_cbb(&cbb, group, T) ||
+      !point_to_cbb(&cbb, group, S) ||
+      !point_to_cbb(&cbb, group, W) ||
+      !point_to_cbb(&cbb, group, K0) ||
+      !point_to_cbb(&cbb, group, K1) ||
+      !CBB_finish(&cbb, &buf, &len) ||
+      !hash_c(group, out, buf, len)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    goto err;
+  }
+
+  ok = 1;
+
+err:
+  CBB_cleanup(&cbb);
+  OPENSSL_free(buf);
+  return ok;
+}
+
+static int hash_c_dleqor(const EC_GROUP *group, EC_SCALAR *out,
+                         const EC_RAW_POINT *X0, const EC_RAW_POINT *X1,
+                         const EC_RAW_POINT *T, const EC_RAW_POINT *S,
+                         const EC_RAW_POINT *W, const EC_RAW_POINT *K00,
+                         const EC_RAW_POINT *K01, const EC_RAW_POINT *K10,
+                         const EC_RAW_POINT *K11) {
+  static const uint8_t kDLEQOR2Label[] = "DLEQOR2";
+
+  int ok = 0;
+  CBB cbb;
+  CBB_zero(&cbb);
+  uint8_t *buf = NULL;
+  size_t len;
+  if (!CBB_init(&cbb, 0) ||
+      !CBB_add_bytes(&cbb, kDLEQOR2Label, sizeof(kDLEQOR2Label)) ||
+      !point_to_cbb(&cbb, group, X0) ||
+      !point_to_cbb(&cbb, group, X1) ||
+      !point_to_cbb(&cbb, group, T) ||
+      !point_to_cbb(&cbb, group, S) ||
+      !point_to_cbb(&cbb, group, W) ||
+      !point_to_cbb(&cbb, group, K00) ||
+      !point_to_cbb(&cbb, group, K01) ||
+      !point_to_cbb(&cbb, group, K10) ||
+      !point_to_cbb(&cbb, group, K11) ||
+      !CBB_finish(&cbb, &buf, &len) ||
+      !hash_c(group, out, buf, len)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    goto err;
+  }
+
+  ok = 1;
+
+err:
+  CBB_cleanup(&cbb);
+  OPENSSL_free(buf);
+  return ok;
+}
+
+// The DLEQ2 and DLEQOR2 constructions are described in appendix B of
+// https://eprint.iacr.org/2020/072/20200324:214215. DLEQ2 is an instance of
+// DLEQOR2 with only one value (n=1).
+
+static int dleq_generate(const EC_GROUP *group, uint8_t **out_proof,
+                         size_t *out_proof_len,
+                         const struct trust_token_issuer_key_st *priv,
+                         const EC_RAW_POINT *T, const EC_RAW_POINT *S,
+                         const EC_RAW_POINT *W, const EC_RAW_POINT *Ws,
+                         uint8_t private_metadata) {
+  int ok = 0;
+  CBB proof;
+  if (!CBB_init(&proof, 0)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    return 0;
+  }
+
+  EC_RAW_POINT h;
+  if (!get_h(&h)) {
+    goto err;
+  }
+
+  // Generate DLEQ2 proof for the validity token.
+
+  // ks0, ks1 <- Zp
+  EC_SCALAR ks0, ks1;
+  if (!ec_random_nonzero_scalar(group, &ks0, kDefaultAdditionalData) ||
+      !ec_random_nonzero_scalar(group, &ks1, kDefaultAdditionalData)) {
+    goto err;
+  }
+
+  // Ks = ks0*(G;T) + ks1*(H;S)
+  EC_RAW_POINT Ks0, Ks1;
+  if (!mul_twice_base(group, &Ks0, &ks0, &h, &ks1) ||
+      !mul_twice(group, &Ks1, T, &ks0, S, &ks1)) {
+    goto err;
+  }
+
+  // cs = Hc(...)
+  EC_SCALAR cs;
+  if (!hash_c_dleq(group, &cs, &priv->pubs, T, S, Ws, &Ks0, &Ks1)) {
+    goto err;
+  }
+
+  EC_SCALAR cs_mont;
+  ec_scalar_to_montgomery(group, &cs_mont, &cs);
+
+  // In each of these products, only one operand is in Montgomery form, so the
+  // product does not need to be converted.
+
+  // us = ks0 + cs*xs
+  EC_SCALAR us;
+  ec_scalar_mul_montgomery(group, &us, &priv->xs, &cs_mont);
+  ec_scalar_add(group, &us, &ks0, &us);
+
+  // vs = ks1 + cs*ys
+  EC_SCALAR vs;
+  ec_scalar_mul_montgomery(group, &vs, &priv->ys, &cs_mont);
+  ec_scalar_add(group, &vs, &ks1, &vs);
+
+  // Store DLEQ2 proof in transcript.
+  if (!scalar_to_cbb(&proof, group, &cs) ||
+      !scalar_to_cbb(&proof, group, &us) ||
+      !scalar_to_cbb(&proof, group, &vs)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    goto err;
+  }
+
+  // Generate DLEQOR2 proof for the private metadata token.
+  BN_ULONG mask = ((BN_ULONG)0) - (private_metadata&1);
+
+  // Select values of xb, yb (keys corresponding to the private metadata value)
+  // and pubo (public key corresponding to the other value) in constant time.
+  EC_RAW_POINT pubo;
+  EC_SCALAR xb, yb;
+  ec_scalar_select(group, &xb, mask, &priv->x1, &priv->x0);
+  ec_scalar_select(group, &yb, mask, &priv->y1, &priv->y0);
+  ec_point_select(group, &pubo, mask, &priv->pub0, &priv->pub1);
+
+  // k0, k1 <- Zp
+  EC_SCALAR k0, k1;
+  if (!ec_random_nonzero_scalar(group, &k0, kDefaultAdditionalData) ||
+      !ec_random_nonzero_scalar(group, &k1, kDefaultAdditionalData)) {
+    goto err;
+  }
+
+  // Kb = k0*(G;T) + k1*(H;S)
+  EC_RAW_POINT Kb0, Kb1;
+  if (!mul_twice_base(group, &Kb0, &k0, &h, &k1) ||
+      !mul_twice(group, &Kb1, T, &k0, S, &k1)) {
+    goto err;
+  }
+
+  // co, uo, vo <- Zp
+  EC_SCALAR co, uo, vo;
+  if (!ec_random_nonzero_scalar(group, &co, kDefaultAdditionalData) ||
+      !ec_random_nonzero_scalar(group, &uo, kDefaultAdditionalData) ||
+      !ec_random_nonzero_scalar(group, &vo, kDefaultAdditionalData)) {
+    goto err;
+  }
+
+  // Ko = uo*(G;T) + vo*(H;S) - co*(pubo;W)
+  EC_RAW_POINT Ko0, Ko1;
+  if (!mul_add_and_sub(group, &Ko0, &Ko1, T, &uo, &h, S, &vo, &pubo, W, &co)) {
+    goto err;
+  }
+
+  // Select the K corresponding to K0 and K1 in constant-time.
+  EC_RAW_POINT K00, K01, K10, K11;
+  ec_point_select(group, &K00, mask, &Ko0, &Kb0);
+  ec_point_select(group, &K01, mask, &Ko1, &Kb1);
+  ec_point_select(group, &K10, mask, &Kb0, &Ko0);
+  ec_point_select(group, &K11, mask, &Kb1, &Ko1);
+
+  // c = Hc(...)
+  EC_SCALAR c;
+  if (!hash_c_dleqor(group, &c, &priv->pub0, &priv->pub1, T, S, W, &K00, &K01,
+                     &K10, &K11)) {
+    goto err;
+  }
+
+  // cb = c - co
+  EC_SCALAR cb, ub, vb;
+  ec_scalar_sub(group, &cb, &c, &co);
+
+  EC_SCALAR cb_mont;
+  ec_scalar_to_montgomery(group, &cb_mont, &cb);
+
+  // In each of these products, only one operand is in Montgomery form, so the
+  // product does not need to be converted.
+
+  // ub = k0 + cb*xb
+  ec_scalar_mul_montgomery(group, &ub, &xb, &cb_mont);
+  ec_scalar_add(group, &ub, &k0, &ub);
+
+  // vb = k1 + cb*yb
+  ec_scalar_mul_montgomery(group, &vb, &yb, &cb_mont);
+  ec_scalar_add(group, &vb, &k1, &vb);
+
+  // Select c, u, v in constant-time.
+  EC_SCALAR c0, c1, u0, u1, v0, v1;
+  ec_scalar_select(group, &c0, mask, &co, &cb);
+  ec_scalar_select(group, &u0, mask, &uo, &ub);
+  ec_scalar_select(group, &v0, mask, &vo, &vb);
+  ec_scalar_select(group, &c1, mask, &cb, &co);
+  ec_scalar_select(group, &u1, mask, &ub, &uo);
+  ec_scalar_select(group, &v1, mask, &vb, &vo);
+
+  // Store DLEQOR2 proof in transcript.
+  if (!scalar_to_cbb(&proof, group, &c0) ||
+      !scalar_to_cbb(&proof, group, &c1) ||
+      !scalar_to_cbb(&proof, group, &u0) ||
+      !scalar_to_cbb(&proof, group, &u1) ||
+      !scalar_to_cbb(&proof, group, &v0) ||
+      !scalar_to_cbb(&proof, group, &v1) ||
+      !CBB_finish(&proof, out_proof, out_proof_len)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    goto err;
+  }
+
+  ok = 1;
+
+err:
+  CBB_cleanup(&proof);
+  return ok;
+}
+
+static int dleq_verify(const EC_GROUP *group, const uint8_t *proof,
+                       size_t proof_len,
+                       const struct trust_token_client_key_st *pub,
+                       const EC_RAW_POINT *T, const EC_RAW_POINT *S,
+                       const EC_RAW_POINT *W, const EC_RAW_POINT *Ws) {
+  EC_RAW_POINT h;
+  if (!get_h(&h)) {
+    return 0;
+  }
+
+  // Verify the DLEQ2 proof over the validity token.
+
+  CBS cbs;
+  CBS_init(&cbs, proof, proof_len);
+  EC_SCALAR cs, us, vs;
+  if (!scalar_from_cbs(&cbs, group, &cs) ||
+      !scalar_from_cbs(&cbs, group, &us) ||
+      !scalar_from_cbs(&cbs, group, &vs)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
+    return 0;
+  }
+
+  // Ks = us*(G;T) + vs*(H;S) - cs*(pubs;Ws)
+  EC_RAW_POINT Ks0, Ks1;
+  if (!mul_add_and_sub(group, &Ks0, &Ks1, T, &us, &h, S, &vs, &pub->pubs, Ws,
+                       &cs)) {
+    return 0;
+  }
+
+  // calculated = Hc(...)
+  EC_SCALAR calculated;
+  if (!hash_c_dleq(group, &calculated, &pub->pubs, T, S, Ws, &Ks0, &Ks1)) {
+    return 0;
+  }
+
+  // cs == calculated
+  if (!ec_scalar_equal_vartime(group, &cs, &calculated)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_INVALID_PROOF);
+    return 0;
+  }
+
+  // Verify the DLEQOR2 proof over the private metadata token.
+
+  EC_SCALAR c0, c1, u0, u1, v0, v1;
+  if (!scalar_from_cbs(&cbs, group, &c0) ||
+      !scalar_from_cbs(&cbs, group, &c1) ||
+      !scalar_from_cbs(&cbs, group, &u0) ||
+      !scalar_from_cbs(&cbs, group, &u1) ||
+      !scalar_from_cbs(&cbs, group, &v0) ||
+      !scalar_from_cbs(&cbs, group, &v1) ||
+      CBS_len(&cbs) != 0) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
+    return 0;
+  }
+
+  // K0 = u0*(G;T) + v0*(H;S) - c0*(pub0;W)
+  EC_RAW_POINT K00, K01;
+  if (!mul_add_and_sub(group, &K00, &K01, T, &u0, &h, S, &v0, &pub->pub0, W,
+                       &c0)) {
+    return 0;
+  }
+
+  // K1 = u1*(G;T) + v1*(H;S) - c1*(pub1;Ws)
+  EC_RAW_POINT K10, K11;
+  if (!mul_add_and_sub(group, &K10, &K11, T, &u1, &h, S, &v1, &pub->pub1, W,
+                       &c1)) {
+    return 0;
+  }
+
+  // calculated = Hc(...)
+  if (!hash_c_dleqor(group, &calculated, &pub->pub0, &pub->pub1, T, S, W, &K00,
+                     &K01, &K10, &K11)) {
+    return 0;
+  }
+
+  // c = c0 + c1
+  EC_SCALAR c;
+  ec_scalar_add(group, &c, &c0, &c1);
+
+  // c == calculated
+  if (!ec_scalar_equal_vartime(group, &c, &calculated)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_INVALID_PROOF);
+    return 0;
+  }
+
+  return 1;
+}
+
 int pmbtoken_sign(const TRUST_TOKEN_ISSUER *ctx,
                   uint8_t out_s[PMBTOKEN_NONCE_SIZE], EC_RAW_POINT *out_Wp,
-                  EC_RAW_POINT *out_Wsp, const EC_RAW_POINT *Tp,
+                  EC_RAW_POINT *out_Wsp, uint8_t **out_proof,
+                  size_t *out_proof_len, const EC_RAW_POINT *Tp,
                   uint32_t key_id, uint8_t private_metadata) {
   EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp521r1);
   if (group == NULL) {
@@ -341,7 +748,7 @@
   }
 
   EC_SCALAR xb, yb;
-  BN_ULONG mask = ((BN_ULONG)0) - (private_metadata&1);
+  BN_ULONG mask = ((BN_ULONG)0) - (private_metadata & 1);
   ec_scalar_select(group, &xb, mask, &key->x1, &key->x0);
   ec_scalar_select(group, &yb, mask, &key->y1, &key->y0);
 
@@ -352,31 +759,35 @@
     return 0;
   }
 
-  if (!mul_g_and_p(group, out_Wp, Tp, &xb, &Sp, &yb) ||
-      !mul_g_and_p(group, out_Wsp, Tp, &key->xs, &Sp, &key->ys)) {
+  if (!mul_twice(group, out_Wp, Tp, &xb, &Sp, &yb) ||
+      !mul_twice(group, out_Wsp, Tp, &key->xs, &Sp, &key->ys)) {
     return 0;
   }
 
-  // TODO: DLEQ Proofs
-  return 1;
+  return dleq_generate(group, out_proof, out_proof_len, key, Tp, &Sp, out_Wp,
+                       out_Wsp, private_metadata);
 }
 
 int pmbtoken_unblind(PMBTOKEN_TOKEN *out_token,
+                     const struct trust_token_client_key_st *key,
                      const uint8_t s[PMBTOKEN_NONCE_SIZE],
                      const EC_RAW_POINT *Wp, const EC_RAW_POINT *Wsp,
+                     const uint8_t *proof, size_t proof_len,
                      const PMBTOKEN_PRETOKEN *pretoken) {
   EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp521r1);
   if (group == NULL) {
     return 0;
   }
 
-  // TODO: Check DLEQ Proofs
-
   EC_RAW_POINT Sp;
   if (!hash_s(group, &Sp, &pretoken->Tp, s)) {
     return 0;
   }
 
+  if (!dleq_verify(group, proof, proof_len, key, &pretoken->Tp, &Sp, Wp, Wsp)) {
+    return 0;
+  }
+
   OPENSSL_memcpy(out_token->t, pretoken->t, PMBTOKEN_NONCE_SIZE);
   if (!ec_point_mul_scalar(group, &out_token->S, &Sp, &pretoken->r) ||
       !ec_point_mul_scalar(group, &out_token->W, Wp, &pretoken->r) ||
@@ -418,15 +829,15 @@
 
   EC_RAW_POINT calculated;
   // Check the validity of the token.
-  if (!mul_g_and_p(group, &calculated, &T, &key->xs, &token->S, &key->ys) ||
+  if (!mul_twice(group, &calculated, &T, &key->xs, &token->S, &key->ys) ||
       !ec_GFp_simple_points_equal(group, &calculated, &token->Ws)) {
     OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_BAD_VALIDITY_CHECK);
     return 0;
   }
 
   EC_RAW_POINT W0, W1;
-  if (!mul_g_and_p(group, &W0, &T, &key->x0, &token->S, &key->y0) ||
-      !mul_g_and_p(group, &W1, &T, &key->x1, &token->S, &key->y1)) {
+  if (!mul_twice(group, &W0, &T, &key->x0, &token->S, &key->y0) ||
+      !mul_twice(group, &W1, &T, &key->x1, &token->S, &key->y1)) {
     return 0;
   }
 
diff --git a/crypto/trust_token/trust_token.c b/crypto/trust_token/trust_token.c
index bc93562..9826f42 100644
--- a/crypto/trust_token/trust_token.c
+++ b/crypto/trust_token/trust_token.c
@@ -228,17 +228,19 @@
   for (size_t i = 0; i < count; i++) {
     uint8_t s[PMBTOKEN_NONCE_SIZE];
     EC_RAW_POINT Wp, Wsp;
+    CBS proof;
     if (!CBS_copy_bytes(&in, s, PMBTOKEN_NONCE_SIZE) ||
         !cbs_get_raw_point(&in, group, &Wp) ||
-        !cbs_get_raw_point(&in, group, &Wsp)) {
+        !cbs_get_raw_point(&in, group, &Wsp) ||
+        !CBS_get_u16_length_prefixed(&in, &proof)) {
       OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
       goto err;
     }
 
     PMBTOKEN_PRETOKEN *pretoken = sk_PMBTOKEN_PRETOKEN_value(ctx->pretokens, i);
     PMBTOKEN_TOKEN pmbtoken;
-    if (!pmbtoken_unblind(&pmbtoken, s, &Wp, &Wsp, pretoken)) {
-      OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
+    if (!pmbtoken_unblind(&pmbtoken, key, s, &Wp, &Wsp, CBS_data(&proof),
+                          CBS_len(&proof), pretoken)) {
       goto err;
     }
 
@@ -408,6 +410,11 @@
      return 0;
     }
   }
+
+  if (!pmbtoken_compute_public(key_s)) {
+    return 0;
+  }
+
   key_s->id = key_id;
   ctx->num_keys += 1;
   return 1;
@@ -498,16 +505,22 @@
 
     uint8_t s[PMBTOKEN_NONCE_SIZE];
     EC_RAW_POINT Wp, Wsp;
-    if (!pmbtoken_sign(ctx, s, &Wp, &Wsp, &Tp, public_metadata,
-                       private_metadata)) {
+    uint8_t *proof = NULL;
+    size_t proof_len;
+    if (!pmbtoken_sign(ctx, s, &Wp, &Wsp, &proof, &proof_len, &Tp,
+                       public_metadata, private_metadata)) {
       goto err;
     }
 
     if (!CBB_add_bytes(&response, s, PMBTOKEN_NONCE_SIZE) ||
         !cbb_add_raw_point(&response, group, &Wp) ||
-        !cbb_add_raw_point(&response, group, &Wsp)) {
+        !cbb_add_raw_point(&response, group, &Wsp) ||
+        !CBB_add_u16(&response, proof_len) ||
+        !CBB_add_bytes(&response, proof, proof_len)) {
+      OPENSSL_free(proof);
       goto err;
     }
+    OPENSSL_free(proof);
   }
 
   *out_tokens_issued = count;
diff --git a/crypto/trust_token/trust_token_test.cc b/crypto/trust_token/trust_token_test.cc
index 1d3f543..5ab3995 100644
--- a/crypto/trust_token/trust_token_test.cc
+++ b/crypto/trust_token/trust_token_test.cc
@@ -436,6 +436,119 @@
   ASSERT_EQ(sk_TRUST_TOKEN_num(tokens.get()), 1UL);
 }
 
+
+TEST_P(TrustTokenMetadataTest, TruncatedProof) {
+  ASSERT_NO_FATAL_FAILURE(SetupContexts());
+
+  uint8_t *issue_msg = NULL, *issue_resp = NULL;
+  size_t msg_len, resp_len;
+  ASSERT_TRUE(TRUST_TOKEN_CLIENT_begin_issuance(client.get(), &issue_msg,
+                                                &msg_len, 10));
+  bssl::UniquePtr<uint8_t> free_issue_msg(issue_msg);
+  uint8_t tokens_issued;
+  ASSERT_TRUE(TRUST_TOKEN_ISSUER_issue(
+      issuer.get(), &issue_resp, &resp_len, &tokens_issued, issue_msg, msg_len,
+      std::get<0>(GetParam()), std::get<1>(GetParam()), /*max_issuance=*/1));
+  bssl::UniquePtr<uint8_t> free_msg(issue_resp);
+
+  CBS real_response;
+  CBS_init(&real_response, issue_resp, resp_len);
+  uint16_t count;
+  uint32_t public_metadata;
+  bssl::ScopedCBB bad_response;
+  ASSERT_TRUE(CBB_init(bad_response.get(), 0));
+  ASSERT_TRUE(CBS_get_u16(&real_response, &count));
+  ASSERT_TRUE(CBB_add_u16(bad_response.get(), count));
+  ASSERT_TRUE(CBS_get_u32(&real_response, &public_metadata));
+  ASSERT_TRUE(CBB_add_u32(bad_response.get(), public_metadata));
+
+  for (size_t i = 0; i < count; i++) {
+    uint8_t s[PMBTOKEN_NONCE_SIZE];
+    CBS tmp;
+    ASSERT_TRUE(CBS_copy_bytes(&real_response, s, PMBTOKEN_NONCE_SIZE));
+    ASSERT_TRUE(CBB_add_bytes(bad_response.get(), s, PMBTOKEN_NONCE_SIZE));
+    ASSERT_TRUE(CBS_get_u16_length_prefixed(&real_response, &tmp));
+    ASSERT_TRUE(CBB_add_u16(bad_response.get(), CBS_len(&tmp)));
+    ASSERT_TRUE(
+        CBB_add_bytes(bad_response.get(), CBS_data(&tmp), CBS_len(&tmp)));
+    ASSERT_TRUE(CBS_get_u16_length_prefixed(&real_response, &tmp));
+    ASSERT_TRUE(CBB_add_u16(bad_response.get(), CBS_len(&tmp)));
+    ASSERT_TRUE(
+        CBB_add_bytes(bad_response.get(), CBS_data(&tmp), CBS_len(&tmp)));
+    ASSERT_TRUE(CBS_get_u16_length_prefixed(&real_response, &tmp));
+    ASSERT_TRUE(CBB_add_u16(bad_response.get(), CBS_len(&tmp) - 2));
+    ASSERT_TRUE(
+        CBB_add_bytes(bad_response.get(), CBS_data(&tmp), CBS_len(&tmp) - 2));
+  }
+
+  uint8_t *bad_buf;
+  size_t bad_len;
+  ASSERT_TRUE(CBB_finish(bad_response.get(), &bad_buf, &bad_len));
+  bssl::UniquePtr<uint8_t> free_bad(bad_buf);
+
+  size_t key_index;
+  bssl::UniquePtr<STACK_OF(TRUST_TOKEN)> tokens(
+      TRUST_TOKEN_CLIENT_finish_issuance(client.get(), &key_index, bad_buf, bad_len));
+  ASSERT_FALSE(tokens);
+}
+
+TEST_P(TrustTokenMetadataTest, ExcessDataProof) {
+  ASSERT_NO_FATAL_FAILURE(SetupContexts());
+
+  uint8_t *issue_msg = NULL, *issue_resp = NULL;
+  size_t msg_len, resp_len;
+  ASSERT_TRUE(TRUST_TOKEN_CLIENT_begin_issuance(client.get(), &issue_msg,
+                                                &msg_len, 10));
+  bssl::UniquePtr<uint8_t> free_issue_msg(issue_msg);
+  uint8_t tokens_issued;
+  ASSERT_TRUE(TRUST_TOKEN_ISSUER_issue(
+      issuer.get(), &issue_resp, &resp_len, &tokens_issued, issue_msg, msg_len,
+      std::get<0>(GetParam()), std::get<1>(GetParam()), /*max_issuance=*/1));
+  bssl::UniquePtr<uint8_t> free_msg(issue_resp);
+
+  CBS real_response;
+  CBS_init(&real_response, issue_resp, resp_len);
+  uint16_t count;
+  uint32_t public_metadata;
+  bssl::ScopedCBB bad_response;
+  ASSERT_TRUE(CBB_init(bad_response.get(), 0));
+  ASSERT_TRUE(CBS_get_u16(&real_response, &count));
+  ASSERT_TRUE(CBB_add_u16(bad_response.get(), count));
+  ASSERT_TRUE(CBS_get_u32(&real_response, &public_metadata));
+  ASSERT_TRUE(CBB_add_u32(bad_response.get(), public_metadata));
+
+  for (size_t i = 0; i < count; i++) {
+    uint8_t s[PMBTOKEN_NONCE_SIZE];
+    CBS tmp;
+    ASSERT_TRUE(CBS_copy_bytes(&real_response, s, PMBTOKEN_NONCE_SIZE));
+    ASSERT_TRUE(CBB_add_bytes(bad_response.get(), s, PMBTOKEN_NONCE_SIZE));
+    ASSERT_TRUE(CBS_get_u16_length_prefixed(&real_response, &tmp));
+    ASSERT_TRUE(CBB_add_u16(bad_response.get(), CBS_len(&tmp)));
+    ASSERT_TRUE(
+        CBB_add_bytes(bad_response.get(), CBS_data(&tmp), CBS_len(&tmp)));
+    ASSERT_TRUE(CBS_get_u16_length_prefixed(&real_response, &tmp));
+    ASSERT_TRUE(CBB_add_u16(bad_response.get(), CBS_len(&tmp)));
+    ASSERT_TRUE(
+        CBB_add_bytes(bad_response.get(), CBS_data(&tmp), CBS_len(&tmp)));
+    ASSERT_TRUE(CBS_get_u16_length_prefixed(&real_response, &tmp));
+    ASSERT_TRUE(CBB_add_u16(bad_response.get(), CBS_len(&tmp) + 2));
+    ASSERT_TRUE(
+        CBB_add_bytes(bad_response.get(), CBS_data(&tmp), CBS_len(&tmp)));
+    ASSERT_TRUE(CBB_add_u16(bad_response.get(), 42));
+  }
+
+  uint8_t *bad_buf;
+  size_t bad_len;
+  ASSERT_TRUE(CBB_finish(bad_response.get(), &bad_buf, &bad_len));
+  bssl::UniquePtr<uint8_t> free_bad(bad_buf);
+
+  size_t key_index;
+  bssl::UniquePtr<STACK_OF(TRUST_TOKEN)> tokens(
+      TRUST_TOKEN_CLIENT_finish_issuance(client.get(), &key_index, bad_buf,
+                                         bad_len));
+  ASSERT_FALSE(tokens);
+}
+
 INSTANTIATE_TEST_SUITE_P(
     TrustTokenAllMetadataTest, TrustTokenMetadataTest,
     testing::Combine(testing::Values(TrustTokenProtocolTest::KeyID(0),
@@ -443,5 +556,51 @@
                                      TrustTokenProtocolTest::KeyID(2)),
                      testing::Bool()));
 
+
+class TrustTokenBadKeyTest
+    : public TrustTokenProtocolTest,
+      public testing::WithParamInterface<std::tuple<bool, int>> {};
+
+TEST_P(TrustTokenBadKeyTest, BadKey) {
+  ASSERT_NO_FATAL_FAILURE(SetupContexts());
+
+  uint8_t *issue_msg = NULL, *issue_resp = NULL;
+  size_t msg_len, resp_len;
+  ASSERT_TRUE(TRUST_TOKEN_CLIENT_begin_issuance(client.get(), &issue_msg,
+                                                &msg_len, 10));
+  bssl::UniquePtr<uint8_t> free_issue_msg(issue_msg);
+
+  struct trust_token_issuer_key_st *key = &issuer->keys[0];
+  EC_SCALAR *scalars[] = {&key->x0, &key->y0, &key->x1,
+                          &key->y1, &key->xs, &key->ys};
+  int corrupted_key = std::get<1>(GetParam());
+
+  // Corrupt private key scalar.
+  scalars[corrupted_key]->bytes[0] ^= 42;
+
+  uint8_t tokens_issued;
+  ASSERT_TRUE(TRUST_TOKEN_ISSUER_issue(
+      issuer.get(), &issue_resp, &resp_len, &tokens_issued, issue_msg, msg_len,
+      /*public_metadata=*/7, std::get<0>(GetParam()), /*max_issuance=*/1));
+  bssl::UniquePtr<uint8_t> free_msg(issue_resp);
+  size_t key_index;
+  bssl::UniquePtr<STACK_OF(TRUST_TOKEN)> tokens(
+      TRUST_TOKEN_CLIENT_finish_issuance(client.get(), &key_index, issue_resp,
+                                         resp_len));
+
+  // If the unused private key is corrupted, then the DLEQ proof should succeed.
+  if ((corrupted_key / 2 == 0 && std::get<0>(GetParam()) == true) ||
+      (corrupted_key / 2 == 1 && std::get<0>(GetParam()) == false)) {
+    ASSERT_TRUE(tokens);
+  } else {
+    ASSERT_FALSE(tokens);
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    TrustTokenAllBadKeyTest, TrustTokenBadKeyTest,
+    testing::Combine(testing::Bool(),
+                     testing::Values(0, 1, 2, 3, 4, 5)));
+
 }  // namespace
 BSSL_NAMESPACE_END
diff --git a/include/openssl/trust_token.h b/include/openssl/trust_token.h
index 473999d..8bd1eda 100644
--- a/include/openssl/trust_token.h
+++ b/include/openssl/trust_token.h
@@ -273,5 +273,6 @@
 #define TRUST_TOKEN_R_BAD_VALIDITY_CHECK 111
 #define TRUST_TOKEN_R_NO_SRR_KEY_CONFIGURED 112
 #define TRUST_TOKEN_R_INVALID_METADATA_KEY 113
+#define TRUST_TOKEN_R_INVALID_PROOF 114
 
 #endif  // OPENSSL_HEADER_TRUST_TOKEN_H