Add Trust Token version using standardized hash2curve.

Change-Id: I6e53434246f3fef06d4f88924bfe1cbfad0543e8
Bug: chromium:1414562
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/58205
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: Steven Valdez <svaldez@google.com>
diff --git a/crypto/ec_extra/hash_to_curve.c b/crypto/ec_extra/hash_to_curve.c
index fecd535..6c9abf9 100644
--- a/crypto/ec_extra/hash_to_curve.c
+++ b/crypto/ec_extra/hash_to_curve.c
@@ -466,6 +466,18 @@
                                                msg, msg_len);
 }
 
+int ec_hash_to_scalar_p384_xmd_sha384(
+    const EC_GROUP *group, EC_SCALAR *out, const uint8_t *dst, size_t dst_len,
+    const uint8_t *msg, size_t msg_len) {
+  if (EC_GROUP_get_curve_name(group) != NID_secp384r1) {
+    OPENSSL_PUT_ERROR(EC, EC_R_GROUP_MISMATCH);
+    return 0;
+  }
+
+  return hash_to_scalar(group, EVP_sha384(), out, dst, dst_len, /*k=*/192, msg,
+                        msg_len);
+}
+
 int ec_hash_to_curve_p384_xmd_sha512_sswu_draft07(
     const EC_GROUP *group, EC_RAW_POINT *out, const uint8_t *dst,
     size_t dst_len, const uint8_t *msg, size_t msg_len) {
diff --git a/crypto/ec_extra/internal.h b/crypto/ec_extra/internal.h
index c7f517d..cf6ff2f 100644
--- a/crypto/ec_extra/internal.h
+++ b/crypto/ec_extra/internal.h
@@ -44,6 +44,14 @@
     const EC_GROUP *group, EC_RAW_POINT *out, const uint8_t *dst,
     size_t dst_len, const uint8_t *msg, size_t msg_len);
 
+// ec_hash_to_scalar_p384_xmd_sha384 hashes |msg| to a scalar on |group|
+// and writes the result to |out|, using the hash_to_field operation from the
+// P384_XMD:SHA-384_SSWU_RO_ suite from draft-irtf-cfrg-hash-to-curve-16, but
+// generating a value modulo the group order rather than a field element.
+OPENSSL_EXPORT int ec_hash_to_scalar_p384_xmd_sha384(
+    const EC_GROUP *group, EC_SCALAR *out, const uint8_t *dst, size_t dst_len,
+    const uint8_t *msg, size_t msg_len);
+
 // ec_hash_to_curve_p384_xmd_sha512_sswu_draft07 hashes |msg| to a point on
 // |group| and writes the result to |out|, implementing the
 // P384_XMD:SHA-512_SSWU_RO_ suite from draft-irtf-cfrg-hash-to-curve-07. It
diff --git a/crypto/trust_token/internal.h b/crypto/trust_token/internal.h
index 093b4ac..e940565 100644
--- a/crypto/trust_token/internal.h
+++ b/crypto/trust_token/internal.h
@@ -154,6 +154,38 @@
 // function is used to confirm H was computed as expected.
 OPENSSL_EXPORT int pmbtoken_exp2_get_h_for_testing(uint8_t out[97]);
 
+// The following functions implement the corresponding |TRUST_TOKENS_METHOD|
+// functions for |TRUST_TOKENS_pst_v1|'s PMBTokens construction which uses
+// P-384.
+int pmbtoken_pst1_generate_key(CBB *out_private, CBB *out_public);
+int pmbtoken_pst1_derive_key_from_secret(CBB *out_private, CBB *out_public,
+                                         const uint8_t *secret,
+                                         size_t secret_len);
+int pmbtoken_pst1_client_key_from_bytes(TRUST_TOKEN_CLIENT_KEY *key,
+                                        const uint8_t *in, size_t len);
+int pmbtoken_pst1_issuer_key_from_bytes(TRUST_TOKEN_ISSUER_KEY *key,
+                                        const uint8_t *in, size_t len);
+STACK_OF(TRUST_TOKEN_PRETOKEN) *pmbtoken_pst1_blind(CBB *cbb, size_t count,
+                                                    int include_message,
+                                                    const uint8_t *msg,
+                                                    size_t msg_len);
+int pmbtoken_pst1_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_pst1_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_pst1_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, int include_message,
+                       const uint8_t *msg, size_t msg_len);
+
+// pmbtoken_pst1_get_h_for_testing returns H in uncompressed coordinates. This
+// function is used to confirm H was computed as expected.
+OPENSSL_EXPORT int pmbtoken_pst1_get_h_for_testing(uint8_t out[97]);
+
 
 // VOPRF.
 //
