Add TrustTokenV2.

Changes:
- Remove point prefixes.
- Don't verify SRR on the client.

TODO:
- Replace SRR generation with RR generation on issuer.
- Add finalized PrivacyPass version.

Change-Id: Ibfb04aaba2cf669639af77299da22ab668175edb
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/42824
Commit-Queue: Steven Valdez <svaldez@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/crypto/trust_token/internal.h b/crypto/trust_token/internal.h
index a44de76..c935888 100644
--- a/crypto/trust_token/internal.h
+++ b/crypto/trust_token/internal.h
@@ -110,6 +110,39 @@
 // function is used to confirm H was computed as expected.
 OPENSSL_EXPORT int pmbtoken_exp1_get_h_for_testing(uint8_t out[97]);
 
+// The following functions implement the corresponding |TRUST_TOKENS_METHOD|
+// functions for |TRUST_TOKENS_experiment_v2|'s PMBTokens construction which
+// uses P-384.
+//
+// We use P-384 instead of our usual choice of P-256. See Appendix I which
+// describes two attacks which may affect smaller curves. In particular, p-1 for
+// P-256 is smooth, giving a low complexity for the p-1 attack. P-384's p-1 has
+// a 281-bit prime factor,
+// 3055465788140352002733946906144561090641249606160407884365391979704929268480326390471.
+// This lower-bounds the p-1 attack at O(2^140). The p+1 attack is lower-bounded
+// by O(p^(1/3)) or O(2^128), so we do not need to check the smoothness of p+1.
+int pmbtoken_exp2_generate_key(CBB *out_private, CBB *out_public);
+int pmbtoken_exp2_client_key_from_bytes(PMBTOKEN_CLIENT_KEY *key,
+                                        const uint8_t *in, size_t len);
+int pmbtoken_exp2_issuer_key_from_bytes(PMBTOKEN_ISSUER_KEY *key,
+                                        const uint8_t *in, size_t len);
+STACK_OF(PMBTOKEN_PRETOKEN) * pmbtoken_exp2_blind(CBB *cbb, size_t count);
+int pmbtoken_exp2_sign(const PMBTOKEN_ISSUER_KEY *key, CBB *cbb, CBS *cbs,
+                       size_t num_requested, size_t num_to_issue,
+                       uint8_t private_metadata);
+STACK_OF(TRUST_TOKEN) *
+    pmbtoken_exp2_unblind(const PMBTOKEN_CLIENT_KEY *key,
+                          const STACK_OF(PMBTOKEN_PRETOKEN) * pretokens,
+                          CBS *cbs, size_t count, uint32_t key_id);
+int pmbtoken_exp2_read(const PMBTOKEN_ISSUER_KEY *key,
+                       uint8_t out_nonce[PMBTOKEN_NONCE_SIZE],
+                       uint8_t *out_private_metadata, const uint8_t *token,
+                       size_t token_len);
+
+// pmbtoken_exp2_get_h_for_testing returns H in uncompressed coordinates. This
+// function is used to confirm H was computed as expected.
+OPENSSL_EXPORT int pmbtoken_exp2_get_h_for_testing(uint8_t out[97]);
+
 
 // Trust Tokens internals.
 
@@ -172,6 +205,15 @@
               uint8_t out_nonce[PMBTOKEN_NONCE_SIZE],
               uint8_t *out_private_metadata, const uint8_t *token,
               size_t token_len);
+
+  // whether the construction supports private metadata.
+  int has_private_metadata;
+
+  // max keys that can be configured.
+  size_t max_keys;
+
+  // whether the SRR is part of the protocol.
+  int has_srr;
 };
 
 // Structure representing a single Trust Token public key with the specified ID.
@@ -195,7 +237,7 @@
 
   // keys is the set of public keys that are supported by the client for
   // issuance/redemptions.
-  struct trust_token_client_key_st keys[3];
+  struct trust_token_client_key_st keys[6];
 
   // num_keys is the number of keys currently configured.
   size_t num_keys;
@@ -217,7 +259,7 @@
   // keys is the set of private keys that are supported by the issuer for
   // issuance/redemptions. The public metadata is an index into this list of
   // keys.
-  struct trust_token_issuer_key_st keys[3];
+  struct trust_token_issuer_key_st keys[6];
 
   // num_keys is the number of keys currently configured.
   size_t num_keys;
diff --git a/crypto/trust_token/pmbtoken.c b/crypto/trust_token/pmbtoken.c
index 5ea60c3..f9132e6 100644
--- a/crypto/trust_token/pmbtoken.c
+++ b/crypto/trust_token/pmbtoken.c
@@ -52,6 +52,7 @@
   // hash_c implements the H_c operation in PMBTokens. It returns one on success
   // and zero on error.
   hash_c_func_t hash_c;
+  int prefix_point : 1;
 } PMBTOKEN_METHOD;
 
 static const uint8_t kDefaultAdditionalData[32] = {0};
