Update TrustTokenV2 to use VOPRFs and assemble RR.

Change-Id: I2f1f6b187bf42ebfdb61def73726d95740a9d55c
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/42965
Commit-Queue: Steven Valdez <svaldez@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt
index a872626..2771768 100644
--- a/crypto/CMakeLists.txt
+++ b/crypto/CMakeLists.txt
@@ -340,6 +340,7 @@
   thread_win.c
   trust_token/pmbtoken.c
   trust_token/trust_token.c
+  trust_token/voprf.c
   x509/a_digest.c
   x509/a_sign.c
   x509/a_strex.c
diff --git a/crypto/trust_token/internal.h b/crypto/trust_token/internal.h
index c935888..0aa1936 100644
--- a/crypto/trust_token/internal.h
+++ b/crypto/trust_token/internal.h
@@ -30,16 +30,20 @@
 #endif
 
 
-// PMBTokens.
-//
-// PMBTokens is described in https://eprint.iacr.org/2020/072/20200324:214215
-// and provides anonymous tokens with private metadata. We implement the
-// construction with validity verification, described in appendix H,
-// construction 6.
+// For the following cryptographic schemes, we use P-384 instead of our usual
+// choice of P-256. See Appendix I of
+// https://eprint.iacr.org/2020/072/20200324:214215 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.
 
-// PMBTOKEN_NONCE_SIZE is the size of nonces used as part of the PMBToken
+
+// TRUST_TOKEN_NONCE_SIZE is the size of nonces used as part of the Trust_Token
 // protocol.
-#define PMBTOKEN_NONCE_SIZE 64
+#define TRUST_TOKEN_NONCE_SIZE 64
 
 typedef struct {
   // TODO(https://crbug.com/boringssl/334): These should store |EC_PRECOMP| so
@@ -47,7 +51,7 @@
   EC_AFFINE pub0;
   EC_AFFINE pub1;
   EC_AFFINE pubs;
-} PMBTOKEN_CLIENT_KEY;
+} TRUST_TOKEN_CLIENT_KEY;
 
 typedef struct {
   EC_SCALAR x0;
@@ -62,47 +66,47 @@
   EC_PRECOMP pub1_precomp;
   EC_AFFINE pubs;
   EC_PRECOMP pubs_precomp;
-} PMBTOKEN_ISSUER_KEY;
+} TRUST_TOKEN_ISSUER_KEY;
 
-// PMBTOKEN_PRETOKEN represents the intermediate state a client keeps during a
-// PMBToken issuance operation.
+// TRUST_TOKEN_PRETOKEN represents the intermediate state a client keeps during
+// a Trust_Token issuance operation.
 typedef struct pmb_pretoken_st {
-  uint8_t t[PMBTOKEN_NONCE_SIZE];
+  uint8_t t[TRUST_TOKEN_NONCE_SIZE];
   EC_SCALAR r;
   EC_AFFINE Tp;
-} PMBTOKEN_PRETOKEN;
+} TRUST_TOKEN_PRETOKEN;
 
-// PMBTOKEN_PRETOKEN_free releases the memory associated with |token|.
-OPENSSL_EXPORT void PMBTOKEN_PRETOKEN_free(PMBTOKEN_PRETOKEN *token);
+// TRUST_TOKEN_PRETOKEN_free releases the memory associated with |token|.
+OPENSSL_EXPORT void TRUST_TOKEN_PRETOKEN_free(TRUST_TOKEN_PRETOKEN *token);
 
-DEFINE_STACK_OF(PMBTOKEN_PRETOKEN)
+DEFINE_STACK_OF(TRUST_TOKEN_PRETOKEN)
+
+
+// PMBTokens.
+//
+// PMBTokens is described in https://eprint.iacr.org/2020/072/20200324:214215
+// and provides anonymous tokens with private metadata. We implement the
+// construction with validity verification, described in appendix H,
+// construction 6.
 
 // The following functions implement the corresponding |TRUST_TOKENS_METHOD|
 // functions for |TRUST_TOKENS_experiment_v1|'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_exp1_generate_key(CBB *out_private, CBB *out_public);