@@ -191,6 +223,32 @@
                     size_t token_len, int include_message, const uint8_t *msg,
                     size_t msg_len);
 
+// The following functions implement the corresponding |TRUST_TOKENS_METHOD|
+// functions for |TRUST_TOKENS_pst_v1|'s VOPRF construction which uses P-384.
+int voprf_pst1_generate_key(CBB *out_private, CBB *out_public);
+int voprf_pst1_derive_key_from_secret(CBB *out_private, CBB *out_public,
+                                      const uint8_t *secret, size_t secret_len);
+int voprf_pst1_client_key_from_bytes(TRUST_TOKEN_CLIENT_KEY *key,
+                                     const uint8_t *in, size_t len);
+int voprf_pst1_issuer_key_from_bytes(TRUST_TOKEN_ISSUER_KEY *key,
+                                     const uint8_t *in, size_t len);
+STACK_OF(TRUST_TOKEN_PRETOKEN) *voprf_pst1_blind(CBB *cbb, size_t count,
+                                                 int include_message,
+                                                 const uint8_t *msg,
+                                                 size_t msg_len);
+int voprf_pst1_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_pst1_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_pst1_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, int include_message, const uint8_t *msg,
+                    size_t msg_len);
+
 
 // Trust Tokens internals.
 
diff --git a/crypto/trust_token/pmbtoken.c b/crypto/trust_token/pmbtoken.c
index 0e3d4bc..dcb9466 100644
--- a/crypto/trust_token/pmbtoken.c
+++ b/crypto/trust_token/pmbtoken.c
@@ -1508,3 +1508,177 @@
          ec_point_to_bytes(pmbtoken_exp2_method.group, &h,
                            POINT_CONVERSION_UNCOMPRESSED, out, 97) == 97;
 }