@@ -59,7 +60,7 @@
 static int pmbtoken_init_method(PMBTOKEN_METHOD *method, int curve_nid,
                                 const uint8_t *h_bytes, size_t h_len,
                                 hash_t_func_t hash_t, hash_s_func_t hash_s,
-                                hash_c_func_t hash_c) {
+                                hash_c_func_t hash_c, int prefix_point) {
   method->group = EC_GROUP_new_by_curve_name(curve_nid);
   if (method->group == NULL) {
     return 0;
@@ -68,6 +69,7 @@
   method->hash_t = hash_t;
   method->hash_s = hash_s;
   method->hash_c = hash_c;
+  method->prefix_point = prefix_point;
 
   EC_AFFINE h;
   if (!ec_point_from_uncompressed(method->group, &h, h_bytes, h_len)) {
@@ -113,11 +115,40 @@
                            len) == len;
 }
 
+static int cbb_add_prefixed_point(CBB *out, const EC_GROUP *group,
+                                  const EC_AFFINE *point, int prefix_point) {
+  if (prefix_point) {
+    CBB child;
+    if (!CBB_add_u16_length_prefixed(out, &child) ||
+        !point_to_cbb(&child, group, point) ||
+        !CBB_flush(out)) {
+      return 0;
+    }
+  } else {
+    if (!point_to_cbb(out, group, point) ||
+        !CBB_flush(out)) {
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
 static int cbs_get_prefixed_point(CBS *cbs, const EC_GROUP *group,
-                                  EC_AFFINE *out) {
+                                  EC_AFFINE *out, int prefix_point) {
   CBS child;
-  if (!CBS_get_u16_length_prefixed(cbs, &child) ||
-      !ec_point_from_uncompressed(group, out, CBS_data(&child),
+  if (prefix_point) {
+    if (!CBS_get_u16_length_prefixed(cbs, &child)) {
+      return 0;
+    }
+  } else {
+    size_t plen = 1 + 2 * BN_num_bytes(&group->field);
+    if (!CBS_get_bytes(cbs, &child, plen)) {
+      return 0;
+    }
+  }
+
+  if (!ec_point_from_uncompressed(group, out, CBS_data(&child),
                                   CBS_len(&child))) {
     return 0;
   }
@@ -166,16 +197,12 @@
     return 0;
   }
 
-  // TODO(https://crbug.com/boringssl/331): When updating the key format, remove
-  // the redundant length prefixes.
-  CBB child;
-  if (!CBB_add_u16_length_prefixed(out_public, &child) ||
-      !point_to_cbb(&child, group, &pub_affine[0]) ||
-      !CBB_add_u16_length_prefixed(out_public, &child) ||
-      !point_to_cbb(&child, group, &pub_affine[1]) ||
-      !CBB_add_u16_length_prefixed(out_public, &child) ||
-      !point_to_cbb(&child, group, &pub_affine[2]) ||
-      !CBB_flush(out_public)) {
+  if (!cbb_add_prefixed_point(out_public, group, &pub_affine[0],
+                              method->prefix_point) ||
+      !cbb_add_prefixed_point(out_public, group, &pub_affine[1],
+                              method->prefix_point) ||
+      !cbb_add_prefixed_point(out_public, group, &pub_affine[2],
+                              method->prefix_point)) {
     OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_BUFFER_TOO_SMALL);
     return 0;
   }
@@ -186,13 +213,14 @@
 static int pmbtoken_client_key_from_bytes(const PMBTOKEN_METHOD *method,
                                           PMBTOKEN_CLIENT_KEY *key,
                                           const uint8_t *in, size_t len) {
-  // TODO(https://crbug.com/boringssl/331): When updating the key format, remove
-  // the redundant length prefixes.
   CBS cbs;
   CBS_init(&cbs, in, len);
-  if (!cbs_get_prefixed_point(&cbs, method->group, &key->pub0) ||
-      !cbs_get_prefixed_point(&cbs, method->group, &key->pub1) ||
-      !cbs_get_prefixed_point(&cbs, method->group, &key->pubs) ||
+  if (!cbs_get_prefixed_point(&cbs, method->group, &key->pub0,
+                              method->prefix_point) ||
+      !cbs_get_prefixed_point(&cbs, method->group, &key->pub1,
+                              method->prefix_point) ||
+      !cbs_get_prefixed_point(&cbs, method->group, &key->pubs,
+                              method->prefix_point) ||
       CBS_len(&cbs) != 0) {
     OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
     return 0;
@@ -282,12 +310,8 @@
       goto err;
     }
 
-    // TODO(https://crbug.com/boringssl/331): When updating the key format,
-    // remove the redundant length prefixes.
-    CBB child;
-    if (!CBB_add_u16_length_prefixed(cbb, &child) ||
-        !point_to_cbb(&child, group, &pretoken->Tp) ||
-        !CBB_flush(cbb)) {
+    if (!cbb_add_prefixed_point(cbb, group, &pretoken->Tp,
+                                method->prefix_point)) {
       goto err;
     }
   }
@@ -750,7 +774,7 @@
   for (size_t i = 0; i < num_to_issue; i++) {
     EC_AFFINE Tp_affine;
     EC_RAW_POINT Tp;
-    if (!cbs_get_prefixed_point(cbs, group, &Tp_affine)) {
+    if (!cbs_get_prefixed_point(cbs, group, &Tp_affine, method->prefix_point)) {
       OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
       goto err;
     }
@@ -766,7 +790,6 @@
     // The |jacobians| and |affines| contain Sp, Wp, and Wsp.
     EC_RAW_POINT jacobians[3];
     EC_AFFINE affines[3];
-    CBB child;
     if (!method->hash_s(group, &jacobians[0], &Tp_affine, s) ||
         !ec_point_mul_scalar_batch(group, &jacobians[1], &Tp, &xb,
                                    &jacobians[0], &yb, NULL, NULL) ||
@@ -774,12 +797,8 @@
                                    &jacobians[0], &key->ys, NULL, NULL) ||
         !ec_jacobian_to_affine_batch(group, affines, jacobians, 3) ||
         !CBB_add_bytes(cbb, s, PMBTOKEN_NONCE_SIZE) ||
-        // TODO(https://crbug.com/boringssl/331): When updating the key format,
-        // remove the redundant length prefixes.
-        !CBB_add_u16_length_prefixed(cbb, &child) ||
-        !point_to_cbb(&child, group, &affines[1]) ||
-        !CBB_add_u16_length_prefixed(cbb, &child) ||
-        !point_to_cbb(&child, group, &affines[2])) {
+        !cbb_add_prefixed_point(cbb, group, &affines[1], method->prefix_point) ||
+        !cbb_add_prefixed_point(cbb, group, &affines[2], method->prefix_point)) {
       goto err;
     }
 
@@ -835,7 +854,11 @@
 
   // Skip over any unused requests.
   size_t point_len = 1 + 2 * BN_num_bytes(&group->field);
-  if (!CBS_skip(cbs, (2 + point_len) * (num_requested - num_to_issue))) {
+  size_t token_len = point_len;
+  if (method->prefix_point) {
+    token_len += 2;
+  }
+  if (!CBS_skip(cbs, token_len * (num_requested - num_to_issue))) {
     OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
     goto err;
   }
@@ -902,8 +925,9 @@
     uint8_t s[PMBTOKEN_NONCE_SIZE];
     EC_AFFINE Wp_affine, Wsp_affine;
     if (!CBS_copy_bytes(cbs, s, PMBTOKEN_NONCE_SIZE) ||
-        !cbs_get_prefixed_point(cbs, group, &Wp_affine) ||
-        !cbs_get_prefixed_point(cbs, group, &Wsp_affine)) {
+        !cbs_get_prefixed_point(cbs, group, &Wp_affine, method->prefix_point) ||
+        !cbs_get_prefixed_point(cbs, group, &Wsp_affine,
+                                method->prefix_point)) {
       OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
       goto err;
     }
@@ -937,19 +961,17 @@
 
     // Serialize the token. Include |key_id| to avoid an extra copy in the layer
     // above.
-    CBB token_cbb, child;
+    CBB token_cbb;
     size_t point_len = 1 + 2 * BN_num_bytes(&group->field);
     if (!CBB_init(&token_cbb, 4 + PMBTOKEN_NONCE_SIZE + 3 * (2 + point_len)) ||
         !CBB_add_u32(&token_cbb, key_id) ||
         !CBB_add_bytes(&token_cbb, pretoken->t, PMBTOKEN_NONCE_SIZE) ||
-        // TODO(https://crbug.com/boringssl/331): When updating the key format,
-        // remove the redundant length prefixes.
-        !CBB_add_u16_length_prefixed(&token_cbb, &child) ||
-        !point_to_cbb(&child, group, &affines[0]) ||
-        !CBB_add_u16_length_prefixed(&token_cbb, &child) ||
-        !point_to_cbb(&child, group, &affines[1]) ||
-        !CBB_add_u16_length_prefixed(&token_cbb, &child) ||
-        !point_to_cbb(&child, group, &affines[2]) ||
+        !cbb_add_prefixed_point(&token_cbb, group, &affines[0],
+                                method->prefix_point) ||
+        !cbb_add_prefixed_point(&token_cbb, group, &affines[1],
+                                method->prefix_point) ||
+        !cbb_add_prefixed_point(&token_cbb, group, &affines[2],
+                                method->prefix_point) ||
         !CBB_flush(&token_cbb)) {
       CBB_cleanup(&token_cbb);
       goto err;
@@ -1021,9 +1043,9 @@
   CBS_init(&cbs, token, token_len);
   EC_AFFINE S, W, Ws;
   if (!CBS_copy_bytes(&cbs, out_nonce, PMBTOKEN_NONCE_SIZE) ||
-      !cbs_get_prefixed_point(&cbs, group, &S) ||
-      !cbs_get_prefixed_point(&cbs, group, &W) ||
-      !cbs_get_prefixed_point(&cbs, group, &Ws) ||
+      !cbs_get_prefixed_point(&cbs, group, &S, method->prefix_point) ||
+      !cbs_get_prefixed_point(&cbs, group, &W, method->prefix_point) ||
+      !cbs_get_prefixed_point(&cbs, group, &Ws, method->prefix_point) ||
       CBS_len(&cbs) != 0) {
     OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_INVALID_TOKEN);
     return 0;
@@ -1140,7 +1162,7 @@
   pmbtoken_exp1_ok =
       pmbtoken_init_method(&pmbtoken_exp1_method, NID_secp384r1, kH, sizeof(kH),
                            pmbtoken_exp1_hash_t, pmbtoken_exp1_hash_s,
-                           pmbtoken_exp1_hash_c);
+                           pmbtoken_exp1_hash_c, 1);
 }
 
 static int pmbtoken_exp1_init_method(void) {
@@ -1225,3 +1247,153 @@
          ec_point_to_bytes(pmbtoken_exp1_method.group, &h,
                            POINT_CONVERSION_UNCOMPRESSED, out, 97) == 97;
 }
+
+// PMBTokens experiment v2.
+
+static int pmbtoken_exp2_hash_t(const EC_GROUP *group, EC_RAW_POINT *out,
+                                const uint8_t t[PMBTOKEN_NONCE_SIZE]) {
+  const uint8_t kHashTLabel[] = "PMBTokens Experiment V2 HashT";
+  return ec_hash_to_curve_p384_xmd_sha512_sswu_draft07(
+      group, out, kHashTLabel, sizeof(kHashTLabel), t, PMBTOKEN_NONCE_SIZE);
+}
+
+static int pmbtoken_exp2_hash_s(const EC_GROUP *group, EC_RAW_POINT *out,
+                                const EC_AFFINE *t,
+                                const uint8_t s[PMBTOKEN_NONCE_SIZE]) {
+  const uint8_t kHashSLabel[] = "PMBTokens Experiment V2 HashS";
+  int ret = 0;
+  CBB cbb;
+  uint8_t *buf = NULL;
+  size_t len;
+  if (!CBB_init(&cbb, 0) ||
+      !point_to_cbb(&cbb, group, t) ||
+      !CBB_add_bytes(&cbb, s, PMBTOKEN_NONCE_SIZE) ||
+      !CBB_finish(&cbb, &buf, &len) ||
+      !ec_hash_to_curve_p384_xmd_sha512_sswu_draft07(
+          group, out, kHashSLabel, sizeof(kHashSLabel), buf, len)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    goto err;
+  }
+
+  ret = 1;
+
+err:
+  OPENSSL_free(buf);
+  CBB_cleanup(&cbb);
+  return ret;
+}
+
+static int pmbtoken_exp2_hash_c(const EC_GROUP *group, EC_SCALAR *out,
+                                uint8_t *buf, size_t len) {
+  const uint8_t kHashCLabel[] = "PMBTokens Experiment V2 HashC";
+  return ec_hash_to_scalar_p384_xmd_sha512_draft07(
+      group, out, kHashCLabel, sizeof(kHashCLabel), buf, len);
+}
+
+static int pmbtoken_exp2_ok = 0;
+static PMBTOKEN_METHOD pmbtoken_exp2_method;
+static CRYPTO_once_t pmbtoken_exp2_method_once = CRYPTO_ONCE_INIT;
+
+static void pmbtoken_exp2_init_method_impl(void) {
+  // This is the output of |ec_hash_to_scalar_p384_xmd_sha512_draft07| with DST
+  // "PMBTokens Experiment V2 HashH" and message "generator".
+  static const uint8_t kH[] = {
+      0x04, 0xbc, 0x27, 0x24, 0x99, 0xfa, 0xc9, 0xa4, 0x74, 0x6f, 0xf9,
+      0x07, 0x81, 0x55, 0xf8, 0x1f, 0x6f, 0xda, 0x09, 0xe7, 0x8c, 0x5d,
+      0x9e, 0x4e, 0x14, 0x7c, 0x53, 0x14, 0xbc, 0x7e, 0x29, 0x57, 0x92,
+      0x17, 0x94, 0x6e, 0xd2, 0xdf, 0xa5, 0x31, 0x1b, 0x4e, 0xb7, 0xfc,
+      0x93, 0xe3, 0x6e, 0x14, 0x1f, 0x4f, 0x14, 0xf3, 0xe5, 0x47, 0x61,
+      0x1c, 0x2c, 0x72, 0x25, 0xf0, 0x4a, 0x45, 0x23, 0x2d, 0x57, 0x93,
+      0x0e, 0xb2, 0x55, 0xb8, 0x57, 0x25, 0x4c, 0x1e, 0xdb, 0xfd, 0x58,
+      0x70, 0x17, 0x9a, 0xbb, 0x9e, 0x5e, 0x93, 0x9e, 0x92, 0xd3, 0xe8,
+      0x25, 0x62, 0xbf, 0x59, 0xb2, 0xd2, 0x3d, 0x71, 0xff
+  };
+
+  pmbtoken_exp2_ok =
+      pmbtoken_init_method(&pmbtoken_exp2_method, NID_secp384r1, kH, sizeof(kH),
+                           pmbtoken_exp2_hash_t, pmbtoken_exp2_hash_s,
+                           pmbtoken_exp2_hash_c, 0);
+}
+
+static int pmbtoken_exp2_init_method(void) {
+  CRYPTO_once(&pmbtoken_exp2_method_once, pmbtoken_exp2_init_method_impl);
+  if (!pmbtoken_exp2_ok) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_INTERNAL_ERROR);
+    return 0;
+  }
+  return 1;
+}
+
+int pmbtoken_exp2_generate_key(CBB *out_private, CBB *out_public) {
+  if (!pmbtoken_exp2_init_method()) {
+    return 0;
+  }
+
+  return pmbtoken_generate_key(&pmbtoken_exp2_method, out_private, out_public);
+}
+
+int pmbtoken_exp2_client_key_from_bytes(PMBTOKEN_CLIENT_KEY *key,
+                                        const uint8_t *in, size_t len) {
+  if (!pmbtoken_exp2_init_method()) {
+    return 0;
+  }
+  return pmbtoken_client_key_from_bytes(&pmbtoken_exp2_method, key, in, len);
+}
+
+int pmbtoken_exp2_issuer_key_from_bytes(PMBTOKEN_ISSUER_KEY *key,
+                                        const uint8_t *in, size_t len) {
+  if (!pmbtoken_exp2_init_method()) {
+    return 0;
+  }
+  return pmbtoken_issuer_key_from_bytes(&pmbtoken_exp2_method, key, in, len);
+}
+
+STACK_OF(PMBTOKEN_PRETOKEN) * pmbtoken_exp2_blind(CBB *cbb, size_t count) {
+  if (!pmbtoken_exp2_init_method()) {
+    return NULL;
+  }
+  return pmbtoken_blind(&pmbtoken_exp2_method, cbb, count);
+}
+
+int pmbtoken_exp2_sign(const PMBTOKEN_ISSUER_KEY *key, CBB *cbb, CBS *cbs,
+                       size_t num_requested, size_t num_to_issue,
+                       uint8_t private_metadata) {
+  if (!pmbtoken_exp2_init_method()) {
+    return 0;
+  }
+  return pmbtoken_sign(&pmbtoken_exp2_method, key, cbb, cbs, num_requested,
+                       num_to_issue, private_metadata);
+}
+
+STACK_OF(TRUST_TOKEN) *
+    pmbtoken_exp2_unblind(const PMBTOKEN_CLIENT_KEY *key,
+                          const STACK_OF(PMBTOKEN_PRETOKEN) * pretokens,
+                          CBS *cbs, size_t count, uint32_t key_id) {
+  if (!pmbtoken_exp2_init_method()) {
+    return NULL;
+  }
+  return pmbtoken_unblind(&pmbtoken_exp2_method, key, pretokens, cbs, count,
+                          key_id);
+}
+
+int pmbtoken_exp2_read(const PMBTOKEN_ISSUER_KEY *key,
+                       uint8_t out_nonce[PMBTOKEN_NONCE_SIZE],
+                       uint8_t *out_private_metadata, const uint8_t *token,
+                       size_t token_len) {
+  if (!pmbtoken_exp2_init_method()) {
+    return 0;
+  }
+  return pmbtoken_read(&pmbtoken_exp2_method, key, out_nonce,
+                       out_private_metadata, token, token_len);
+}
+
+int pmbtoken_exp2_get_h_for_testing(uint8_t out[97]) {
+  if (!pmbtoken_exp2_init_method()) {
+    return 0;
+  }
+  EC_AFFINE h;
+  return ec_jacobian_to_affine(pmbtoken_exp2_method.group, &h,
+                               &pmbtoken_exp2_method.h) &&
+         ec_point_to_bytes(pmbtoken_exp2_method.group, &h,
+                           POINT_CONVERSION_UNCOMPRESSED, out, 97) == 97;
+}
diff --git a/crypto/trust_token/trust_token.c b/crypto/trust_token/trust_token.c
index 87b8277..fea619e 100644
--- a/crypto/trust_token/trust_token.c
+++ b/crypto/trust_token/trust_token.c
@@ -36,6 +36,41 @@
       pmbtoken_exp1_sign,
       pmbtoken_exp1_unblind,
       pmbtoken_exp1_read,
+      1, /* has_private_metadata */
+      3, /* max_keys */
+      1, /* has_srr */
+  };
+  return &kMethod;
+}
+
+const TRUST_TOKEN_METHOD *TRUST_TOKEN_experiment_v2_pp(void) {
+  static const TRUST_TOKEN_METHOD kMethod = {
+      pmbtoken_exp2_generate_key,
+      pmbtoken_exp2_client_key_from_bytes,
+      pmbtoken_exp2_issuer_key_from_bytes,
+      pmbtoken_exp2_blind,
+      pmbtoken_exp2_sign,
+      pmbtoken_exp2_unblind,
+      pmbtoken_exp2_read,
+      0, /* has_private_metadata */
+      6, /* max_keys */
+      0, /* has_srr */
+  };
+  return &kMethod;
+}
+
+const TRUST_TOKEN_METHOD *TRUST_TOKEN_experiment_v2_pmb(void) {
+  static const TRUST_TOKEN_METHOD kMethod = {
+      pmbtoken_exp2_generate_key,
+      pmbtoken_exp2_client_key_from_bytes,
+      pmbtoken_exp2_issuer_key_from_bytes,
+      pmbtoken_exp2_blind,
+      pmbtoken_exp2_sign,
+      pmbtoken_exp2_unblind,
+      pmbtoken_exp2_read,
+      1, /* has_private_metadata */
+      3, /* max_keys */
+      0, /* has_srr */
   };
   return &kMethod;
 }
@@ -131,7 +166,8 @@
 
 int TRUST_TOKEN_CLIENT_add_key(TRUST_TOKEN_CLIENT *ctx, size_t *out_key_index,
                                const uint8_t *key, size_t key_len) {
-  if (ctx->num_keys == OPENSSL_ARRAY_SIZE(ctx->keys)) {
+  if (ctx->num_keys == OPENSSL_ARRAY_SIZE(ctx->keys) ||
+      ctx->num_keys >= ctx->method->max_keys) {
     OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_TOO_MANY_KEYS);
     return 0;
   }
@@ -153,6 +189,9 @@
 }
 
 int TRUST_TOKEN_CLIENT_set_srr_key(TRUST_TOKEN_CLIENT *ctx, EVP_PKEY *key) {
+  if (!ctx->method->has_srr) {
+    return 1;
+  }
   EVP_PKEY_free(ctx->srr_key);
   EVP_PKEY_up_ref(key);
   ctx->srr_key = key;
@@ -270,15 +309,10 @@
 }
 
 int TRUST_TOKEN_CLIENT_finish_redemption(TRUST_TOKEN_CLIENT *ctx,
-                                         uint8_t **out_srr, size_t *out_srr_len,
+                                         uint8_t **out_rr, size_t *out_rr_len,
                                          uint8_t **out_sig, size_t *out_sig_len,
                                          const uint8_t *response,
                                          size_t response_len) {
-  if (ctx->srr_key == NULL) {
-    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_NO_SRR_KEY_CONFIGURED);
-    return 0;
-  }
-
   CBS in, srr, sig;
   CBS_init(&in, response, response_len);
   if (!CBS_get_u16_length_prefixed(&in, &srr) ||
@@ -287,16 +321,24 @@
     return 0;
   }
 
-  EVP_MD_CTX md_ctx;
-  EVP_MD_CTX_init(&md_ctx);
-  int sig_ok = EVP_DigestVerifyInit(&md_ctx, NULL, NULL, NULL, ctx->srr_key) &&
-               EVP_DigestVerify(&md_ctx, CBS_data(&sig), CBS_len(&sig),
-                                CBS_data(&srr), CBS_len(&srr));
-  EVP_MD_CTX_cleanup(&md_ctx);
+  if (ctx->method->has_srr) {
+    if (ctx->srr_key == NULL) {
+      OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_NO_SRR_KEY_CONFIGURED);
+      return 0;
+    }
 
-  if (!sig_ok) {
-    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_SRR_SIGNATURE_ERROR);
-    return 0;
+    EVP_MD_CTX md_ctx;
+    EVP_MD_CTX_init(&md_ctx);
+    int sig_ok =
+        EVP_DigestVerifyInit(&md_ctx, NULL, NULL, NULL, ctx->srr_key) &&
+        EVP_DigestVerify(&md_ctx, CBS_data(&sig), CBS_len(&sig), CBS_data(&srr),
+                         CBS_len(&srr));
+    EVP_MD_CTX_cleanup(&md_ctx);
+
+    if (!sig_ok) {
+      OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_SRR_SIGNATURE_ERROR);
+      return 0;
+    }
   }
 
   uint8_t *srr_buf = NULL, *sig_buf = NULL;
@@ -309,8 +351,8 @@
     return 0;
   }
 
-  *out_srr = srr_buf;
-  *out_srr_len = srr_len;
+  *out_rr = srr_buf;
+  *out_rr_len = srr_len;
   *out_sig = sig_buf;
   *out_sig_len = sig_len;
   return 1;
@@ -346,7 +388,8 @@
 
 int TRUST_TOKEN_ISSUER_add_key(TRUST_TOKEN_ISSUER *ctx, const uint8_t *key,
                                size_t key_len) {
-  if (ctx->num_keys == OPENSSL_ARRAY_SIZE(ctx->keys)) {
+  if (ctx->num_keys == OPENSSL_ARRAY_SIZE(ctx->keys) ||
+      ctx->num_keys >= ctx->method->max_keys) {
     OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_TOO_MANY_KEYS);
     return 0;
   }
@@ -411,7 +454,8 @@
 
   const struct trust_token_issuer_key_st *key =
       trust_token_issuer_get_key(ctx, public_metadata);
-  if (key == NULL || private_metadata > 1) {
+  if (key == NULL || private_metadata > 1 ||
+      (!ctx->method->has_private_metadata && private_metadata != 0)) {
     OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_INVALID_METADATA);
     return 0;
   }
diff --git a/crypto/trust_token/trust_token_test.cc b/crypto/trust_token/trust_token_test.cc
index f6ff86c..b282500 100644
--- a/crypto/trust_token/trust_token_test.cc
+++ b/crypto/trust_token/trust_token_test.cc
@@ -56,6 +56,30 @@
   ASSERT_EQ(301u, pub_key_len);
 }
 
+TEST(TrustTokenTest, KeyGenExp2PP) {
+  uint8_t priv_key[TRUST_TOKEN_MAX_PRIVATE_KEY_SIZE];
+  uint8_t pub_key[TRUST_TOKEN_MAX_PUBLIC_KEY_SIZE];
+  size_t priv_key_len, pub_key_len;
+  ASSERT_TRUE(TRUST_TOKEN_generate_key(
+      TRUST_TOKEN_experiment_v2_pp(), priv_key, &priv_key_len,
+      TRUST_TOKEN_MAX_PRIVATE_KEY_SIZE, pub_key, &pub_key_len,
+      TRUST_TOKEN_MAX_PUBLIC_KEY_SIZE, 0x0001));
+  ASSERT_EQ(292u, priv_key_len);
+  ASSERT_EQ(295u, pub_key_len);
+}
+
+TEST(TrustTokenTest, KeyGenExp2PMB) {
+  uint8_t priv_key[TRUST_TOKEN_MAX_PRIVATE_KEY_SIZE];
+  uint8_t pub_key[TRUST_TOKEN_MAX_PUBLIC_KEY_SIZE];
+  size_t priv_key_len, pub_key_len;
+  ASSERT_TRUE(TRUST_TOKEN_generate_key(
+      TRUST_TOKEN_experiment_v2_pmb(), priv_key, &priv_key_len,
+      TRUST_TOKEN_MAX_PRIVATE_KEY_SIZE, pub_key, &pub_key_len,
+      TRUST_TOKEN_MAX_PUBLIC_KEY_SIZE, 0x0001));
+  ASSERT_EQ(292u, priv_key_len);
+  ASSERT_EQ(295u, pub_key_len);
+}
+
 // Test that H in |TRUST_TOKEN_experiment_v1| was computed correctly.
 TEST(TrustTokenTest, HExp1) {
   const EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp384r1);
@@ -78,8 +102,34 @@
   EXPECT_EQ(Bytes(h), Bytes(expected_bytes, expected_len));
 }
 
+// Test that H in |TRUST_TOKEN_experiment_v2_pmb| was computed correctly.
+TEST(TrustTokenTest, HExp2) {
+  const EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp384r1);
+  ASSERT_TRUE(group);
+
+  const uint8_t kHGen[] = "generator";
+  const uint8_t kHLabel[] = "PMBTokens Experiment V2 HashH";
+
+  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->raw, kHLabel, sizeof(kHLabel), kHGen, sizeof(kHGen)));
+  uint8_t expected_bytes[1 + 2 * EC_MAX_BYTES];
+  size_t expected_len =
+      EC_POINT_point2oct(group, expected_h.get(), POINT_CONVERSION_UNCOMPRESSED,
+                         expected_bytes, sizeof(expected_bytes), nullptr);
+
+  uint8_t h[97];
+  ASSERT_TRUE(pmbtoken_exp2_get_h_for_testing(h));
+  EXPECT_EQ(Bytes(h), Bytes(expected_bytes, expected_len));
+}
+
 static std::vector<const TRUST_TOKEN_METHOD *> AllMethods() {
-  return {TRUST_TOKEN_experiment_v1()};
+  return {
+    TRUST_TOKEN_experiment_v1(),
+    TRUST_TOKEN_experiment_v2_pp(),
+    TRUST_TOKEN_experiment_v2_pmb()
+  };
 }
 
 class TrustTokenProtocolTestBase : public ::testing::Test {
@@ -102,7 +152,7 @@
     issuer.reset(TRUST_TOKEN_ISSUER_new(method(), issuer_max_batchsize));
     ASSERT_TRUE(issuer);
 
-    for (size_t i = 0; i < 3; i++) {
+    for (size_t i = 0; i < method()->max_keys; i++) {
       uint8_t priv_key[TRUST_TOKEN_MAX_PRIVATE_KEY_SIZE];
       uint8_t pub_key[TRUST_TOKEN_MAX_PUBLIC_KEY_SIZE];
       size_t priv_key_len, pub_key_len, key_index;
@@ -163,7 +213,7 @@
   bssl::UniquePtr<uint8_t> free_issue_msg(issue_msg);
   ASSERT_TRUE(TRUST_TOKEN_ISSUER_issue(
       issuer.get(), &issue_resp, &resp_len, &tokens_issued, issue_msg, msg_len,
-      /*public_metadata=*/KeyID(0), /*private_metadata=*/1,
+      /*public_metadata=*/KeyID(0), /*private_metadata=*/0,
       /*max_issuance=*/10));
   bssl::UniquePtr<uint8_t> free_msg(issue_resp);
   bssl::UniquePtr<STACK_OF(TRUST_TOKEN)> tokens(
@@ -428,9 +478,14 @@
                                                 &msg_len, 10));
   bssl::UniquePtr<uint8_t> free_issue_msg(issue_msg);
   size_t tokens_issued;
-  ASSERT_TRUE(TRUST_TOKEN_ISSUER_issue(
+  bool result = TRUST_TOKEN_ISSUER_issue(
       issuer.get(), &issue_resp, &resp_len, &tokens_issued, issue_msg, msg_len,
-      public_metadata(), private_metadata(), /*max_issuance=*/1));
+      public_metadata(), private_metadata(), /*max_issuance=*/1);
+  if (!method()->has_private_metadata && private_metadata()) {
+    ASSERT_FALSE(result);
+    return;
+  }
+  ASSERT_TRUE(result);
   bssl::UniquePtr<uint8_t> free_msg(issue_resp);
   size_t key_index;
   bssl::UniquePtr<STACK_OF(TRUST_TOKEN)> tokens(
@@ -510,6 +565,10 @@
 }
 
 TEST_P(TrustTokenMetadataTest, TooManyRequests) {
+  if (!method()->has_private_metadata && private_metadata()) {
+    return;
+  }
+
   issuer_max_batchsize = 1;
   ASSERT_NO_FATAL_FAILURE(SetupContexts());
 
@@ -534,6 +593,10 @@
 
 
 TEST_P(TrustTokenMetadataTest, TruncatedProof) {
+  if (!method()->has_private_metadata && private_metadata()) {
+    return;
+  }
+
   ASSERT_NO_FATAL_FAILURE(SetupContexts());
 
   uint8_t *issue_msg = NULL, *issue_resp = NULL;
@@ -558,19 +621,16 @@
   ASSERT_TRUE(CBS_get_u32(&real_response, &public_metadata));
   ASSERT_TRUE(CBB_add_u32(bad_response.get(), public_metadata));
 
+  const EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp384r1);
+  size_t token_length =
+      PMBTOKEN_NONCE_SIZE + 2 * (1 + 2 * BN_num_bytes(&group->field));
+  if (method() == TRUST_TOKEN_experiment_v1()) {
+    token_length += 4;
+  }
   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(CBB_add_bytes(bad_response.get(), CBS_data(&real_response),
+                              token_length));
+    ASSERT_TRUE(CBS_skip(&real_response, token_length));
   }
 
   CBS tmp;
@@ -593,6 +653,10 @@
 }
 
 TEST_P(TrustTokenMetadataTest, ExcessDataProof) {
+  if (!method()->has_private_metadata && private_metadata()) {
+    return;
+  }
+
   ASSERT_NO_FATAL_FAILURE(SetupContexts());
 
   uint8_t *issue_msg = NULL, *issue_resp = NULL;
@@ -617,19 +681,16 @@
   ASSERT_TRUE(CBS_get_u32(&real_response, &public_metadata));
   ASSERT_TRUE(CBB_add_u32(bad_response.get(), public_metadata));
 
+  const EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp384r1);
+  size_t token_length =
+      PMBTOKEN_NONCE_SIZE + 2 * (1 + 2 * BN_num_bytes(&group->field));
+  if (method() == TRUST_TOKEN_experiment_v1()) {
+    token_length += 4;
+  }
   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(CBB_add_bytes(bad_response.get(), CBS_data(&real_response),
+                              token_length));
+    ASSERT_TRUE(CBS_skip(&real_response, token_length));
   }
 
   CBS tmp;