-int pmbtoken_exp1_client_key_from_bytes(PMBTOKEN_CLIENT_KEY *key,
+int pmbtoken_exp1_client_key_from_bytes(TRUST_TOKEN_CLIENT_KEY *key,
                                         const uint8_t *in, size_t len);
-int pmbtoken_exp1_issuer_key_from_bytes(PMBTOKEN_ISSUER_KEY *key,
+int pmbtoken_exp1_issuer_key_from_bytes(TRUST_TOKEN_ISSUER_KEY *key,
                                         const uint8_t *in, size_t len);
-STACK_OF(PMBTOKEN_PRETOKEN) * pmbtoken_exp1_blind(CBB *cbb, size_t count);
-int pmbtoken_exp1_sign(const PMBTOKEN_ISSUER_KEY *key, CBB *cbb, CBS *cbs,
+STACK_OF(TRUST_TOKEN_PRETOKEN) * pmbtoken_exp1_blind(CBB *cbb, size_t count);
+int pmbtoken_exp1_sign(const TRUST_TOKEN_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_exp1_unblind(const PMBTOKEN_CLIENT_KEY *key,
-                          const STACK_OF(PMBTOKEN_PRETOKEN) * pretokens,
+    pmbtoken_exp1_unblind(const TRUST_TOKEN_CLIENT_KEY *key,
+                          const STACK_OF(TRUST_TOKEN_PRETOKEN) * pretokens,
                           CBS *cbs, size_t count, uint32_t key_id);
-int pmbtoken_exp1_read(const PMBTOKEN_ISSUER_KEY *key,
-                       uint8_t out_nonce[PMBTOKEN_NONCE_SIZE],
+int pmbtoken_exp1_read(const TRUST_TOKEN_ISSUER_KEY *key,
+                       uint8_t out_nonce[TRUST_TOKEN_NONCE_SIZE],
                        uint8_t *out_private_metadata, const uint8_t *token,
                        size_t token_len);
 
@@ -113,29 +117,21 @@
 // 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,
+int pmbtoken_exp2_client_key_from_bytes(TRUST_TOKEN_CLIENT_KEY *key,
                                         const uint8_t *in, size_t len);
-int pmbtoken_exp2_issuer_key_from_bytes(PMBTOKEN_ISSUER_KEY *key,
+int pmbtoken_exp2_issuer_key_from_bytes(TRUST_TOKEN_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,
+STACK_OF(TRUST_TOKEN_PRETOKEN) * pmbtoken_exp2_blind(CBB *cbb, size_t count);
+int pmbtoken_exp2_sign(const TRUST_TOKEN_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,
+    pmbtoken_exp2_unblind(const TRUST_TOKEN_CLIENT_KEY *key,
+                          const STACK_OF(TRUST_TOKEN_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],
+int pmbtoken_exp2_read(const TRUST_TOKEN_ISSUER_KEY *key,
+                       uint8_t out_nonce[TRUST_TOKEN_NONCE_SIZE],
                        uint8_t *out_private_metadata, const uint8_t *token,
                        size_t token_len);
 
@@ -144,6 +140,37 @@
 OPENSSL_EXPORT int pmbtoken_exp2_get_h_for_testing(uint8_t out[97]);
 
 
+// VOPRF.
+//
+// VOPRFs are described in https://tools.ietf.org/html/draft-irtf-cfrg-voprf-04
+// and provide anonymous tokens. This implementation uses TrustToken DSTs and
+// the DLEQ batching primitive from
+// https://eprint.iacr.org/2020/072/20200324:214215.
+// VOPRF only uses the |pub|' field of the TRUST_TOKEN_CLIENT_KEY and
+// |xs|/|pubs| fields of the TRUST_TOKEN_ISSUER_KEY.
+
+// The following functions implement the corresponding |TRUST_TOKENS_METHOD|
+// functions for |TRUST_TOKENS_experiment_v2|'s VOPRF construction which uses
+// P-384.
+int voprf_exp2_generate_key(CBB *out_private, CBB *out_public);
+int voprf_exp2_client_key_from_bytes(TRUST_TOKEN_CLIENT_KEY *key,
+                                     const uint8_t *in, size_t len);
+int voprf_exp2_issuer_key_from_bytes(TRUST_TOKEN_ISSUER_KEY *key,
+                                     const uint8_t *in, size_t len);
+STACK_OF(TRUST_TOKEN_PRETOKEN) * voprf_exp2_blind(CBB *cbb, size_t count);
+int voprf_exp2_sign(const TRUST_TOKEN_ISSUER_KEY *key, CBB *cbb, CBS *cbs,
+                    size_t num_requested, size_t num_to_issue,
+                    uint8_t private_metadata);
+STACK_OF(TRUST_TOKEN) *
+    voprf_exp2_unblind(const TRUST_TOKEN_CLIENT_KEY *key,
+                       const STACK_OF(TRUST_TOKEN_PRETOKEN) * pretokens,
+                       CBS *cbs, size_t count, uint32_t key_id);
+int voprf_exp2_read(const TRUST_TOKEN_ISSUER_KEY *key,
+                    uint8_t out_nonce[TRUST_TOKEN_NONCE_SIZE],
+                    uint8_t *out_private_metadata, const uint8_t *token,
+                    size_t token_len);
+
+
 // Trust Tokens internals.
 
 struct trust_token_method_st {
@@ -155,23 +182,23 @@
   // client_key_from_bytes decodes a client key from |in| and sets |key|
   // to the resulting key. It returns one on success and zero
   // on failure.
-  int (*client_key_from_bytes)(PMBTOKEN_CLIENT_KEY *key, const uint8_t *in,
+  int (*client_key_from_bytes)(TRUST_TOKEN_CLIENT_KEY *key, const uint8_t *in,
                                size_t len);
 
   // issuer_key_from_bytes decodes a issuer key from |in| and sets |key|
   // to the resulting key. It returns one on success and zero
   // on failure.
-  int (*issuer_key_from_bytes)(PMBTOKEN_ISSUER_KEY *key, const uint8_t *in,
+  int (*issuer_key_from_bytes)(TRUST_TOKEN_ISSUER_KEY *key, const uint8_t *in,
                                size_t len);
 
   // blind generates a new issuance request for |count| tokens. On
-  // success, it returns a newly-allocated |STACK_OF(PMBTOKEN_PRETOKEN)| and
+  // success, it returns a newly-allocated |STACK_OF(TRUST_TOKEN_PRETOKEN)| and
   // writes a request to the issuer to |cbb|. On failure, it returns NULL. The
-  // |STACK_OF(PMBTOKEN_PRETOKEN)|s should be passed to |pmbtoken_unblind| when
+  // |STACK_OF(TRUST_TOKEN_PRETOKEN)|s should be passed to |pmbtoken_unblind| when
   // the server responds.
   //
   // This function implements the AT.Usr0 operation.
-  STACK_OF(PMBTOKEN_PRETOKEN) *(*blind)(CBB *cbb, size_t count);
+  STACK_OF(TRUST_TOKEN_PRETOKEN) * (*blind)(CBB *cbb, size_t count);
 
   // sign parses a request for |num_requested| tokens from |cbs| and
   // issues |num_to_issue| tokens with |key| and a private metadata value of
@@ -179,7 +206,7 @@
   // success and zero on failure.
   //
   // This function implements the AT.Sig operation.
-  int (*sign)(const PMBTOKEN_ISSUER_KEY *key, CBB *cbb, CBS *cbs,
+  int (*sign)(const TRUST_TOKEN_ISSUER_KEY *key, CBB *cbb, CBS *cbs,
               size_t num_requested, size_t num_to_issue,
               uint8_t private_metadata);
 
@@ -192,8 +219,8 @@
   //
   // This function implements the AT.Usr1 operation.
   STACK_OF(TRUST_TOKEN) *
-      (*unblind)(const PMBTOKEN_CLIENT_KEY *key,
-                 const STACK_OF(PMBTOKEN_PRETOKEN) * pretokens, CBS *cbs,
+      (*unblind)(const TRUST_TOKEN_CLIENT_KEY *key,
+                 const STACK_OF(TRUST_TOKEN_PRETOKEN) * pretokens, CBS *cbs,
                  size_t count, uint32_t key_id);
 
   // read parses a PMBToken from |token| and verifies it using |key|. On
@@ -201,8 +228,8 @@
   // |out_nonce| and |*out_private_metadata|. Otherwise, it returns zero. Note
   // that, unlike the output of |unblind|, |token| does not have a
   // four-byte key ID prepended.
-  int (*read)(const PMBTOKEN_ISSUER_KEY *key,
-              uint8_t out_nonce[PMBTOKEN_NONCE_SIZE],
+  int (*read)(const TRUST_TOKEN_ISSUER_KEY *key,
+              uint8_t out_nonce[TRUST_TOKEN_NONCE_SIZE],
               uint8_t *out_private_metadata, const uint8_t *token,
               size_t token_len);
 
@@ -219,14 +246,14 @@
 // Structure representing a single Trust Token public key with the specified ID.
 struct trust_token_client_key_st {
   uint32_t id;
-  PMBTOKEN_CLIENT_KEY key;
+  TRUST_TOKEN_CLIENT_KEY key;
 };
 
 // Structure representing a single Trust Token private key with the specified
 // ID.
 struct trust_token_issuer_key_st {
   uint32_t id;
-  PMBTOKEN_ISSUER_KEY key;
+  TRUST_TOKEN_ISSUER_KEY key;
 };
 
 struct trust_token_client_st {
@@ -243,7 +270,7 @@
   size_t num_keys;
 
   // pretokens is the intermediate state during an active issuance.
-  STACK_OF(PMBTOKEN_PRETOKEN)* pretokens;
+  STACK_OF(TRUST_TOKEN_PRETOKEN)* pretokens;
 
   // srr_key is the public key used to verify the signature of the SRR.
   EVP_PKEY *srr_key;
@@ -281,7 +308,7 @@
 
 BSSL_NAMESPACE_BEGIN
 
-BORINGSSL_MAKE_DELETER(PMBTOKEN_PRETOKEN, PMBTOKEN_PRETOKEN_free)
+BORINGSSL_MAKE_DELETER(TRUST_TOKEN_PRETOKEN, TRUST_TOKEN_PRETOKEN_free)
 
 BSSL_NAMESPACE_END
 
diff --git a/crypto/trust_token/pmbtoken.c b/crypto/trust_token/pmbtoken.c
index f9132e6..a6549b9 100644
--- a/crypto/trust_token/pmbtoken.c
+++ b/crypto/trust_token/pmbtoken.c
@@ -31,10 +31,10 @@
 
 
 typedef int (*hash_t_func_t)(const EC_GROUP *group, EC_RAW_POINT *out,
-                             const uint8_t t[PMBTOKEN_NONCE_SIZE]);
+                             const uint8_t t[TRUST_TOKEN_NONCE_SIZE]);
 typedef int (*hash_s_func_t)(const EC_GROUP *group, EC_RAW_POINT *out,
                              const EC_AFFINE *t,
-                             const uint8_t s[PMBTOKEN_NONCE_SIZE]);
+                             const uint8_t s[TRUST_TOKEN_NONCE_SIZE]);
 typedef int (*hash_c_func_t)(const EC_GROUP *group, EC_SCALAR *out,
                              uint8_t *buf, size_t len);
 
@@ -165,10 +165,6 @@
                                           scalars, 3);
 }
 
-void PMBTOKEN_PRETOKEN_free(PMBTOKEN_PRETOKEN *pretoken) {
-  OPENSSL_free(pretoken);
-}
-
 static int pmbtoken_generate_key(const PMBTOKEN_METHOD *method,
                                  CBB *out_private, CBB *out_public) {
   const EC_GROUP *group = method->group;
@@ -211,7 +207,7 @@
 }
 
 static int pmbtoken_client_key_from_bytes(const PMBTOKEN_METHOD *method,
-                                          PMBTOKEN_CLIENT_KEY *key,
+                                          TRUST_TOKEN_CLIENT_KEY *key,
                                           const uint8_t *in, size_t len) {
   CBS cbs;
   CBS_init(&cbs, in, len);
@@ -230,7 +226,7 @@
 }
 
 static int pmbtoken_issuer_key_from_bytes(const PMBTOKEN_METHOD *method,
-                                          PMBTOKEN_ISSUER_KEY *key,
+                                          TRUST_TOKEN_ISSUER_KEY *key,
                                           const uint8_t *in, size_t len) {
   const EC_GROUP *group = method->group;
   CBS cbs, tmp;
@@ -269,10 +265,10 @@
   return 1;
 }
 
-static STACK_OF(PMBTOKEN_PRETOKEN) *
+static STACK_OF(TRUST_TOKEN_PRETOKEN) *
     pmbtoken_blind(const PMBTOKEN_METHOD *method, CBB *cbb, size_t count) {
   const EC_GROUP *group = method->group;
-  STACK_OF(PMBTOKEN_PRETOKEN) *pretokens = sk_PMBTOKEN_PRETOKEN_new_null();
+  STACK_OF(TRUST_TOKEN_PRETOKEN) *pretokens = sk_TRUST_TOKEN_PRETOKEN_new_null();
   if (pretokens == NULL) {
     OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
     goto err;
@@ -280,11 +276,11 @@
 
   for (size_t i = 0; i < count; i++) {
     // Insert |pretoken| into |pretokens| early to simplify error-handling.
-    PMBTOKEN_PRETOKEN *pretoken = OPENSSL_malloc(sizeof(PMBTOKEN_PRETOKEN));
+    TRUST_TOKEN_PRETOKEN *pretoken = OPENSSL_malloc(sizeof(TRUST_TOKEN_PRETOKEN));
     if (pretoken == NULL ||
-        !sk_PMBTOKEN_PRETOKEN_push(pretokens, pretoken)) {
+        !sk_TRUST_TOKEN_PRETOKEN_push(pretokens, pretoken)) {
       OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
-      PMBTOKEN_PRETOKEN_free(pretoken);
+      TRUST_TOKEN_PRETOKEN_free(pretoken);
       goto err;
     }
 
@@ -319,7 +315,7 @@
   return pretokens;
 
 err:
-  sk_PMBTOKEN_PRETOKEN_pop_free(pretokens, PMBTOKEN_PRETOKEN_free);
+  sk_TRUST_TOKEN_PRETOKEN_pop_free(pretokens, TRUST_TOKEN_PRETOKEN_free);
   return NULL;
 }
 
@@ -455,9 +451,10 @@
 // DLEQOR2 with only one value (n=1).
 
 static int dleq_generate(const PMBTOKEN_METHOD *method, CBB *cbb,
-                         const PMBTOKEN_ISSUER_KEY *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) {
+                         const TRUST_TOKEN_ISSUER_KEY *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) {
   const EC_GROUP *group = method->group;
 
   // We generate a DLEQ proof for the validity token and a DLEQOR2 proof for the
@@ -616,7 +613,7 @@
 }
 
 static int dleq_verify(const PMBTOKEN_METHOD *method, CBS *cbs,
-                       const PMBTOKEN_CLIENT_KEY *pub, const EC_RAW_POINT *T,
+                       const TRUST_TOKEN_CLIENT_KEY *pub, const EC_RAW_POINT *T,
                        const EC_RAW_POINT *S, const EC_RAW_POINT *W,
                        const EC_RAW_POINT *Ws) {
   const EC_GROUP *group = method->group;
@@ -735,7 +732,7 @@
 }
 
 static int pmbtoken_sign(const PMBTOKEN_METHOD *method,
-                         const PMBTOKEN_ISSUER_KEY *key, CBB *cbb, CBS *cbs,
+                         const TRUST_TOKEN_ISSUER_KEY *key, CBB *cbb, CBS *cbs,
                          size_t num_requested, size_t num_to_issue,
                          uint8_t private_metadata) {
   const EC_GROUP *group = method->group;
@@ -785,8 +782,8 @@
     ec_scalar_select(group, &xb, mask, &key->x1, &key->x0);
     ec_scalar_select(group, &yb, mask, &key->y1, &key->y0);
 
-    uint8_t s[PMBTOKEN_NONCE_SIZE];
-    RAND_bytes(s, PMBTOKEN_NONCE_SIZE);
+    uint8_t s[TRUST_TOKEN_NONCE_SIZE];
+    RAND_bytes(s, TRUST_TOKEN_NONCE_SIZE);
     // The |jacobians| and |affines| contain Sp, Wp, and Wsp.
     EC_RAW_POINT jacobians[3];
     EC_AFFINE affines[3];
@@ -796,9 +793,11 @@
         !ec_point_mul_scalar_batch(group, &jacobians[2], &Tp, &key->xs,
                                    &jacobians[0], &key->ys, NULL, NULL) ||
         !ec_jacobian_to_affine_batch(group, affines, jacobians, 3) ||
-        !CBB_add_bytes(cbb, s, PMBTOKEN_NONCE_SIZE) ||
-        !cbb_add_prefixed_point(cbb, group, &affines[1], method->prefix_point) ||
-        !cbb_add_prefixed_point(cbb, group, &affines[2], method->prefix_point)) {
+        !CBB_add_bytes(cbb, s, TRUST_TOKEN_NONCE_SIZE) ||
+        !cbb_add_prefixed_point(cbb, group, &affines[1],
+                                method->prefix_point) ||
+        !cbb_add_prefixed_point(cbb, group, &affines[2],
+                                method->prefix_point)) {
       goto err;
     }
 
@@ -877,11 +876,11 @@
 
 static STACK_OF(TRUST_TOKEN) *
     pmbtoken_unblind(const PMBTOKEN_METHOD *method,
-                     const PMBTOKEN_CLIENT_KEY *key,
-                     const STACK_OF(PMBTOKEN_PRETOKEN) * pretokens, CBS *cbs,
+                     const TRUST_TOKEN_CLIENT_KEY *key,
+                     const STACK_OF(TRUST_TOKEN_PRETOKEN) * pretokens, CBS *cbs,
                      size_t count, uint32_t key_id) {
   const EC_GROUP *group = method->group;
-  if (count > sk_PMBTOKEN_PRETOKEN_num(pretokens)) {
+  if (count > sk_TRUST_TOKEN_PRETOKEN_num(pretokens)) {
     OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
     return NULL;
   }
@@ -919,12 +918,12 @@
   }
 
   for (size_t i = 0; i < count; i++) {
-    const PMBTOKEN_PRETOKEN *pretoken =
-        sk_PMBTOKEN_PRETOKEN_value(pretokens, i);
+    const TRUST_TOKEN_PRETOKEN *pretoken =
+        sk_TRUST_TOKEN_PRETOKEN_value(pretokens, i);
 
-    uint8_t s[PMBTOKEN_NONCE_SIZE];
+    uint8_t s[TRUST_TOKEN_NONCE_SIZE];
     EC_AFFINE Wp_affine, Wsp_affine;
-    if (!CBS_copy_bytes(cbs, s, PMBTOKEN_NONCE_SIZE) ||
+    if (!CBS_copy_bytes(cbs, s, TRUST_TOKEN_NONCE_SIZE) ||
         !cbs_get_prefixed_point(cbs, group, &Wp_affine, method->prefix_point) ||
         !cbs_get_prefixed_point(cbs, group, &Wsp_affine,
                                 method->prefix_point)) {
@@ -963,9 +962,10 @@
     // above.
     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)) ||
+    if (!CBB_init(&token_cbb,
+                  4 + TRUST_TOKEN_NONCE_SIZE + 3 * (2 + point_len)) ||
         !CBB_add_u32(&token_cbb, key_id) ||
-        !CBB_add_bytes(&token_cbb, pretoken->t, PMBTOKEN_NONCE_SIZE) ||
+        !CBB_add_bytes(&token_cbb, pretoken->t, TRUST_TOKEN_NONCE_SIZE) ||
         !cbb_add_prefixed_point(&token_cbb, group, &affines[0],
                                 method->prefix_point) ||
         !cbb_add_prefixed_point(&token_cbb, group, &affines[1],
@@ -1034,15 +1034,15 @@
 }
 
 static int pmbtoken_read(const PMBTOKEN_METHOD *method,
-                         const PMBTOKEN_ISSUER_KEY *key,
-                         uint8_t out_nonce[PMBTOKEN_NONCE_SIZE],
+                         const TRUST_TOKEN_ISSUER_KEY *key,
+                         uint8_t out_nonce[TRUST_TOKEN_NONCE_SIZE],
                          uint8_t *out_private_metadata, const uint8_t *token,
                          size_t token_len) {
   const EC_GROUP *group = method->group;
   CBS cbs;
   CBS_init(&cbs, token, token_len);
   EC_AFFINE S, W, Ws;
-  if (!CBS_copy_bytes(&cbs, out_nonce, PMBTOKEN_NONCE_SIZE) ||
+  if (!CBS_copy_bytes(&cbs, out_nonce, TRUST_TOKEN_NONCE_SIZE) ||
       !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) ||
@@ -1101,15 +1101,15 @@
 // PMBTokens experiment v1.
 
 static int pmbtoken_exp1_hash_t(const EC_GROUP *group, EC_RAW_POINT *out,
-                                const uint8_t t[PMBTOKEN_NONCE_SIZE]) {
+                                const uint8_t t[TRUST_TOKEN_NONCE_SIZE]) {
   const uint8_t kHashTLabel[] = "PMBTokens Experiment V1 HashT";
   return ec_hash_to_curve_p384_xmd_sha512_sswu_draft07(
-      group, out, kHashTLabel, sizeof(kHashTLabel), t, PMBTOKEN_NONCE_SIZE);
+      group, out, kHashTLabel, sizeof(kHashTLabel), t, TRUST_TOKEN_NONCE_SIZE);
 }
 
 static int pmbtoken_exp1_hash_s(const EC_GROUP *group, EC_RAW_POINT *out,
                                 const EC_AFFINE *t,
-                                const uint8_t s[PMBTOKEN_NONCE_SIZE]) {
+                                const uint8_t s[TRUST_TOKEN_NONCE_SIZE]) {
   const uint8_t kHashSLabel[] = "PMBTokens Experiment V1 HashS";
   int ret = 0;
   CBB cbb;
@@ -1117,7 +1117,7 @@
   size_t len;
   if (!CBB_init(&cbb, 0) ||
       !point_to_cbb(&cbb, group, t) ||
-      !CBB_add_bytes(&cbb, s, PMBTOKEN_NONCE_SIZE) ||
+      !CBB_add_bytes(&cbb, s, TRUST_TOKEN_NONCE_SIZE) ||
       !CBB_finish(&cbb, &buf, &len) ||
       !ec_hash_to_curve_p384_xmd_sha512_sswu_draft07(
           group, out, kHashSLabel, sizeof(kHashSLabel), buf, len)) {
@@ -1182,7 +1182,7 @@
   return pmbtoken_generate_key(&pmbtoken_exp1_method, out_private, out_public);
 }
 
-int pmbtoken_exp1_client_key_from_bytes(PMBTOKEN_CLIENT_KEY *key,
+int pmbtoken_exp1_client_key_from_bytes(TRUST_TOKEN_CLIENT_KEY *key,
                                         const uint8_t *in, size_t len) {
   if (!pmbtoken_exp1_init_method()) {
     return 0;
@@ -1190,7 +1190,7 @@
   return pmbtoken_client_key_from_bytes(&pmbtoken_exp1_method, key, in, len);
 }
 
-int pmbtoken_exp1_issuer_key_from_bytes(PMBTOKEN_ISSUER_KEY *key,
+int pmbtoken_exp1_issuer_key_from_bytes(TRUST_TOKEN_ISSUER_KEY *key,
                                         const uint8_t *in, size_t len) {
   if (!pmbtoken_exp1_init_method()) {
     return 0;
@@ -1198,14 +1198,14 @@
   return pmbtoken_issuer_key_from_bytes(&pmbtoken_exp1_method, key, in, len);
 }
 
-STACK_OF(PMBTOKEN_PRETOKEN) * pmbtoken_exp1_blind(CBB *cbb, size_t count) {
+STACK_OF(TRUST_TOKEN_PRETOKEN) * pmbtoken_exp1_blind(CBB *cbb, size_t count) {
   if (!pmbtoken_exp1_init_method()) {
     return NULL;
   }
   return pmbtoken_blind(&pmbtoken_exp1_method, cbb, count);
 }
 
-int pmbtoken_exp1_sign(const PMBTOKEN_ISSUER_KEY *key, CBB *cbb, CBS *cbs,
+int pmbtoken_exp1_sign(const TRUST_TOKEN_ISSUER_KEY *key, CBB *cbb, CBS *cbs,
                        size_t num_requested, size_t num_to_issue,
                        uint8_t private_metadata) {
   if (!pmbtoken_exp1_init_method()) {
@@ -1216,8 +1216,8 @@
 }
 
 STACK_OF(TRUST_TOKEN) *
-    pmbtoken_exp1_unblind(const PMBTOKEN_CLIENT_KEY *key,
-                          const STACK_OF(PMBTOKEN_PRETOKEN) * pretokens,
+    pmbtoken_exp1_unblind(const TRUST_TOKEN_CLIENT_KEY *key,
+                          const STACK_OF(TRUST_TOKEN_PRETOKEN) * pretokens,
                           CBS *cbs, size_t count, uint32_t key_id) {
   if (!pmbtoken_exp1_init_method()) {
     return NULL;
@@ -1226,8 +1226,8 @@
                           key_id);
 }
 
-int pmbtoken_exp1_read(const PMBTOKEN_ISSUER_KEY *key,
-                       uint8_t out_nonce[PMBTOKEN_NONCE_SIZE],
+int pmbtoken_exp1_read(const TRUST_TOKEN_ISSUER_KEY *key,
+                       uint8_t out_nonce[TRUST_TOKEN_NONCE_SIZE],
                        uint8_t *out_private_metadata, const uint8_t *token,
                        size_t token_len) {
   if (!pmbtoken_exp1_init_method()) {
@@ -1251,15 +1251,15 @@
 // 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 t[TRUST_TOKEN_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);
+      group, out, kHashTLabel, sizeof(kHashTLabel), t, TRUST_TOKEN_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 s[TRUST_TOKEN_NONCE_SIZE]) {
   const uint8_t kHashSLabel[] = "PMBTokens Experiment V2 HashS";
   int ret = 0;
   CBB cbb;
@@ -1267,7 +1267,7 @@
   size_t len;
   if (!CBB_init(&cbb, 0) ||
       !point_to_cbb(&cbb, group, t) ||
-      !CBB_add_bytes(&cbb, s, PMBTOKEN_NONCE_SIZE) ||
+      !CBB_add_bytes(&cbb, s, TRUST_TOKEN_NONCE_SIZE) ||
       !CBB_finish(&cbb, &buf, &len) ||
       !ec_hash_to_curve_p384_xmd_sha512_sswu_draft07(
           group, out, kHashSLabel, sizeof(kHashSLabel), buf, len)) {
@@ -1332,7 +1332,7 @@
   return pmbtoken_generate_key(&pmbtoken_exp2_method, out_private, out_public);
 }
 
-int pmbtoken_exp2_client_key_from_bytes(PMBTOKEN_CLIENT_KEY *key,
+int pmbtoken_exp2_client_key_from_bytes(TRUST_TOKEN_CLIENT_KEY *key,
                                         const uint8_t *in, size_t len) {
   if (!pmbtoken_exp2_init_method()) {
     return 0;
@@ -1340,7 +1340,7 @@
   return pmbtoken_client_key_from_bytes(&pmbtoken_exp2_method, key, in, len);
 }
 
-int pmbtoken_exp2_issuer_key_from_bytes(PMBTOKEN_ISSUER_KEY *key,
+int pmbtoken_exp2_issuer_key_from_bytes(TRUST_TOKEN_ISSUER_KEY *key,
                                         const uint8_t *in, size_t len) {
   if (!pmbtoken_exp2_init_method()) {
     return 0;
@@ -1348,14 +1348,14 @@
   return pmbtoken_issuer_key_from_bytes(&pmbtoken_exp2_method, key, in, len);
 }
 
-STACK_OF(PMBTOKEN_PRETOKEN) * pmbtoken_exp2_blind(CBB *cbb, size_t count) {
+STACK_OF(TRUST_TOKEN_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,
+int pmbtoken_exp2_sign(const TRUST_TOKEN_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()) {
@@ -1366,8 +1366,8 @@
 }
 
 STACK_OF(TRUST_TOKEN) *
-    pmbtoken_exp2_unblind(const PMBTOKEN_CLIENT_KEY *key,
-                          const STACK_OF(PMBTOKEN_PRETOKEN) * pretokens,
+    pmbtoken_exp2_unblind(const TRUST_TOKEN_CLIENT_KEY *key,
+                          const STACK_OF(TRUST_TOKEN_PRETOKEN) * pretokens,
                           CBS *cbs, size_t count, uint32_t key_id) {
   if (!pmbtoken_exp2_init_method()) {
     return NULL;
@@ -1376,8 +1376,8 @@
                           key_id);
 }
 
-int pmbtoken_exp2_read(const PMBTOKEN_ISSUER_KEY *key,
-                       uint8_t out_nonce[PMBTOKEN_NONCE_SIZE],
+int pmbtoken_exp2_read(const TRUST_TOKEN_ISSUER_KEY *key,
+                       uint8_t out_nonce[TRUST_TOKEN_NONCE_SIZE],
                        uint8_t *out_private_metadata, const uint8_t *token,
                        size_t token_len) {
   if (!pmbtoken_exp2_init_method()) {
diff --git a/crypto/trust_token/trust_token.c b/crypto/trust_token/trust_token.c
index fea619e..a4891d8 100644
--- a/crypto/trust_token/trust_token.c
+++ b/crypto/trust_token/trust_token.c
@@ -43,15 +43,15 @@
   return &kMethod;
 }
 
-const TRUST_TOKEN_METHOD *TRUST_TOKEN_experiment_v2_pp(void) {
+const TRUST_TOKEN_METHOD *TRUST_TOKEN_experiment_v2_voprf(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,
+      voprf_exp2_generate_key,
+      voprf_exp2_client_key_from_bytes,
+      voprf_exp2_issuer_key_from_bytes,
+      voprf_exp2_blind,
+      voprf_exp2_sign,
+      voprf_exp2_unblind,
+      voprf_exp2_read,
       0, /* has_private_metadata */
       6, /* max_keys */
       0, /* has_srr */
@@ -75,6 +75,10 @@
   return &kMethod;
 }
 
+void TRUST_TOKEN_PRETOKEN_free(TRUST_TOKEN_PRETOKEN *pretoken) {
+  OPENSSL_free(pretoken);
+}
+
 TRUST_TOKEN *TRUST_TOKEN_new(const uint8_t *data, size_t len) {
   TRUST_TOKEN *ret = OPENSSL_malloc(sizeof(TRUST_TOKEN));
   if (ret == NULL) {
@@ -160,7 +164,7 @@
     return;
   }
   EVP_PKEY_free(ctx->srr_key);
-  sk_PMBTOKEN_PRETOKEN_pop_free(ctx->pretokens, PMBTOKEN_PRETOKEN_free);
+  sk_TRUST_TOKEN_PRETOKEN_pop_free(ctx->pretokens, TRUST_TOKEN_PRETOKEN_free);
   OPENSSL_free(ctx);
 }
 
@@ -206,7 +210,7 @@
 
   int ret = 0;
   CBB request;
-  STACK_OF(PMBTOKEN_PRETOKEN) *pretokens = NULL;
+  STACK_OF(TRUST_TOKEN_PRETOKEN) *pretokens = NULL;
   if (!CBB_init(&request, 0) ||
       !CBB_add_u16(&request, count)) {
     OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
@@ -223,14 +227,14 @@
     goto err;
   }
 
-  sk_PMBTOKEN_PRETOKEN_pop_free(ctx->pretokens, PMBTOKEN_PRETOKEN_free);
+  sk_TRUST_TOKEN_PRETOKEN_pop_free(ctx->pretokens, TRUST_TOKEN_PRETOKEN_free);
   ctx->pretokens = pretokens;
   pretokens = NULL;
   ret = 1;
 
 err:
   CBB_cleanup(&request);
-  sk_PMBTOKEN_PRETOKEN_pop_free(pretokens, PMBTOKEN_PRETOKEN_free);
+  sk_TRUST_TOKEN_PRETOKEN_pop_free(pretokens, TRUST_TOKEN_PRETOKEN_free);
   return ret;
 }
 
@@ -264,7 +268,7 @@
     return NULL;
   }
 
-  if (count > sk_PMBTOKEN_PRETOKEN_num(ctx->pretokens)) {
+  if (count > sk_TRUST_TOKEN_PRETOKEN_num(ctx->pretokens)) {
     OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
     return NULL;
   }
@@ -281,7 +285,7 @@
     return NULL;
   }
 
-  sk_PMBTOKEN_PRETOKEN_pop_free(ctx->pretokens, PMBTOKEN_PRETOKEN_free);
+  sk_TRUST_TOKEN_PRETOKEN_pop_free(ctx->pretokens, TRUST_TOKEN_PRETOKEN_free);
   ctx->pretokens = NULL;
 
   *out_key_index = key_index;
@@ -315,30 +319,39 @@
                                          size_t response_len) {
   CBS in, srr, sig;
   CBS_init(&in, response, response_len);
+  if (!ctx->method->has_srr) {
+    if (!CBS_stow(&in, out_rr, out_rr_len)) {
+      OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+      return 0;
+    }
+
+    *out_sig = NULL;
+    *out_sig_len = 0;
+    return 1;
+  }
+
   if (!CBS_get_u16_length_prefixed(&in, &srr) ||
-      !CBS_get_u16_length_prefixed(&in, &sig)) {
+      !CBS_get_u16_length_prefixed(&in, &sig) ||
+      CBS_len(&in) != 0) {
     OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_ERROR);
     return 0;
   }
 
-  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 (ctx->srr_key == NULL) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_NO_SRR_KEY_CONFIGURED);
+    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);
+  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;
-    }
+  if (!sig_ok) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_SRR_SIGNATURE_ERROR);
+    return 0;
   }
 
   uint8_t *srr_buf = NULL, *sig_buf = NULL;
@@ -588,7 +601,7 @@
 
   const struct trust_token_issuer_key_st *key =
       trust_token_issuer_get_key(ctx, public_metadata);
-  uint8_t nonce[PMBTOKEN_NONCE_SIZE];
+  uint8_t nonce[TRUST_TOKEN_NONCE_SIZE];
   if (key == NULL ||
       !ctx->method->read(&key->key, nonce, &private_metadata,
                          CBS_data(&token_cbs), CBS_len(&token_cbs))) {
@@ -672,16 +685,56 @@
     goto err;
   }
 
-  CBB child;
-  uint8_t *ptr;
-  if (!CBB_add_u16_length_prefixed(&response, &child) ||
-      !CBB_add_bytes(&child, srr_buf, srr_len) ||
-      !CBB_add_u16_length_prefixed(&response, &child) ||
-      !CBB_reserve(&child, &ptr, sig_len) ||
-      !EVP_DigestSign(&md_ctx, ptr, &sig_len, srr_buf, srr_len) ||
-      !CBB_did_write(&child, sig_len)) {
-    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
-    goto err;
+  // Merge SRR and Signature into single string.
+  // TODO(svaldez): Expose API to construct this from the caller.
+  if (!ctx->method->has_srr) {
+    static const char kSRRHeader[] = "body=:";
+    static const char kSRRSplit[] = ":, signature=:";
+    static const char kSRREnd[] = ":";
+
+    size_t srr_b64_len, sig_b64_len;
+    if (!EVP_EncodedLength(&srr_b64_len, srr_len) ||
+        !EVP_EncodedLength(&sig_b64_len, sig_len)) {
+      goto err;
+    }
+
+    sig_buf = OPENSSL_malloc(sig_len);
+    uint8_t *srr_b64_buf = OPENSSL_malloc(srr_b64_len);
+    uint8_t *sig_b64_buf = OPENSSL_malloc(sig_b64_len);
+    if (!sig_buf ||
+        !srr_b64_buf ||
+        !sig_b64_buf ||
+        !EVP_DigestSign(&md_ctx, sig_buf, &sig_len, srr_buf, srr_len) ||
+        !CBB_add_bytes(&response, (const uint8_t *)kSRRHeader,
+                       strlen(kSRRHeader)) ||
+        !CBB_add_bytes(&response, srr_b64_buf,
+                       EVP_EncodeBlock(srr_b64_buf, srr_buf, srr_len)) ||
+        !CBB_add_bytes(&response, (const uint8_t *)kSRRSplit,
+                       strlen(kSRRSplit)) ||
+        !CBB_add_bytes(&response, sig_b64_buf,
+                       EVP_EncodeBlock(sig_b64_buf, sig_buf, sig_len)) ||
+        !CBB_add_bytes(&response, (const uint8_t *)kSRREnd, strlen(kSRREnd))) {
+      OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+      OPENSSL_free(srr_b64_buf);
+      OPENSSL_free(sig_b64_buf);
+      goto err;
+    }
+
+    OPENSSL_free(srr_b64_buf);
+    OPENSSL_free(sig_b64_buf);
+  } else {
+    CBB child;
+    uint8_t *ptr;
+    if (!CBB_add_u16_length_prefixed(&response, &child) ||
+        !CBB_add_bytes(&child, srr_buf, srr_len) ||
+        !CBB_add_u16_length_prefixed(&response, &child) ||
+        !CBB_reserve(&child, &ptr, sig_len) ||
+        !EVP_DigestSign(&md_ctx, ptr, &sig_len, srr_buf, srr_len) ||
+        !CBB_did_write(&child, sig_len) ||
+        !CBB_flush(&response)) {
+      OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+      goto err;
+    }
   }
 
   if (!CBS_stow(&client_data, &client_data_buf, &client_data_len) ||
@@ -690,7 +743,7 @@
     goto err;
   }
 
-  TRUST_TOKEN *token = TRUST_TOKEN_new(nonce, PMBTOKEN_NONCE_SIZE);
+  TRUST_TOKEN *token = TRUST_TOKEN_new(nonce, TRUST_TOKEN_NONCE_SIZE);
   if (token == NULL) {
     OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
     goto err;
diff --git a/crypto/trust_token/trust_token_test.cc b/crypto/trust_token/trust_token_test.cc
index b282500..7f9b79e 100644
--- a/crypto/trust_token/trust_token_test.cc
+++ b/crypto/trust_token/trust_token_test.cc
@@ -56,16 +56,16 @@
   ASSERT_EQ(301u, pub_key_len);
 }
 
-TEST(TrustTokenTest, KeyGenExp2PP) {
+TEST(TrustTokenTest, KeyGenExp2VOPRF) {
   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_experiment_v2_voprf(), 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);
+  ASSERT_EQ(52u, priv_key_len);
+  ASSERT_EQ(101u, pub_key_len);
 }
 
 TEST(TrustTokenTest, KeyGenExp2PMB) {
@@ -127,7 +127,7 @@
 static std::vector<const TRUST_TOKEN_METHOD *> AllMethods() {
   return {
     TRUST_TOKEN_experiment_v1(),
-    TRUST_TOKEN_experiment_v2_pp(),
+    TRUST_TOKEN_experiment_v2_voprf(),
     TRUST_TOKEN_experiment_v2_pmb()
   };
 }
@@ -389,10 +389,14 @@
               Bytes(client_data, client_data_len));
     resp_len = 10;
 
+    // If the protocol doesn't use SRRs, TRUST_TOKEN_CLIENT_finish_redemtpion
+    // leaves all SRR validation to the caller.
     uint8_t *srr = NULL, *sig = NULL;
     size_t srr_len, sig_len;
-    ASSERT_FALSE(TRUST_TOKEN_CLIENT_finish_redemption(
-        client.get(), &srr, &srr_len, &sig, &sig_len, redeem_resp, resp_len));
+    bool expect_failure = !method()->has_srr;
+    ASSERT_EQ(expect_failure, TRUST_TOKEN_CLIENT_finish_redemption(
+                                  client.get(), &srr, &srr_len, &sig, &sig_len,
+                                  redeem_resp, resp_len));
     bssl::UniquePtr<uint8_t> free_srr(srr);
     bssl::UniquePtr<uint8_t> free_sig(sig);
   }
@@ -534,6 +538,27 @@
     bssl::UniquePtr<uint8_t> free_srr(srr);
     bssl::UniquePtr<uint8_t> free_sig(sig);
 
+    if (!method()->has_srr) {
+      size_t b64_len;
+      ASSERT_TRUE(EVP_EncodedLength(&b64_len, sizeof(kExpectedSRR) - 1));
+      b64_len -= 1;
+
+      const char kSRRHeader[] = "body=:";
+      ASSERT_LT(sizeof(kSRRHeader) - 1 + b64_len, srr_len);
+
+      ASSERT_EQ(Bytes(kSRRHeader, sizeof(kSRRHeader) - 1),
+                Bytes(srr, sizeof(kSRRHeader) - 1));
+      uint8_t *decoded_srr =
+          (uint8_t *)OPENSSL_malloc(sizeof(kExpectedSRR) + 1);
+      ASSERT_TRUE(decoded_srr);
+      ASSERT_LT(
+          int(sizeof(kExpectedSRR) - 1),
+          EVP_DecodeBlock(decoded_srr, srr + sizeof(kSRRHeader) - 1, b64_len));
+      srr = decoded_srr;
+      srr_len = sizeof(kExpectedSRR) - 1;
+      free_srr.reset(srr);
+    }
+
     const uint8_t kTokenHashDSTLabel[] = "TrustTokenV0 TokenHash";
     uint8_t token_hash[SHA256_DIGEST_LENGTH];
     SHA256_CTX sha_ctx;
@@ -547,8 +572,8 @@
 
     uint8_t decode_private_metadata;
     ASSERT_TRUE(TRUST_TOKEN_decode_private_metadata(
-        method(), &decode_private_metadata, metadata_key, sizeof(metadata_key),
-        token_hash, sizeof(token_hash), srr[27]));
+        method(), &decode_private_metadata, metadata_key,
+        sizeof(metadata_key), token_hash, sizeof(token_hash), srr[27]));
     ASSERT_EQ(srr[18], public_metadata());
     ASSERT_EQ(decode_private_metadata, private_metadata());
 
@@ -623,10 +648,13 @@
 
   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));
+      TRUST_TOKEN_NONCE_SIZE + 2 * (1 + 2 * BN_num_bytes(&group->field));
   if (method() == TRUST_TOKEN_experiment_v1()) {
     token_length += 4;
   }
+  if (method() == TRUST_TOKEN_experiment_v2_voprf()) {
+    token_length = 1 + 2 * BN_num_bytes(&group->field);
+  }
   for (size_t i = 0; i < count; i++) {
     ASSERT_TRUE(CBB_add_bytes(bad_response.get(), CBS_data(&real_response),
                               token_length));
@@ -683,10 +711,13 @@
 
   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));
+      TRUST_TOKEN_NONCE_SIZE + 2 * (1 + 2 * BN_num_bytes(&group->field));
   if (method() == TRUST_TOKEN_experiment_v1()) {
     token_length += 4;
   }
+  if (method() == TRUST_TOKEN_experiment_v2_voprf()) {
+    token_length = 1 + 2 * BN_num_bytes(&group->field);
+  }
   for (size_t i = 0; i < count; i++) {
     ASSERT_TRUE(CBB_add_bytes(bad_response.get(), CBS_data(&real_response),
                               token_length));
@@ -734,7 +765,11 @@
 };
 
 TEST_P(TrustTokenBadKeyTest, BadKey) {
-  if (!method()->has_private_metadata && private_metadata()) {
+  // For versions without private metadata, only corruptions of 'xs' (the 4th
+  // entry in |scalars| below) result in a bad key, as the other scalars are
+  // unused internally.
+  if (!method()->has_private_metadata &&
+      (private_metadata() || corrupted_key() != 4)) {
     return;
   }
 
diff --git a/crypto/trust_token/voprf.c b/crypto/trust_token/voprf.c
new file mode 100644
index 0000000..f93ee9c
--- /dev/null
+++ b/crypto/trust_token/voprf.c
@@ -0,0 +1,766 @@
+/* Copyright (c) 2020, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+#include <openssl/trust_token.h>
+
+#include <openssl/bn.h>
+#include <openssl/bytestring.h>
+#include <openssl/ec.h>
+#include <openssl/err.h>
+#include <openssl/mem.h>
+#include <openssl/nid.h>
+#include <openssl/rand.h>
+
+#include "../ec_extra/internal.h"
+#include "../fipsmodule/ec/internal.h"
+
+#include "internal.h"
+
+
+typedef int (*hash_to_group_func_t)(const EC_GROUP *group, EC_RAW_POINT *out,
+                                    const uint8_t t[TRUST_TOKEN_NONCE_SIZE]);
+typedef int (*hash_to_scalar_func_t)(const EC_GROUP *group, EC_SCALAR *out,
+                                     uint8_t *buf, size_t len);
+
+typedef struct {
+  const EC_GROUP *group;
+
+  // hash_to_group implements the HashToGroup operation for VOPRFs. It returns
+  // one on success and zero on error.
+  hash_to_group_func_t hash_to_group;
+  // hash_to_scalar implements the HashToScalar operation for VOPRFs. It returns
+  // one on success and zero on error.
+  hash_to_scalar_func_t hash_to_scalar;
+} VOPRF_METHOD;
+
+static const uint8_t kDefaultAdditionalData[32] = {0};
+
+static int voprf_init_method(VOPRF_METHOD *method, int curve_nid,
+                             hash_to_group_func_t hash_to_group,
+                             hash_to_scalar_func_t hash_to_scalar) {
+  method->group = EC_GROUP_new_by_curve_name(curve_nid);
+  if (method->group == NULL) {
+    return 0;
+  }
+
+  method->hash_to_group = hash_to_group;
+  method->hash_to_scalar = hash_to_scalar;
+
+  return 1;
+}
+
+static int cbb_add_point(CBB *out, const EC_GROUP *group,
+                         const EC_AFFINE *point) {
+  size_t len =
+      ec_point_to_bytes(group, point, 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,
+                           len) == len &&
+         CBB_flush(out);
+}
+
+static int cbs_get_point(CBS *cbs, const EC_GROUP *group, EC_AFFINE *out) {
+  CBS child;
+  size_t plen = 1 + 2 * BN_num_bytes(&group->field);
+  if (!CBS_get_bytes(cbs, &child, plen) ||
+      !ec_point_from_uncompressed(group, out, CBS_data(&child),
+                                  CBS_len(&child))) {
+    return 0;
+  }
+  return 1;
+}
+
+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 voprf_generate_key(const VOPRF_METHOD *method, CBB *out_private,
+                              CBB *out_public) {
+  const EC_GROUP *group = method->group;
+  EC_RAW_POINT pub;
+  EC_SCALAR priv;
+  EC_AFFINE pub_affine;
+  if (!ec_random_nonzero_scalar(group, &priv, kDefaultAdditionalData) ||
+      !ec_point_mul_scalar_base(group, &pub, &priv) ||
+      !ec_jacobian_to_affine(group, &pub_affine, &pub)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_KEYGEN_FAILURE);
+    return 0;
+  }
+
+  if (!scalar_to_cbb(out_private, group, &priv) ||
+      !cbb_add_point(out_public, group, &pub_affine)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_BUFFER_TOO_SMALL);
+    return 0;
+  }
+
+  return 1;
+}
+
+static int voprf_client_key_from_bytes(const VOPRF_METHOD *method,
+                                       TRUST_TOKEN_CLIENT_KEY *key,
+                                       const uint8_t *in, size_t len) {
+  const EC_GROUP *group = method->group;
+  if (!ec_point_from_uncompressed(group, &key->pubs, in, len)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
+    return 0;
+  }
+
+  return 1;
+}
+
+static int voprf_issuer_key_from_bytes(const VOPRF_METHOD *method,
+                                       TRUST_TOKEN_ISSUER_KEY *key,
+                                       const uint8_t *in, size_t len) {
+  const EC_GROUP *group = method->group;
+  if (!ec_scalar_from_bytes(group, &key->xs, in, len)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
+    return 0;
+  }
+
+  // Recompute the public key.
+  EC_RAW_POINT pub;
+  if (!ec_point_mul_scalar_base(group, &pub, &key->xs) ||
+      !ec_jacobian_to_affine(group, &key->pubs, &pub)) {
+    return 0;
+  }
+
+  return 1;
+}
+
+static STACK_OF(TRUST_TOKEN_PRETOKEN) *
+    voprf_blind(const VOPRF_METHOD *method, CBB *cbb, size_t count) {
+  const EC_GROUP *group = method->group;
+  STACK_OF(TRUST_TOKEN_PRETOKEN) *pretokens =
+      sk_TRUST_TOKEN_PRETOKEN_new_null();
+  if (pretokens == NULL) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    goto err;
+  }
+
+  for (size_t i = 0; i < count; i++) {
+    // Insert |pretoken| into |pretokens| early to simplify error-handling.
+    TRUST_TOKEN_PRETOKEN *pretoken =
+        OPENSSL_malloc(sizeof(TRUST_TOKEN_PRETOKEN));
+    if (pretoken == NULL ||
+        !sk_TRUST_TOKEN_PRETOKEN_push(pretokens, pretoken)) {
+      OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+      TRUST_TOKEN_PRETOKEN_free(pretoken);
+      goto err;
+    }
+
+    RAND_bytes(pretoken->t, sizeof(pretoken->t));
+
+    // We sample r in Montgomery form to simplify inverting.
+    EC_SCALAR r;
+    if (!ec_random_nonzero_scalar(group, &r,
+                                  kDefaultAdditionalData)) {
+      OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+      goto err;
+    }
+
+    // pretoken->r is rinv.
+    ec_scalar_inv0_montgomery(group, &pretoken->r, &r);
+    // Convert both out of Montgomery form.
+    ec_scalar_from_montgomery(group, &r, &r);
+    ec_scalar_from_montgomery(group, &pretoken->r, &pretoken->r);
+
+    // Tp is the blinded token in the VOPRF protocol.
+    EC_RAW_POINT P, Tp;
+    if (!method->hash_to_group(group, &P, pretoken->t) ||
+        !ec_point_mul_scalar(group, &Tp, &P, &r) ||
+        !ec_jacobian_to_affine(group, &pretoken->Tp, &Tp)) {
+      goto err;
+    }
+
+    if (!cbb_add_point(cbb, group, &pretoken->Tp)) {
+      goto err;
+    }
+  }
+
+  return pretokens;
+
+err:
+  sk_TRUST_TOKEN_PRETOKEN_pop_free(pretokens, TRUST_TOKEN_PRETOKEN_free);
+  return NULL;
+}
+
+static int hash_to_scalar_dleq(const VOPRF_METHOD *method, EC_SCALAR *out,
+                               const EC_AFFINE *X, const EC_AFFINE *T,
+                               const EC_AFFINE *W, const EC_AFFINE *K0,
+                               const EC_AFFINE *K1) {
+  static const uint8_t kDLEQLabel[] = "DLEQ";
+
+  int ok = 0;
+  CBB cbb;
+  CBB_zero(&cbb);
+  uint8_t *buf = NULL;
+  size_t len;
+  if (!CBB_init(&cbb, 0) ||
+      !CBB_add_bytes(&cbb, kDLEQLabel, sizeof(kDLEQLabel)) ||
+      !cbb_add_point(&cbb, method->group, X) ||
+      !cbb_add_point(&cbb, method->group, T) ||
+      !cbb_add_point(&cbb, method->group, W) ||
+      !cbb_add_point(&cbb, method->group, K0) ||
+      !cbb_add_point(&cbb, method->group, K1) ||
+      !CBB_finish(&cbb, &buf, &len) ||
+      !method->hash_to_scalar(method->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_to_scalar_batch(const VOPRF_METHOD *method, EC_SCALAR *out,
+                                const CBB *points, size_t index) {
+  static const uint8_t kDLEQBatchLabel[] = "DLEQ BATCH";
+  if (index > 0xffff) {
+    // The protocol supports only two-byte batches.
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_OVERFLOW);
+    return 0;
+  }
+
+  int ok = 0;
+  CBB cbb;
+  CBB_zero(&cbb);
+  uint8_t *buf = NULL;
+  size_t len;
+  if (!CBB_init(&cbb, 0) ||
+      !CBB_add_bytes(&cbb, kDLEQBatchLabel, sizeof(kDLEQBatchLabel)) ||
+      !CBB_add_bytes(&cbb, CBB_data(points), CBB_len(points)) ||
+      !CBB_add_u16(&cbb, (uint16_t)index) ||
+      !CBB_finish(&cbb, &buf, &len) ||
+      !method->hash_to_scalar(method->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 dleq_generate(const VOPRF_METHOD *method, CBB *cbb,
+                         const TRUST_TOKEN_ISSUER_KEY *priv,
+                         const EC_RAW_POINT *T, const EC_RAW_POINT *W) {
+  const EC_GROUP *group = method->group;
+
+  enum {
+    idx_T,
+    idx_W,
+    idx_k0,
+    idx_k1,
+    num_idx,
+  };
+  EC_RAW_POINT jacobians[num_idx];
+
+  // Setup the DLEQ proof.
+  EC_SCALAR r;
+  if (// r <- Zp
+      !ec_random_nonzero_scalar(group, &r, kDefaultAdditionalData) ||
+      // k0;k1 = r*(G;T)
+      !ec_point_mul_scalar_base(group, &jacobians[idx_k0], &r) ||
+      !ec_point_mul_scalar(group, &jacobians[idx_k1], T, &r))  {
+    return 0;
+  }
+
+  EC_AFFINE affines[num_idx];
+  jacobians[idx_T] = *T;
+  jacobians[idx_W] = *W;
+  if (!ec_jacobian_to_affine_batch(group, affines, jacobians, num_idx)) {
+    return 0;
+  }
+
+  // Compute c = Hc(...).
+  EC_SCALAR c;
+  if (!hash_to_scalar_dleq(method, &c, &priv->pubs, &affines[idx_T],
+                           &affines[idx_W], &affines[idx_k0],
+                           &affines[idx_k1])) {
+    return 0;
+  }
+
+
+  EC_SCALAR c_mont;
+  ec_scalar_to_montgomery(group, &c_mont, &c);
+
+  // u = r + c*xs
+  EC_SCALAR u;
+  ec_scalar_mul_montgomery(group, &u, &priv->xs, &c_mont);
+  ec_scalar_add(group, &u, &r, &u);
+
+  // Store DLEQ proof in transcript.
+  if (!scalar_to_cbb(cbb, group, &c) ||
+      !scalar_to_cbb(cbb, group, &u)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    return 0;
+  }
+
+  return 1;
+}
+
+static int mul_public_2(const EC_GROUP *group, EC_RAW_POINT *out,
+                        const EC_RAW_POINT *p0, const EC_SCALAR *scalar0,
+                        const EC_RAW_POINT *p1, const EC_SCALAR *scalar1) {
+  EC_RAW_POINT points[2] = {*p0, *p1};
+  EC_SCALAR scalars[2] = {*scalar0, *scalar1};
+  return ec_point_mul_scalar_public_batch(group, out, /*g_scalar=*/NULL, points,
+                                          scalars, 2);
+}
+
+static int dleq_verify(const VOPRF_METHOD *method, CBS *cbs,
+                       const TRUST_TOKEN_CLIENT_KEY *pub, const EC_RAW_POINT *T,
+                       const EC_RAW_POINT *W) {
+  const EC_GROUP *group = method->group;
+
+
+  enum {
+    idx_T,
+    idx_W,
+    idx_k0,
+    idx_k1,
+    num_idx,
+  };
+  EC_RAW_POINT jacobians[num_idx];
+
+  // Decode the DLEQ proof.
+  EC_SCALAR c, u;
+  if (!scalar_from_cbs(cbs, group, &c) ||
+      !scalar_from_cbs(cbs, group, &u)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
+    return 0;
+  }
+
+  // k0;k1 = u*(G;T) - c*(pub;W)
+  EC_RAW_POINT pubs;
+  ec_affine_to_jacobian(group, &pubs, &pub->pubs);
+  EC_SCALAR minus_c;
+  ec_scalar_neg(group, &minus_c, &c);
+  if (!ec_point_mul_scalar_public(group, &jacobians[idx_k0], &u, &pubs,
+                                  &minus_c) ||
+      !mul_public_2(group, &jacobians[idx_k1], T, &u, W, &minus_c)) {
+    return 0;
+  }
+
+  // Check the DLEQ proof.
+  EC_AFFINE affines[num_idx];
+  jacobians[idx_T] = *T;
+  jacobians[idx_W] = *W;
+  if (!ec_jacobian_to_affine_batch(group, affines, jacobians, num_idx)) {
+    return 0;
+  }
+
+  // Compute c = Hc(...).
+  EC_SCALAR calculated;
+  if (!hash_to_scalar_dleq(method, &calculated, &pub->pubs, &affines[idx_T],
+                           &affines[idx_W], &affines[idx_k0],
+                           &affines[idx_k1])) {
+    return 0;
+  }
+
+  // c == calculated
+  if (!ec_scalar_equal_vartime(group, &c, &calculated)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_INVALID_PROOF);
+    return 0;
+  }
+
+  return 1;
+}
+
+static int voprf_sign(const VOPRF_METHOD *method,
+                      const TRUST_TOKEN_ISSUER_KEY *key, CBB *cbb, CBS *cbs,
+                      size_t num_requested, size_t num_to_issue) {
+  const EC_GROUP *group = method->group;
+  if (num_requested < num_to_issue) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_INTERNAL_ERROR);
+    return 0;
+  }
+
+  if (num_to_issue > ((size_t)-1) / sizeof(EC_RAW_POINT) ||
+      num_to_issue > ((size_t)-1) / sizeof(EC_SCALAR)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_OVERFLOW);
+    return 0;
+  }
+
+  int ret = 0;
+  EC_RAW_POINT *BTs = OPENSSL_malloc(num_to_issue * sizeof(EC_RAW_POINT));
+  EC_RAW_POINT *Zs = OPENSSL_malloc(num_to_issue * sizeof(EC_RAW_POINT));
+  EC_SCALAR *es = OPENSSL_malloc(num_to_issue * sizeof(EC_SCALAR));
+  CBB batch_cbb;
+  CBB_zero(&batch_cbb);
+  if (!BTs ||
+      !Zs ||
+      !es ||
+      !CBB_init(&batch_cbb, 0) ||
+      !cbb_add_point(&batch_cbb, method->group, &key->pubs)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    goto err;
+  }
+
+  for (size_t i = 0; i < num_to_issue; i++) {
+    EC_AFFINE BT_affine, Z_affine;
+    EC_RAW_POINT BT, Z;
+    if (!cbs_get_point(cbs, group, &BT_affine)) {
+      OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
+      goto err;
+    }
+    ec_affine_to_jacobian(group, &BT, &BT_affine);
+    if (!ec_point_mul_scalar(group, &Z, &BT, &key->xs) ||
+        !ec_jacobian_to_affine(group, &Z_affine, &Z) ||
+        !cbb_add_point(cbb, group, &Z_affine)) {
+      goto err;
+    }
+
+    if (!cbb_add_point(&batch_cbb, group, &BT_affine) ||
+        !cbb_add_point(&batch_cbb, group, &Z_affine)) {
+      OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+      goto err;
+    }
+    BTs[i] = BT;
+    Zs[i] = Z;
+
+    if (!CBB_flush(cbb)) {
+      goto err;
+    }
+  }
+
+  // The DLEQ batching construction is described in appendix B of
+  // https://eprint.iacr.org/2020/072/20200324:214215. Note the additional
+  // computations all act on public inputs.
+  for (size_t i = 0; i < num_to_issue; i++) {
+    if (!hash_to_scalar_batch(method, &es[i], &batch_cbb, i)) {
+      goto err;
+    }
+  }
+
+  EC_RAW_POINT BT_batch, Z_batch;
+  if (!ec_point_mul_scalar_public_batch(group, &BT_batch,
+                                        /*g_scalar=*/NULL, BTs, es,
+                                        num_to_issue) ||
+      !ec_point_mul_scalar_public_batch(group, &Z_batch,
+                                        /*g_scalar=*/NULL, Zs, es,
+                                        num_to_issue)) {
+    goto err;
+  }
+
+  CBB proof;
+  if (!CBB_add_u16_length_prefixed(cbb, &proof) ||
+      !dleq_generate(method, &proof, key, &BT_batch, &Z_batch) ||
+      !CBB_flush(cbb)) {
+    goto err;
+  }
+
+  // Skip over any unused requests.
+  size_t point_len = 1 + 2 * BN_num_bytes(&group->field);
+  if (!CBS_skip(cbs, point_len * (num_requested - num_to_issue))) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
+    goto err;
+  }
+
+  ret = 1;
+
+err:
+  OPENSSL_free(BTs);
+  OPENSSL_free(Zs);
+  OPENSSL_free(es);
+  CBB_cleanup(&batch_cbb);
+  return ret;
+}
+
+static STACK_OF(TRUST_TOKEN) *
+    voprf_unblind(const VOPRF_METHOD *method, const TRUST_TOKEN_CLIENT_KEY *key,
+                  const STACK_OF(TRUST_TOKEN_PRETOKEN) * pretokens, CBS *cbs,
+                  size_t count, uint32_t key_id) {
+  const EC_GROUP *group = method->group;
+  if (count > sk_TRUST_TOKEN_PRETOKEN_num(pretokens)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
+    return NULL;
+  }
+
+  int ok = 0;
+  STACK_OF(TRUST_TOKEN) *ret = sk_TRUST_TOKEN_new_null();
+  if (ret == NULL) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    return NULL;
+  }
+
+  if (count > ((size_t)-1) / sizeof(EC_RAW_POINT) ||
+      count > ((size_t)-1) / sizeof(EC_SCALAR)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_OVERFLOW);
+    return 0;
+  }
+  EC_RAW_POINT *BTs = OPENSSL_malloc(count * sizeof(EC_RAW_POINT));
+  EC_RAW_POINT *Zs = OPENSSL_malloc(count * sizeof(EC_RAW_POINT));
+  EC_SCALAR *es = OPENSSL_malloc(count * sizeof(EC_SCALAR));
+  CBB batch_cbb;
+  CBB_zero(&batch_cbb);
+  if (!BTs ||
+      !Zs ||
+      !es ||
+      !CBB_init(&batch_cbb, 0) ||
+      !cbb_add_point(&batch_cbb, method->group, &key->pubs)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    goto err;
+  }
+
+  for (size_t i = 0; i < count; i++) {
+    const TRUST_TOKEN_PRETOKEN *pretoken =
+        sk_TRUST_TOKEN_PRETOKEN_value(pretokens, i);
+
+    EC_AFFINE Z_affine;
+    if (!cbs_get_point(cbs, group, &Z_affine)) {
+      OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
+      goto err;
+    }
+
+    ec_affine_to_jacobian(group, &BTs[i], &pretoken->Tp);
+    ec_affine_to_jacobian(group, &Zs[i], &Z_affine);
+
+    if (!cbb_add_point(&batch_cbb, group, &pretoken->Tp) ||
+        !cbb_add_point(&batch_cbb, group, &Z_affine)) {
+      OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+      goto err;
+    }
+
+    // Unblind the token.
+    // pretoken->r is rinv.
+    EC_RAW_POINT N;
+    EC_AFFINE N_affine;
+    if (!ec_point_mul_scalar(group, &N, &Zs[i], &pretoken->r) ||
+        !ec_jacobian_to_affine(group, &N_affine, &N)) {
+      goto err;
+    }
+
+    // Serialize the token. Include |key_id| to avoid an extra copy in the layer
+    // above.
+    CBB token_cbb;
+    size_t point_len = 1 + 2 * BN_num_bytes(&group->field);
+    if (!CBB_init(&token_cbb, 4 + TRUST_TOKEN_NONCE_SIZE + (2 + point_len)) ||
+        !CBB_add_u32(&token_cbb, key_id) ||
+        !CBB_add_bytes(&token_cbb, pretoken->t, TRUST_TOKEN_NONCE_SIZE) ||
+        !cbb_add_point(&token_cbb, group, &N_affine) ||
+        !CBB_flush(&token_cbb)) {
+      CBB_cleanup(&token_cbb);
+      goto err;
+    }
+
+    TRUST_TOKEN *token =
+        TRUST_TOKEN_new(CBB_data(&token_cbb), CBB_len(&token_cbb));
+    CBB_cleanup(&token_cbb);
+    if (token == NULL ||
+        !sk_TRUST_TOKEN_push(ret, token)) {
+      OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+      TRUST_TOKEN_free(token);
+      goto err;
+    }
+  }
+
+  // The DLEQ batching construction is described in appendix B of
+  // https://eprint.iacr.org/2020/072/20200324:214215. Note the additional
+  // computations all act on public inputs.
+  for (size_t i = 0; i < count; i++) {
+    if (!hash_to_scalar_batch(method, &es[i], &batch_cbb, i)) {
+      goto err;
+    }
+  }
+
+  EC_RAW_POINT BT_batch, Z_batch;
+  if (!ec_point_mul_scalar_public_batch(group, &BT_batch,
+                                        /*g_scalar=*/NULL, BTs, es, count) ||
+      !ec_point_mul_scalar_public_batch(group, &Z_batch,
+                                        /*g_scalar=*/NULL, Zs, es, count)) {
+    goto err;
+  }
+
+  CBS proof;
+  if (!CBS_get_u16_length_prefixed(cbs, &proof) ||
+      !dleq_verify(method, &proof, key, &BT_batch, &Z_batch) ||
+      CBS_len(&proof) != 0) {
+    goto err;
+  }
+
+  ok = 1;
+
+err:
+  OPENSSL_free(BTs);
+  OPENSSL_free(Zs);
+  OPENSSL_free(es);
+  CBB_cleanup(&batch_cbb);
+  if (!ok) {
+    sk_TRUST_TOKEN_pop_free(ret, TRUST_TOKEN_free);
+    ret = NULL;
+  }
+  return ret;
+}
+
+static int voprf_read(const VOPRF_METHOD *method,
+                      const TRUST_TOKEN_ISSUER_KEY *key,
+                      uint8_t out_nonce[TRUST_TOKEN_NONCE_SIZE],
+                      const uint8_t *token, size_t token_len) {
+  const EC_GROUP *group = method->group;
+  CBS cbs;
+  CBS_init(&cbs, token, token_len);
+  EC_AFFINE Ws;
+  if (!CBS_copy_bytes(&cbs, out_nonce, TRUST_TOKEN_NONCE_SIZE) ||
+      !cbs_get_point(&cbs, group, &Ws) ||
+      CBS_len(&cbs) != 0) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_INVALID_TOKEN);
+    return 0;
+  }
+
+
+  EC_RAW_POINT T;
+  if (!method->hash_to_group(group, &T, out_nonce)) {
+    return 0;
+  }
+
+  EC_RAW_POINT Ws_calculated;
+  if (!ec_point_mul_scalar(group, &Ws_calculated, &T, &key->xs) ||
+      !ec_affine_jacobian_equal(group, &Ws, &Ws_calculated)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_BAD_VALIDITY_CHECK);
+    return 0;
+  }
+
+  return 1;
+}
+
+
+// VOPRF experiment v2.
+
+static int voprf_exp2_hash_to_group(const EC_GROUP *group, EC_RAW_POINT *out,
+                                    const uint8_t t[TRUST_TOKEN_NONCE_SIZE]) {
+  const uint8_t kHashTLabel[] = "TrustToken VOPRF Experiment V2 HashToGroup";
+  return ec_hash_to_curve_p384_xmd_sha512_sswu_draft07(
+      group, out, kHashTLabel, sizeof(kHashTLabel), t, TRUST_TOKEN_NONCE_SIZE);
+}
+
+static int voprf_exp2_hash_to_scalar(const EC_GROUP *group, EC_SCALAR *out,
+                             uint8_t *buf, size_t len) {
+  const uint8_t kHashCLabel[] = "TrustToken VOPRF Experiment V2 HashToScalar";
+  return ec_hash_to_scalar_p384_xmd_sha512_draft07(
+      group, out, kHashCLabel, sizeof(kHashCLabel), buf, len);
+}
+
+static int voprf_exp2_ok = 0;
+static VOPRF_METHOD voprf_exp2_method;
+static CRYPTO_once_t voprf_exp2_method_once = CRYPTO_ONCE_INIT;
+
+static void voprf_exp2_init_method_impl(void) {
+  voprf_exp2_ok =
+      voprf_init_method(&voprf_exp2_method, NID_secp384r1,
+                        voprf_exp2_hash_to_group, voprf_exp2_hash_to_scalar);
+}
+
+static int voprf_exp2_init_method(void) {
+  CRYPTO_once(&voprf_exp2_method_once, voprf_exp2_init_method_impl);
+  if (!voprf_exp2_ok) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_INTERNAL_ERROR);
+    return 0;
+  }
+  return 1;
+}
+
+int voprf_exp2_generate_key(CBB *out_private, CBB *out_public) {
+  if (!voprf_exp2_init_method()) {
+    return 0;
+  }
+
+  return voprf_generate_key(&voprf_exp2_method, out_private, out_public);
+}
+
+int voprf_exp2_client_key_from_bytes(TRUST_TOKEN_CLIENT_KEY *key,
+                                     const uint8_t *in, size_t len) {
+  if (!voprf_exp2_init_method()) {
+    return 0;
+  }
+  return voprf_client_key_from_bytes(&voprf_exp2_method, key, in, len);
+}
+
+int voprf_exp2_issuer_key_from_bytes(TRUST_TOKEN_ISSUER_KEY *key,
+                                     const uint8_t *in, size_t len) {
+  if (!voprf_exp2_init_method()) {
+    return 0;
+  }
+  return voprf_issuer_key_from_bytes(&voprf_exp2_method, key, in, len);
+}
+
+STACK_OF(TRUST_TOKEN_PRETOKEN) * voprf_exp2_blind(CBB *cbb, size_t count) {
+  if (!voprf_exp2_init_method()) {
+    return NULL;
+  }
+  return voprf_blind(&voprf_exp2_method, cbb, count);
+}
+
+int voprf_exp2_sign(const TRUST_TOKEN_ISSUER_KEY *key, CBB *cbb, CBS *cbs,
+                    size_t num_requested, size_t num_to_issue,
+                    uint8_t private_metadata) {
+  if (!voprf_exp2_init_method() || private_metadata != 0) {
+    return 0;
+  }
+  return voprf_sign(&voprf_exp2_method, key, cbb, cbs, num_requested,
+                    num_to_issue);
+}
+
+STACK_OF(TRUST_TOKEN) *
+    voprf_exp2_unblind(const TRUST_TOKEN_CLIENT_KEY *key,
+                       const STACK_OF(TRUST_TOKEN_PRETOKEN) * pretokens,
+                       CBS *cbs, size_t count, uint32_t key_id) {
+  if (!voprf_exp2_init_method()) {
+    return NULL;
+  }
+  return voprf_unblind(&voprf_exp2_method, key, pretokens, cbs, count,
+                          key_id);
+}
+
+int voprf_exp2_read(const TRUST_TOKEN_ISSUER_KEY *key,
+                    uint8_t out_nonce[TRUST_TOKEN_NONCE_SIZE],
+                    uint8_t *out_private_metadata, const uint8_t *token,
+                    size_t token_len) {
+  if (!voprf_exp2_init_method()) {
+    return 0;
+  }
+  return voprf_read(&voprf_exp2_method, key, out_nonce, token, token_len);
+}
diff --git a/include/openssl/trust_token.h b/include/openssl/trust_token.h
index b6c00b2..7146995 100644
--- a/include/openssl/trust_token.h
+++ b/include/openssl/trust_token.h
@@ -40,13 +40,11 @@
 // 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.
+// TRUST_TOKEN_experiment_v2_voprf is an experimental Trust Tokens protocol
+// using VOPRFs 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);
+OPENSSL_EXPORT const TRUST_TOKEN_METHOD *TRUST_TOKEN_experiment_v2_voprf(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.
@@ -165,12 +163,8 @@
 // |*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.
+// to a newly-allocated buffer containing |response| and leaves all validation
+// to the caller. It returns one on success or zero on failure.
 OPENSSL_EXPORT int TRUST_TOKEN_CLIENT_finish_redemption(
     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,
diff --git a/tool/speed.cc b/tool/speed.cc
index af81a60..5d724e7 100644
--- a/tool/speed.cc
+++ b/tool/speed.cc
@@ -984,11 +984,12 @@
   return true;
 }
 
-static PMBTOKEN_PRETOKEN *pmbtoken_pretoken_dup(PMBTOKEN_PRETOKEN *in) {
-  PMBTOKEN_PRETOKEN *out =
-      (PMBTOKEN_PRETOKEN *)OPENSSL_malloc(sizeof(PMBTOKEN_PRETOKEN));
+static TRUST_TOKEN_PRETOKEN *trust_token_pretoken_dup(
+    TRUST_TOKEN_PRETOKEN *in) {
+  TRUST_TOKEN_PRETOKEN *out =
+      (TRUST_TOKEN_PRETOKEN *)OPENSSL_malloc(sizeof(TRUST_TOKEN_PRETOKEN));
   if (out) {
-    OPENSSL_memcpy(out, in, sizeof(PMBTOKEN_PRETOKEN));
+    OPENSSL_memcpy(out, in, sizeof(TRUST_TOKEN_PRETOKEN));
   }
   return out;
 }
@@ -1059,9 +1060,9 @@
                                                    &msg_len, batchsize);
         OPENSSL_free(issue_msg);
         // Clear pretokens.
-        sk_PMBTOKEN_PRETOKEN_pop_free(client->pretokens,
-                                      PMBTOKEN_PRETOKEN_free);
-        client->pretokens = sk_PMBTOKEN_PRETOKEN_new_null();
+        sk_TRUST_TOKEN_PRETOKEN_pop_free(client->pretokens,
+                                         TRUST_TOKEN_PRETOKEN_free);
+        client->pretokens = sk_TRUST_TOKEN_PRETOKEN_new_null();
         return ok;
       })) {
     fprintf(stderr, "TRUST_TOKEN_CLIENT_begin_issuance failed.\n");
@@ -1078,9 +1079,10 @@
   }
   bssl::UniquePtr<uint8_t> free_issue_msg(issue_msg);
 
-  bssl::UniquePtr<STACK_OF(PMBTOKEN_PRETOKEN)> pretokens(
-      sk_PMBTOKEN_PRETOKEN_deep_copy(client->pretokens, pmbtoken_pretoken_dup,
-                                     PMBTOKEN_PRETOKEN_free));
+  bssl::UniquePtr<STACK_OF(TRUST_TOKEN_PRETOKEN)> pretokens(
+      sk_TRUST_TOKEN_PRETOKEN_deep_copy(client->pretokens,
+                                        trust_token_pretoken_dup,
+                                        TRUST_TOKEN_PRETOKEN_free));
 
   if (!TimeFunction(&results, [&]() -> bool {
         uint8_t *issue_resp = NULL;
@@ -1116,8 +1118,9 @@
                                                issue_resp, resp_len));
 
         // Reset pretokens.
-        client->pretokens = sk_PMBTOKEN_PRETOKEN_deep_copy(
-            pretokens.get(), pmbtoken_pretoken_dup, PMBTOKEN_PRETOKEN_free);
+        client->pretokens = sk_TRUST_TOKEN_PRETOKEN_deep_copy(
+            pretokens.get(), trust_token_pretoken_dup,
+            TRUST_TOKEN_PRETOKEN_free);
         return !!tokens;
       })) {
     fprintf(stderr, "TRUST_TOKEN_CLIENT_finish_issuance failed.\n");
@@ -1203,9 +1206,9 @@
   if (!TimeFunction(&results, [&]() -> bool {
         uint8_t *srr = NULL, *sig = NULL;
         size_t srr_len, sig_len;
-        int ok = TRUST_TOKEN_CLIENT_finish_redemption(client.get(), &srr,
-                                                      &srr_len, &sig, &sig_len,
-                                                      redeem_resp, resp_len);
+        int ok = TRUST_TOKEN_CLIENT_finish_redemption(
+            client.get(), &srr, &srr_len, &sig, &sig_len, redeem_resp,
+            redeem_resp_len);
         OPENSSL_free(srr);
         OPENSSL_free(sig);
         return ok;
@@ -1352,7 +1355,15 @@
       !SpeedTrustToken("TrustToken-Exp1-Batch1", TRUST_TOKEN_experiment_v1(), 1,
                        selected) ||
       !SpeedTrustToken("TrustToken-Exp1-Batch10", TRUST_TOKEN_experiment_v1(),
-                       10, selected)) {
+                       10, selected) ||
+      !SpeedTrustToken("TrustToken-Exp2VOPRF-Batch1",
+                       TRUST_TOKEN_experiment_v2_voprf(), 1, selected) ||
+      !SpeedTrustToken("TrustToken-Exp2VOPRF-Batch10",
+                       TRUST_TOKEN_experiment_v2_voprf(), 10, selected) ||
+      !SpeedTrustToken("TrustToken-Exp2PMB-Batch1",
+                       TRUST_TOKEN_experiment_v2_pmb(), 1, selected) ||
+      !SpeedTrustToken("TrustToken-Exp2PMB-Batch10",
+                       TRUST_TOKEN_experiment_v2_pmb(), 10, selected)) {
     return false;
   }
   if (g_print_json) {