+
+// PMBTokens PST v1.
+
+static int pmbtoken_pst1_hash_t(const EC_GROUP *group, EC_RAW_POINT *out,
+                                const uint8_t t[TRUST_TOKEN_NONCE_SIZE]) {
+  const uint8_t kHashTLabel[] = "PMBTokens PST V1 HashT";
+  return ec_hash_to_curve_p384_xmd_sha384_sswu(
+      group, out, kHashTLabel, sizeof(kHashTLabel), t, TRUST_TOKEN_NONCE_SIZE);
+}
+
+static int pmbtoken_pst1_hash_s(const EC_GROUP *group, EC_RAW_POINT *out,
+                                const EC_AFFINE *t,
+                                const uint8_t s[TRUST_TOKEN_NONCE_SIZE]) {
+  const uint8_t kHashSLabel[] = "PMBTokens PST V1 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, TRUST_TOKEN_NONCE_SIZE) ||
+      !CBB_finish(&cbb, &buf, &len) ||
+      !ec_hash_to_curve_p384_xmd_sha384_sswu(
+          group, out, kHashSLabel, sizeof(kHashSLabel), buf, len)) {
+    goto err;
+  }
+
+  ret = 1;
+
+err:
+  OPENSSL_free(buf);
+  CBB_cleanup(&cbb);
+  return ret;
+}
+
+static int pmbtoken_pst1_hash_c(const EC_GROUP *group, EC_SCALAR *out,
+                                uint8_t *buf, size_t len) {
+  const uint8_t kHashCLabel[] = "PMBTokens PST V1 HashC";
+  return ec_hash_to_scalar_p384_xmd_sha384(
+      group, out, kHashCLabel, sizeof(kHashCLabel), buf, len);
+}
+
+static int pmbtoken_pst1_hash_to_scalar(const EC_GROUP *group, EC_SCALAR *out,
+                                        uint8_t *buf, size_t len) {
+  const uint8_t kHashLabel[] = "PMBTokens PST V1 HashToScalar";
+  return ec_hash_to_scalar_p384_xmd_sha384(
+      group, out, kHashLabel, sizeof(kHashLabel), buf, len);
+}
+
+static int pmbtoken_pst1_ok = 0;
+static PMBTOKEN_METHOD pmbtoken_pst1_method;
+static CRYPTO_once_t pmbtoken_pst1_method_once = CRYPTO_ONCE_INIT;
+
+static void pmbtoken_pst1_init_method_impl(void) {
+  // This is the output of |ec_hash_to_scalar_p384_xmd_sha384| with DST
+  // "PMBTokens PST V1 HashH" and message "generator".
+  static const uint8_t kH[] = {
+      0x04, 0x4c, 0xfa, 0xd4, 0x33, 0x6d, 0x8c, 0x4e, 0x18, 0xce, 0x1a,
+      0x82, 0x7b, 0x53, 0x8c, 0xf8, 0x63, 0x18, 0xe5, 0xa3, 0x96, 0x0d,
+      0x05, 0xde, 0xf4, 0x83, 0xa7, 0xd8, 0xde, 0x9c, 0x50, 0x81, 0x38,
+      0xc9, 0x38, 0x25, 0xa3, 0x70, 0x97, 0xc1, 0x1c, 0x33, 0x2e, 0x83,
+      0x68, 0x64, 0x9c, 0x53, 0x73, 0xc3, 0x03, 0xc1, 0xa9, 0xd8, 0x92,
+      0xa2, 0x32, 0xf4, 0x22, 0x40, 0x07, 0x2d, 0x9b, 0x6f, 0xab, 0xff,
+      0x2a, 0x92, 0x03, 0xb1, 0x73, 0x09, 0x1a, 0x6a, 0x4a, 0xc2, 0x4c,
+      0xac, 0x13, 0x59, 0xf4, 0x28, 0x0e, 0x78, 0x69, 0xa5, 0xdf, 0x0d,
+      0x74, 0xeb, 0x14, 0xca, 0x8a, 0x32, 0xbb, 0xd3, 0x91
+  };
+
+  pmbtoken_pst1_ok = pmbtoken_init_method(
+      &pmbtoken_pst1_method, NID_secp384r1, kH, sizeof(kH),
+      pmbtoken_pst1_hash_t, pmbtoken_pst1_hash_s, pmbtoken_pst1_hash_c,
+      pmbtoken_pst1_hash_to_scalar, 0);
+}
+
+static int pmbtoken_pst1_init_method(void) {
+  CRYPTO_once(&pmbtoken_pst1_method_once, pmbtoken_pst1_init_method_impl);
+  if (!pmbtoken_pst1_ok) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_INTERNAL_ERROR);
+    return 0;
+  }
+  return 1;
+}
+
+int pmbtoken_pst1_generate_key(CBB *out_private, CBB *out_public) {
+  if (!pmbtoken_pst1_init_method()) {
+    return 0;
+  }
+
+  return pmbtoken_generate_key(&pmbtoken_pst1_method, out_private, out_public);
+}
+
+
+int pmbtoken_pst1_derive_key_from_secret(CBB *out_private, CBB *out_public,
+                                         const uint8_t *secret,
+                                         size_t secret_len) {
+  if (!pmbtoken_pst1_init_method()) {
+    return 0;
+  }
+
+  return pmbtoken_derive_key_from_secret(&pmbtoken_pst1_method, out_private,
+                                         out_public, secret, secret_len);
+}
+
+int pmbtoken_pst1_client_key_from_bytes(TRUST_TOKEN_CLIENT_KEY *key,
+                                        const uint8_t *in, size_t len) {
+  if (!pmbtoken_pst1_init_method()) {
+    return 0;
+  }
+  return pmbtoken_client_key_from_bytes(&pmbtoken_pst1_method, key, in, len);
+}
+
+int pmbtoken_pst1_issuer_key_from_bytes(TRUST_TOKEN_ISSUER_KEY *key,
+                                        const uint8_t *in, size_t len) {
+  if (!pmbtoken_pst1_init_method()) {
+    return 0;
+  }
+  return pmbtoken_issuer_key_from_bytes(&pmbtoken_pst1_method, key, in, len);
+}
+
+STACK_OF(TRUST_TOKEN_PRETOKEN) *pmbtoken_pst1_blind(CBB *cbb, size_t count,
+                                                    int include_message,
+                                                    const uint8_t *msg,
+                                                    size_t msg_len) {
+  if (!pmbtoken_pst1_init_method()) {
+    return NULL;
+  }
+  return pmbtoken_blind(&pmbtoken_pst1_method, cbb, count, include_message, msg,
+                        msg_len);
+}
+
+int pmbtoken_pst1_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_pst1_init_method()) {
+    return 0;
+  }
+  return pmbtoken_sign(&pmbtoken_pst1_method, key, cbb, cbs, num_requested,
+                       num_to_issue, private_metadata);
+}
+
+STACK_OF(TRUST_TOKEN) *pmbtoken_pst1_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_pst1_init_method()) {
+    return NULL;
+  }
+  return pmbtoken_unblind(&pmbtoken_pst1_method, key, pretokens, cbs, count,
+                          key_id);
+}
+
+int pmbtoken_pst1_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, int include_message,
+                       const uint8_t *msg, size_t msg_len) {
+  if (!pmbtoken_pst1_init_method()) {
+    return 0;
+  }
+  return pmbtoken_read(&pmbtoken_pst1_method, key, out_nonce,
+                       out_private_metadata, token, token_len, include_message,
+                       msg, msg_len);
+}
+
+int pmbtoken_pst1_get_h_for_testing(uint8_t out[97]) {
+  if (!pmbtoken_pst1_init_method()) {
+    return 0;
+  }
+  EC_AFFINE h;
+  return ec_jacobian_to_affine(pmbtoken_pst1_method.group, &h,
+                               &pmbtoken_pst1_method.h) &&
+         ec_point_to_bytes(pmbtoken_pst1_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 7cccf1a..93172c3 100644
--- a/crypto/trust_token/trust_token.c
+++ b/crypto/trust_token/trust_token.c
@@ -78,6 +78,41 @@
   return &kMethod;
 }
 
+const TRUST_TOKEN_METHOD *TRUST_TOKEN_pst_v1_voprf(void) {
+  static const TRUST_TOKEN_METHOD kMethod = {
+      voprf_pst1_generate_key,
+      voprf_pst1_derive_key_from_secret,
+      voprf_pst1_client_key_from_bytes,
+      voprf_pst1_issuer_key_from_bytes,
+      voprf_pst1_blind,
+      voprf_pst1_sign,
+      voprf_pst1_unblind,
+      voprf_pst1_read,
+      0, /* has_private_metadata */
+      6, /* max_keys */
+      0, /* has_srr */
+  };
+  return &kMethod;
+}
+
+const TRUST_TOKEN_METHOD *TRUST_TOKEN_pst_v1_pmb(void) {
+  static const TRUST_TOKEN_METHOD kMethod = {
+      pmbtoken_pst1_generate_key,
+      pmbtoken_pst1_derive_key_from_secret,
+      pmbtoken_pst1_client_key_from_bytes,
+      pmbtoken_pst1_issuer_key_from_bytes,
+      pmbtoken_pst1_blind,
+      pmbtoken_pst1_sign,
+      pmbtoken_pst1_unblind,
+      pmbtoken_pst1_read,
+      1, /* has_private_metadata */
+      3, /* max_keys */
+      0, /* has_srr */
+  };
+  return &kMethod;
+}
+
+
 void TRUST_TOKEN_PRETOKEN_free(TRUST_TOKEN_PRETOKEN *pretoken) {
   OPENSSL_free(pretoken);
 }
diff --git a/crypto/trust_token/trust_token_test.cc b/crypto/trust_token/trust_token_test.cc
index ae4eb5c..376eacf 100644
--- a/crypto/trust_token/trust_token_test.cc
+++ b/crypto/trust_token/trust_token_test.cc
@@ -292,11 +292,35 @@
   EXPECT_EQ(Bytes(h), Bytes(expected_bytes, expected_len));
 }
 