@@ -673,6 +734,10 @@
 };
 
 TEST_P(TrustTokenBadKeyTest, BadKey) {
+  if (!method()->has_private_metadata && private_metadata()) {
+    return;
+  }
+
   ASSERT_NO_FATAL_FAILURE(SetupContexts());
 
   uint8_t *issue_msg = NULL, *issue_resp = NULL;
diff --git a/include/openssl/trust_token.h b/include/openssl/trust_token.h
index 9ecf75f..b6c00b2 100644
--- a/include/openssl/trust_token.h
+++ b/include/openssl/trust_token.h
@@ -40,6 +40,20 @@
 // PMBTokens and P-384.
 OPENSSL_EXPORT const TRUST_TOKEN_METHOD *TRUST_TOKEN_experiment_v1(void);
 
+// TRUST_TOKEN_experiment_v2_pp is an experimental Trust Tokens protocol using
+// PMBTokens (with no private metadata) and P-384 with up to 6 keys, without RR
+// verification.
+//
+// This version is incomplete and should not be used.
+// TODO(svaldez): Update to use the PrivacyPass primitive
+OPENSSL_EXPORT const TRUST_TOKEN_METHOD *TRUST_TOKEN_experiment_v2_pp(void);
+
+// TRUST_TOKEN_experiment_v2_pmb is an experimental Trust Tokens protocol using
+// PMBTokens and P-384 with up to 3 keys, without RR verification.
+//
+// This version is incomplete and should not be used.
+OPENSSL_EXPORT const TRUST_TOKEN_METHOD *TRUST_TOKEN_experiment_v2_pmb(void);
+
 // trust_token_st represents a single-use token for the Trust Token protocol.
 // For the client, this is the token and its corresponding signature. For the
 // issuer, this is the token itself.
@@ -146,13 +160,19 @@
     const TRUST_TOKEN *token, const uint8_t *data, size_t data_len,
     uint64_t time);
 