+// Test that H in |TRUST_TOKEN_pst_v1_pmb| was computed correctly.
+TEST(TrustTokenTest, HPST1) {
+  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 PST V1 HashH";
+
+  bssl::UniquePtr<EC_POINT> expected_h(EC_POINT_new(group));
+  ASSERT_TRUE(expected_h);
+  ASSERT_TRUE(ec_hash_to_curve_p384_xmd_sha384_sswu(
+      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_pst1_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(),
     TRUST_TOKEN_experiment_v2_voprf(),
-    TRUST_TOKEN_experiment_v2_pmb()
+    TRUST_TOKEN_experiment_v2_pmb(),
+    TRUST_TOKEN_pst_v1_voprf(),
+    TRUST_TOKEN_pst_v1_pmb()
   };
 }
 
@@ -773,7 +797,8 @@
   if (method() == TRUST_TOKEN_experiment_v1()) {
     token_length += 4;
   }
-  if (method() == TRUST_TOKEN_experiment_v2_voprf()) {
+  if (method() == TRUST_TOKEN_experiment_v2_voprf() ||
+      method() == TRUST_TOKEN_pst_v1_voprf()) {
     token_length = 1 + 2 * BN_num_bytes(&group->field);
   }
   for (size_t i = 0; i < count; i++) {
@@ -841,7 +866,8 @@
   if (method() == TRUST_TOKEN_experiment_v1()) {
     token_length += 4;
   }
-  if (method() == TRUST_TOKEN_experiment_v2_voprf()) {
+  if (method() == TRUST_TOKEN_experiment_v2_voprf() ||
+      method() == TRUST_TOKEN_pst_v1_voprf()) {
     token_length = 1 + 2 * BN_num_bytes(&group->field);
   }
   for (size_t i = 0; i < count; i++) {
diff --git a/crypto/trust_token/voprf.c b/crypto/trust_token/voprf.c
index 49a324e..d414bfd 100644
--- a/crypto/trust_token/voprf.c
+++ b/crypto/trust_token/voprf.c
@@ -829,3 +829,116 @@
   return voprf_read(&voprf_exp2_method, key, out_nonce, token, token_len,
                     include_message, msg, msg_len);
 }
+
+// VOPRF PST v1.
+
+static int voprf_pst1_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 PST V1 HashToGroup";
+  return ec_hash_to_curve_p384_xmd_sha384_sswu(
+      group, out, kHashTLabel, sizeof(kHashTLabel), t, TRUST_TOKEN_NONCE_SIZE);
+}
+
+static int voprf_pst1_hash_to_scalar(const EC_GROUP *group, EC_SCALAR *out,
+                             uint8_t *buf, size_t len) {
+  const uint8_t kHashCLabel[] = "TrustToken VOPRF PST V1 HashToScalar";
+  return ec_hash_to_scalar_p384_xmd_sha384(
+      group, out, kHashCLabel, sizeof(kHashCLabel), buf, len);
+}
+
+static int voprf_pst1_ok = 0;
+static VOPRF_METHOD voprf_pst1_method;
+static CRYPTO_once_t voprf_pst1_method_once = CRYPTO_ONCE_INIT;
+
+static void voprf_pst1_init_method_impl(void) {
+  voprf_pst1_ok =
+      voprf_init_method(&voprf_pst1_method, NID_secp384r1,
+                        voprf_pst1_hash_to_group, voprf_pst1_hash_to_scalar);
+}
+
+static int voprf_pst1_init_method(void) {
+  CRYPTO_once(&voprf_pst1_method_once, voprf_pst1_init_method_impl);
+  if (!voprf_pst1_ok) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_INTERNAL_ERROR);
+    return 0;
+  }
+  return 1;
+}
+
+int voprf_pst1_generate_key(CBB *out_private, CBB *out_public) {
+  if (!voprf_pst1_init_method()) {
+    return 0;
+  }
+
+  return voprf_generate_key(&voprf_pst1_method, out_private, out_public);
+}
+
+int voprf_pst1_derive_key_from_secret(CBB *out_private, CBB *out_public,
+                                      const uint8_t *secret,
+                                      size_t secret_len) {
+  if (!voprf_pst1_init_method()) {
+    return 0;
+  }
+
+  return voprf_derive_key_from_secret(&voprf_pst1_method, out_private,
+                                      out_public, secret, secret_len);
+}
+
+int voprf_pst1_client_key_from_bytes(TRUST_TOKEN_CLIENT_KEY *key,
+                                     const uint8_t *in, size_t len) {
+  if (!voprf_pst1_init_method()) {
+    return 0;
+  }
+  return voprf_client_key_from_bytes(&voprf_pst1_method, key, in, len);
+}
+
+int voprf_pst1_issuer_key_from_bytes(TRUST_TOKEN_ISSUER_KEY *key,
+                                     const uint8_t *in, size_t len) {
+  if (!voprf_pst1_init_method()) {
+    return 0;
+  }
+  return voprf_issuer_key_from_bytes(&voprf_pst1_method, key, in, len);
+}
+
+STACK_OF(TRUST_TOKEN_PRETOKEN) *voprf_pst1_blind(CBB *cbb, size_t count,
+                                                 int include_message,
+                                                 const uint8_t *msg,
+                                                 size_t msg_len) {
+  if (!voprf_pst1_init_method()) {
+    return NULL;
+  }
+  return voprf_blind(&voprf_pst1_method, cbb, count, include_message, msg,
+                     msg_len);
+}
+
+int voprf_pst1_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_pst1_init_method() || private_metadata != 0) {
+    return 0;
+  }
+  return voprf_sign(&voprf_pst1_method, key, cbb, cbs, num_requested,
+                    num_to_issue);
+}
+
+STACK_OF(TRUST_TOKEN) *voprf_pst1_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_pst1_init_method()) {
+    return NULL;
+  }
+  return voprf_unblind(&voprf_pst1_method, key, pretokens, cbs, count, key_id);
+}
+
+int voprf_pst1_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, int include_message, const uint8_t *msg,
+                    size_t msg_len) {
+  if (!voprf_pst1_init_method()) {
+    return 0;
+  }
+  return voprf_read(&voprf_pst1_method, key, out_nonce, token, token_len,
+                    include_message, msg, msg_len);
+}
diff --git a/include/openssl/trust_token.h b/include/openssl/trust_token.h
index 03ce4b8..b6aa6b3 100644
--- a/include/openssl/trust_token.h
+++ b/include/openssl/trust_token.h
@@ -48,6 +48,14 @@
 // PMBTokens and P-384 with up to 3 keys, without RR verification.
 OPENSSL_EXPORT const TRUST_TOKEN_METHOD *TRUST_TOKEN_experiment_v2_pmb(void);
 
+// TRUST_TOKEN_pst_v1_voprf is an experimental Trust Tokens protocol
+// using VOPRFs and P-384 with up to 6 keys, without RR verification.
+OPENSSL_EXPORT const TRUST_TOKEN_METHOD *TRUST_TOKEN_pst_v1_voprf(void);
+
+// TRUST_TOKEN_pst_v1_pmb is an experimental Trust Tokens protocol using
+// PMBTokens and P-384 with up to 3 keys, without RR verification.
+OPENSSL_EXPORT const TRUST_TOKEN_METHOD *TRUST_TOKEN_pst_v1_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.