-// TRUST_TOKEN_CLIENT_finish_redemption consumes |response| from the issuer and
-// verifies the SRR. If valid, it returns one and sets |*out_srr| and
-// |*out_srr_len| (respectively, |*out_sig| and |*out_sig_len|) to a
-// newly-allocated buffer containing the SRR (respectively, the SRR signature).
-// Otherwise, it returns zero.
+// TRUST_TOKEN_CLIENT_finish_redemption consumes |response| from the issuer. In
+// |TRUST_TOKEN_experiment_v1|, it then verifies the SRR and if valid  sets
+// |*out_rr| and |*out_rr_len| (respectively, |*out_sig| and |*out_sig_len|)
+// to a newly-allocated buffer containing the SRR (respectively, the SRR
+// signature). In other versions, it sets |*out_rr| and |*out_rr_len|
+// (respectively, |*out_sig| and |*out_sig_len|) to a newly-allocated buffer
+// containing the SRR (respectively, the SRR signature). It returns one on
+// success or zero on failure.
+//
+// TODO(svaldez): Return the entire response in |*out_rr| and omit |*out_sig| in
+// non-|TRUST_TOKEN_experiment_v1| versions.
 OPENSSL_EXPORT int TRUST_TOKEN_CLIENT_finish_redemption(
-    TRUST_TOKEN_CLIENT *ctx, uint8_t **out_srr, size_t *out_srr_len,
+    TRUST_TOKEN_CLIENT *ctx, uint8_t **out_rr, size_t *out_rr_len,
     uint8_t **out_sig, size_t *out_sig_len, const uint8_t *response,
     size_t response_len);