Trust Token Implementation.

Trust Token implementation based on PrivacyPass using the PMBToken construction.
This implementation currently omits the DLEQ proofs.

https://github.com/alxdavids/privacy-pass-ietf/blob/master/draft-davidson-pp-protocol.md
https://eprint.iacr.org/2020/072.pdf

Change-Id: If236cc8beaf33a80bdad2991c3163f9dd0cb7571
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/39244
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 f4dd08f..1d35b97 100644
--- a/crypto/CMakeLists.txt
+++ b/crypto/CMakeLists.txt
@@ -336,7 +336,8 @@
   thread_none.c
   thread_pthread.c
   thread_win.c
-  trust_token/privacy_pass.c
+  trust_token/pmbtoken.c
+  trust_token/trust_token.c
   x509/a_digest.c
   x509/a_sign.c
   x509/a_strex.c
diff --git a/crypto/err/trust_token.errordata b/crypto/err/trust_token.errordata
index 9f0b60c..d7d9946 100644
--- a/crypto/err/trust_token.errordata
+++ b/crypto/err/trust_token.errordata
@@ -1,2 +1,14 @@
+TRUST_TOKEN,111,BAD_VALIDITY_CHECK
 TRUST_TOKEN,101,BUFFER_TOO_SMALL
+TRUST_TOKEN,103,DECODE_ERROR
+TRUST_TOKEN,105,DECODE_FAILURE
+TRUST_TOKEN,109,INVALID_KEY_ID
+TRUST_TOKEN,106,INVALID_METADATA
+TRUST_TOKEN,113,INVALID_METADATA_KEY
+TRUST_TOKEN,110,INVALID_TOKEN
 TRUST_TOKEN,100,KEYGEN_FAILURE
+TRUST_TOKEN,108,NO_KEYS_CONFIGURED
+TRUST_TOKEN,112,NO_SRR_KEY_CONFIGURED
+TRUST_TOKEN,102,OVER_BATCHSIZE
+TRUST_TOKEN,104,SRR_SIGNATURE_ERROR
+TRUST_TOKEN,107,TOO_MANY_KEYS
diff --git a/crypto/fipsmodule/ec/internal.h b/crypto/fipsmodule/ec/internal.h
index 9778efb..ebae837 100644
--- a/crypto/fipsmodule/ec/internal.h
+++ b/crypto/fipsmodule/ec/internal.h
@@ -172,6 +172,11 @@
 int ec_scalar_to_montgomery_inv_vartime(const EC_GROUP *group, EC_SCALAR *r,
                                         const EC_SCALAR *a);
 
+// ec_scalar_select, in constant time, sets |out| to |a| if |mask| is all ones
+// and |b| if |mask| is all zeros.
+void ec_scalar_select(const EC_GROUP *group, EC_SCALAR *out, BN_ULONG mask,
+                      const EC_SCALAR *a, const EC_SCALAR *b);
+
 
 // Field elements.
 
diff --git a/crypto/fipsmodule/ec/scalar.c b/crypto/fipsmodule/ec/scalar.c
index 2d49682..038e5ff 100644
--- a/crypto/fipsmodule/ec/scalar.c
+++ b/crypto/fipsmodule/ec/scalar.c
@@ -98,6 +98,12 @@
   OPENSSL_cleanse(tmp, sizeof(tmp));
 }
 
+void ec_scalar_select(const EC_GROUP *group, EC_SCALAR *out, BN_ULONG mask,
+                      const EC_SCALAR *a, const EC_SCALAR *b) {
+  const BIGNUM *order = &group->order;
+  bn_select_words(out->words, mask, a->words, b->words, order->width);
+}
+
 void ec_scalar_to_montgomery(const EC_GROUP *group, EC_SCALAR *r,
                              const EC_SCALAR *a) {
   const BIGNUM *order = &group->order;
diff --git a/crypto/trust_token/internal.h b/crypto/trust_token/internal.h
new file mode 100644
index 0000000..92be6ee
--- /dev/null
+++ b/crypto/trust_token/internal.h
@@ -0,0 +1,153 @@
+/* Copyright (c) 2019, 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. */
+
+#ifndef OPENSSL_HEADER_TRUST_TOKEN_INTERNAL_H
+#define OPENSSL_HEADER_TRUST_TOKEN_INTERNAL_H
+
+#include <openssl/base.h>
+#include <openssl/ec.h>
+#include <openssl/ec_key.h>
+#include <openssl/nid.h>
+
+#include "../fipsmodule/ec/internal.h"
+
+#include <openssl/trust_token.h>
+
+
+// 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, using P-521 as the group.
+
+// PMBTOKEN_NONCE_SIZE is the size of nonces used as part of the PMBToken
+// protocol.
+#define PMBTOKEN_NONCE_SIZE 64
+
+// PMBTOKEN_PRETOKEN represents the intermediate state a client keeps during a
+// PMBToken issuance operation.
+typedef struct pmb_pretoken_st {
+  uint8_t t[PMBTOKEN_NONCE_SIZE];
+  EC_SCALAR r;
+  EC_RAW_POINT T;
+  EC_RAW_POINT Tp;
+} PMBTOKEN_PRETOKEN;
+
+// PMBTOKEN_PRETOKEN_free releases the memory associated with |token|.
+void PMBTOKEN_PRETOKEN_free(PMBTOKEN_PRETOKEN *token);
+
+DEFINE_STACK_OF(PMBTOKEN_PRETOKEN)
+
+// PMBTOKEN_TOKEN represents the final token generated as part of a PMBToken
+// issuance operation.
+typedef struct pmb_token_st {
+  uint8_t t[PMBTOKEN_NONCE_SIZE];
+  EC_RAW_POINT S;
+  EC_RAW_POINT W;
+  EC_RAW_POINT Ws;
+} PMBTOKEN_TOKEN;
+
+// PMBTOKEN_TOKEN_free releases the memory associated with |token|.
+void PMBTOKEN_TOKEN_free(PMBTOKEN_TOKEN *token);
+
+// pmbtoken_blind generates a new blinded pretoken based on the configuration of
+// |ctx| as per the first stage of the AT.Usr operation and returns the
+// resulting pretoken.
+PMBTOKEN_PRETOKEN *pmbtoken_blind(void);
+
+// pmbtoken_sign signs a blinded point based on the configuration of |ctx|
+// and the key specified by |key_id| with a private metadata value of
+// |private_metadata| as per the AT.Sig operation and stores the resulting nonce
+// and points in |*out_s|, |*out_Wp|, and |*out_Wsp|. It returns one on success
+// and zero on failure.
+int pmbtoken_sign(const TRUST_TOKEN_ISSUER *ctx,
+                  uint8_t out_s[PMBTOKEN_NONCE_SIZE], EC_RAW_POINT *out_Wp,
+                  EC_RAW_POINT *out_Wsp, const EC_RAW_POINT *Tp,
+                  uint32_t key_id, uint8_t private_metadata);
+
+// pmbtoken_unblind unblinds the result of an AT.Sig operation as per the final
+// stage of the AT.Usr operation and sets |*out_token| to the resulting token.
+// It returns one on success and zero on failure.
+int pmbtoken_unblind(PMBTOKEN_TOKEN *out_token,
+                     const uint8_t s[PMBTOKEN_NONCE_SIZE],
+                     const EC_RAW_POINT *Wp, const EC_RAW_POINT *Wsp,
+                     const PMBTOKEN_PRETOKEN *pretoken);
+
+// pmbtoken_read verifies the validity of a PMBToken |token| using the key
+// specified by |key_id| and stores the value of the private metadata
+// bit in |*out_private_metadata|. It returns one if the token is valid and zero
+// otherwise.
+int pmbtoken_read(const TRUST_TOKEN_ISSUER *ctx, uint8_t *out_private_metadata,
+                  const PMBTOKEN_TOKEN *token, uint32_t key_id);
+
+// Structure representing a single Trust Token public key with the specified ID.
+struct trust_token_client_key_st {
+  uint32_t id;
+  EC_RAW_POINT pub0;
+  EC_RAW_POINT pub1;
+  EC_RAW_POINT pubs;
+};
+
+// Structure representing a single Trust Token private key with the specified
+// ID.
+struct trust_token_issuer_key_st {
+  uint32_t id;
+  EC_SCALAR x0;
+  EC_SCALAR y0;
+  EC_SCALAR x1;
+  EC_SCALAR y1;
+  EC_SCALAR xs;
+  EC_SCALAR ys;
+};
+
+struct trust_token_client_st {
+  // max_batchsize is the maximum supported batchsize.
+  uint16_t max_batchsize;
+
+  // keys is the set of public keys that are supported by the client for
+  // issuance/redemptions.
+  struct trust_token_client_key_st keys[3];
+
+  // num_keys is the number of keys currently configured.
+  size_t num_keys;
+
+  // pretokens is the intermediate state during an active issuance.
+  STACK_OF(PMBTOKEN_PRETOKEN)* pretokens;
+
+  // srr_key is the public key used to verify the signature of the SRR.
+  EVP_PKEY *srr_key;
+};
+
+
+struct trust_token_issuer_st {
+  // max_batchsize is the maximum supported batchsize.
+  uint16_t max_batchsize;
+
+  // keys is the set of private keys that are supported by the issuer for
+  // issuance/redemptions. The public metadata is an index into this list of
+  // keys.
+  struct trust_token_issuer_key_st keys[3];
+
+  // num_keys is the number of keys currently configured.
+  size_t num_keys;
+
+  // srr_key is the private key used to sign the SRR.
+  EVP_PKEY *srr_key;
+
+  // metadata_key is the secret material used to encode the private metadata bit
+  // in the SRR.
+  uint8_t *metadata_key;
+  size_t metadata_key_len;
+};
+
+#endif  // OPENSSL_HEADER_TRUST_TOKEN_INTERNAL_H
diff --git a/crypto/trust_token/pmbtoken.c b/crypto/trust_token/pmbtoken.c
new file mode 100644
index 0000000..a7a87c8
--- /dev/null
+++ b/crypto/trust_token/pmbtoken.c
@@ -0,0 +1,444 @@
+/* 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/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 <openssl/sha.h>
+#include <openssl/trust_token.h>
+
+#include "../ec_extra/internal.h"
+#include "../fipsmodule/bn/internal.h"
+#include "../fipsmodule/ec/internal.h"
+
+#include "internal.h"
+
+
+// get_h returns the generator H for PMBTokens.
+//
+// x: 66591746412783875033873351891229753622964683369847172829242944646280287810
+//    81195403447871073952234683395256591180452378091073292247502091640572714366
+//    588045092
+// y: 12347430519393087872533727997980072129796839266949808299436682045034861065
+//    18810630511924722292325611253427311923464047364545304196431830383014967865
+//    162306253
+//
+// This point was generated with the following Python code.
+
+/*
+import hashlib
+
+SEED_H = 'PrivacyPass H'
+
+A = -3
+B = 0x051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00
+P = 2**521 - 1
+
+def get_y(x):
+  y2 = (x**3 + A*x + B) % P
+  y = pow(y2, (P+1)/4, P)
+  if (y*y) % P != y2:
+    raise ValueError("point not on curve")
+  return y
+
+def bit(h,i):
+  return (ord(h[i/8]) >> (i%8)) & 1
+
+b = 521
+def decode_point(so):
+  s = hashlib.sha256(so + '0').digest() + hashlib.sha256(so + '1').digest() + \
+      hashlib.sha256(so + '2').digest()
+
+  x = 0
+  for i in range(0,b):
+    x = x + (long(bit(s,i))<<i)
+  if x >= P:
+    raise ValueError("x out of range")
+  y = get_y(x)
+  if y & 1 != bit(s,b-1): y = P-y
+  return (x, y)
+
+
+def gen_point(seed):
+  v = hashlib.sha256(seed).digest()
+  it = 1
+  while True:
+    try:
+      x,y = decode_point(v)
+    except Exception, e:
+      print e
+      it += 1
+      v = hashlib.sha256(v).digest()
+      continue
+    print "Found in %d iterations:" % it
+    print "  x = %d" % x
+    print "  y = %d" % y
+    print " Encoded (hex): (%x, %x)" % (x, y)
+    return (x, y)
+
+if __name__ == "__main__":
+  gen_point(SEED_H)
+*/
+
+static const uint8_t kDefaultAdditionalData[32] = {0};
+
+// TODO(svaldez): Update to use hash2curve to generate H.
+static int get_h(EC_RAW_POINT *out_h) {
+  EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp521r1);
+  if (group == NULL) {
+    return 0;
+  }
+
+  static const uint8_t kH[] = {
+      0x04, 0x01, 0xf0, 0xa9, 0xf7, 0x9e, 0xbc, 0x12, 0x6c, 0xef, 0xd1, 0xab,
+      0x29, 0x10, 0x03, 0x6f, 0x4e, 0xf5, 0xbd, 0xeb, 0x0f, 0x6b, 0xc0, 0x5c,
+      0x0e, 0xce, 0xfe, 0x59, 0x45, 0xd1, 0x3e, 0x25, 0x33, 0x7e, 0x4c, 0xda,
+      0x64, 0x53, 0x54, 0x4e, 0xf9, 0x76, 0x0d, 0x6d, 0xc5, 0x39, 0x2a, 0xd4,
+      0xce, 0x84, 0x6e, 0x31, 0xc2, 0x86, 0x21, 0xf9, 0x5c, 0x98, 0xb9, 0x3d,
+      0x01, 0x74, 0x9f, 0xc5, 0x1e, 0x47, 0x24, 0x00, 0x5c, 0x17, 0x62, 0x51,
+      0x7d, 0x32, 0x5e, 0x29, 0xac, 0x52, 0x14, 0x75, 0x6f, 0x36, 0xd9, 0xc7,
+      0xfa, 0xbb, 0xa9, 0x3b, 0x9d, 0x70, 0x49, 0x1e, 0xb4, 0x53, 0xbc, 0x55,
+      0xea, 0xad, 0x8f, 0x26, 0x1d, 0xe0, 0xbc, 0xf3, 0x50, 0x5c, 0x7e, 0x66,
+      0x41, 0xb5, 0x61, 0x70, 0x12, 0x72, 0xac, 0x6a, 0xb0, 0x6e, 0x78, 0x3d,
+      0x17, 0x08, 0xe3, 0xdf, 0x3c, 0xff, 0xa6, 0xa0, 0xea, 0x96, 0x67, 0x92,
+      0xcd,
+  };
+
+  return ec_point_from_uncompressed(group, out_h, kH, sizeof(kH));
+}
+
+static int mul_g_and_p(const EC_GROUP *group, EC_RAW_POINT *out,
+                       const EC_RAW_POINT *g, const EC_SCALAR *g_scalar,
+                       const EC_RAW_POINT *p, const EC_SCALAR *p_scalar) {
+  EC_RAW_POINT tmp1, tmp2;
+  if (!ec_point_mul_scalar(group, &tmp1, g, g_scalar) ||
+      !ec_point_mul_scalar(group, &tmp2, p, p_scalar)) {
+    return 0;
+  }
+
+  group->meth->add(group, out, &tmp1, &tmp2);
+  return 1;
+}
+
+// generate_keypair generates a keypair for the PMBTokens construction.
+// |out_x| and |out_y| are set to the secret half of the keypair, while
+// |*out_pub| is set to the public half of the keypair. It returns one on
+// success and zero on failure.
+static int generate_keypair(EC_SCALAR *out_x, EC_SCALAR *out_y,
+                            EC_RAW_POINT *out_pub, const EC_GROUP *group) {
+  EC_RAW_POINT h, tmp1, tmp2;
+  if (!get_h(&h) ||
+      !ec_random_nonzero_scalar(group, out_x, kDefaultAdditionalData) ||
+      !ec_random_nonzero_scalar(group, out_y, kDefaultAdditionalData) ||
+      !ec_point_mul_scalar_base(group, &tmp1, out_x) ||
+      !ec_point_mul_scalar(group, &tmp2, &h, out_y)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    return 0;
+  }
+  group->meth->add(group, out_pub, &tmp1, &tmp2);
+  return 1;
+}
+
+static int point_to_cbb(CBB *out, const EC_GROUP *group,
+                        const EC_RAW_POINT *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;
+}
+
+int TRUST_TOKEN_generate_key(uint8_t *out_priv_key, size_t *out_priv_key_len,
+                             size_t max_priv_key_len, uint8_t *out_pub_key,
+                             size_t *out_pub_key_len, size_t max_pub_key_len,
+                             uint32_t id) {
+  EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp521r1);
+  if (group == NULL) {
+    return 0;
+  }
+
+  EC_RAW_POINT pub0, pub1, pubs;
+  EC_SCALAR x0, y0, x1, y1, xs, ys;
+  if (!generate_keypair(&x0, &y0, &pub0, group) ||
+      !generate_keypair(&x1, &y1, &pub1, group) ||
+      !generate_keypair(&xs, &ys, &pubs, group)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_KEYGEN_FAILURE);
+    return 0;
+  }
+
+  int ret = 0;
+  CBB cbb;
+  CBB_zero(&cbb);
+  size_t scalar_len = BN_num_bytes(&group->order);
+  if (!CBB_init_fixed(&cbb, out_priv_key, max_priv_key_len) ||
+      !CBB_add_u32(&cbb, id)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_BUFFER_TOO_SMALL);
+    goto err;
+  }
+
+  const EC_SCALAR *scalars[] = {&x0, &y0, &x1, &y1, &xs, &ys};
+  for (size_t i = 0; i < OPENSSL_ARRAY_SIZE(scalars); i++) {
+    uint8_t *buf;
+    if (!CBB_add_space(&cbb, &buf, scalar_len)) {
+      OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_BUFFER_TOO_SMALL);
+      goto err;
+    }
+    ec_scalar_to_bytes(group, buf, &scalar_len, scalars[i]);
+  }
+
+  if (!CBB_finish(&cbb, NULL, out_priv_key_len)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_BUFFER_TOO_SMALL);
+    goto err;
+  }
+
+  CBB pub_cbb;
+  if (!CBB_init_fixed(&cbb, out_pub_key, max_pub_key_len) ||
+      !CBB_add_u32(&cbb, id) ||
+      !CBB_add_u16_length_prefixed(&cbb, &pub_cbb) ||
+      !point_to_cbb(&pub_cbb, group, &pub0) ||
+      !CBB_add_u16_length_prefixed(&cbb, &pub_cbb) ||
+      !point_to_cbb(&pub_cbb, group, &pub1) ||
+      !CBB_add_u16_length_prefixed(&cbb, &pub_cbb) ||
+      !point_to_cbb(&pub_cbb, group, &pubs) ||
+      !CBB_finish(&cbb, NULL, out_pub_key_len)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_BUFFER_TOO_SMALL);
+    goto err;
+  }
+
+  ret = 1;
+
+err:
+  CBB_cleanup(&cbb);
+  return ret;
+}
+
+void PMBTOKEN_PRETOKEN_free(PMBTOKEN_PRETOKEN *pretoken) {
+  OPENSSL_free(pretoken);
+}
+
+void PMBTOKEN_TOKEN_free(PMBTOKEN_TOKEN *token) {
+  OPENSSL_free(token);
+}
+
+// hash_t implements the H_t operation in PMBTokens. It returns on on success
+// and zero on error.
+static int hash_t(EC_GROUP *group, EC_RAW_POINT *out,
+                  const uint8_t t[PMBTOKEN_NONCE_SIZE]) {
+  const uint8_t kHashTLabel[] = "PMBTokensV0 HashT";
+  return ec_hash_to_curve_p521_xmd_sha512_sswu(
+      group, out, kHashTLabel, sizeof(kHashTLabel), t, PMBTOKEN_NONCE_SIZE);
+}
+
+// hash_s implements the H_s operation in PMBTokens. It returns on on success
+// and zero on error.
+static int hash_s(EC_GROUP *group, EC_RAW_POINT *out, const EC_RAW_POINT *t,
+                  const uint8_t s[PMBTOKEN_NONCE_SIZE]) {
+  const uint8_t kHashSLabel[] = "PMBTokensV0 HashS";
+  int ret = 0;
+  CBB cbb;
+  uint8_t *buf = NULL;
+  size_t len;
+  if (!CBB_init(&cbb, 0) ||
+      !point_to_cbb(&cbb, group, t) ||
+      !CBB_add_bytes(&cbb, s, PMBTOKEN_NONCE_SIZE) ||
+      !CBB_finish(&cbb, &buf, &len) ||
+      !ec_hash_to_curve_p521_xmd_sha512_sswu(group, out, kHashSLabel,
+                                             sizeof(kHashSLabel), buf, len)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    goto err;
+  }
+
+  ret = 1;
+
+err:
+  OPENSSL_free(buf);
+  CBB_cleanup(&cbb);
+  return ret;
+}
+
+PMBTOKEN_PRETOKEN *pmbtoken_blind(void) {
+  EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp521r1);
+  if (group == NULL) {
+    return NULL;
+  }
+
+  PMBTOKEN_PRETOKEN *pretoken = OPENSSL_malloc(sizeof(PMBTOKEN_PRETOKEN));
+  if (pretoken == NULL) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    goto err;
+  }
+
+  RAND_bytes(pretoken->t, sizeof(pretoken->t));
+
+  // We sample |pretoken->r| in Montgomery form to simplify inverting.
+  if (!ec_random_nonzero_scalar(group, &pretoken->r,
+                                kDefaultAdditionalData)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    goto err;
+  }
+
+  EC_SCALAR rinv;
+  ec_scalar_inv0_montgomery(group, &rinv, &pretoken->r);
+  // Convert both out of Montgomery form.
+  ec_scalar_from_montgomery(group, &pretoken->r, &pretoken->r);
+  ec_scalar_from_montgomery(group, &rinv, &rinv);
+
+  if (!hash_t(group, &pretoken->T, pretoken->t) ||
+      !ec_point_mul_scalar(group, &pretoken->Tp, &pretoken->T, &rinv)) {
+    goto err;
+  }
+
+  return pretoken;
+
+err:
+  OPENSSL_free(pretoken);
+  return NULL;
+}
+
+int pmbtoken_sign(const TRUST_TOKEN_ISSUER *ctx,
+                  uint8_t out_s[PMBTOKEN_NONCE_SIZE], EC_RAW_POINT *out_Wp,
+                  EC_RAW_POINT *out_Wsp, const EC_RAW_POINT *Tp,
+                  uint32_t key_id, uint8_t private_metadata) {
+  EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp521r1);
+  if (group == NULL) {
+    return 0;
+  }
+
+  if (ctx->num_keys == 0) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_NO_KEYS_CONFIGURED);
+    return 0;
+  }
+  const struct trust_token_issuer_key_st *key = NULL;
+  for (size_t i = 0; i < ctx->num_keys; i++) {
+    if (ctx->keys[i].id == key_id) {
+      key = &ctx->keys[i];
+      break;
+    }
+  }
+
+  if (key == NULL) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_INVALID_KEY_ID);
+    return 0;
+  }
+
+  EC_SCALAR xb, yb;
+  BN_ULONG mask = ((BN_ULONG)0) - (private_metadata&1);
+  ec_scalar_select(group, &xb, mask, &key->x1, &key->x0);
+  ec_scalar_select(group, &yb, mask, &key->y1, &key->y0);
+
+  RAND_bytes(out_s, PMBTOKEN_NONCE_SIZE);
+
+  EC_RAW_POINT Sp;
+  if (!hash_s(group, &Sp, Tp, out_s)) {
+    return 0;
+  }
+
+  if (!mul_g_and_p(group, out_Wp, Tp, &xb, &Sp, &yb) ||
+      !mul_g_and_p(group, out_Wsp, Tp, &key->xs, &Sp, &key->ys)) {
+    return 0;
+  }
+
+  // TODO: DLEQ Proofs
+  return 1;
+}
+
+int pmbtoken_unblind(PMBTOKEN_TOKEN *out_token,
+                     const uint8_t s[PMBTOKEN_NONCE_SIZE],
+                     const EC_RAW_POINT *Wp, const EC_RAW_POINT *Wsp,
+                     const PMBTOKEN_PRETOKEN *pretoken) {
+  EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp521r1);
+  if (group == NULL) {
+    return 0;
+  }
+
+  // TODO: Check DLEQ Proofs
+
+  EC_RAW_POINT Sp;
+  if (!hash_s(group, &Sp, &pretoken->Tp, s)) {
+    return 0;
+  }
+
+  OPENSSL_memcpy(out_token->t, pretoken->t, PMBTOKEN_NONCE_SIZE);
+  if (!ec_point_mul_scalar(group, &out_token->S, &Sp, &pretoken->r) ||
+      !ec_point_mul_scalar(group, &out_token->W, Wp, &pretoken->r) ||
+      !ec_point_mul_scalar(group, &out_token->Ws, Wsp, &pretoken->r)) {
+    return 0;
+  }
+
+  return 1;
+}
+
+int pmbtoken_read(const TRUST_TOKEN_ISSUER *ctx, uint8_t *out_private_metadata,
+                  const PMBTOKEN_TOKEN *token, uint32_t key_id) {
+  EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp521r1);
+  if (group == NULL) {
+    return 0;
+  }
+
+  if (ctx->num_keys == 0) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_NO_KEYS_CONFIGURED);
+    return 0;
+  }
+  const struct trust_token_issuer_key_st *key = NULL;
+  for (size_t i = 0; i < ctx->num_keys; i++) {
+    if (ctx->keys[i].id == key_id) {
+      key = &ctx->keys[i];
+      break;
+    }
+  }
+
+  if (key == NULL) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_INVALID_KEY_ID);
+    return 0;
+  }
+
+  EC_RAW_POINT T;
+  if (!hash_t(group, &T, token->t)) {
+    return 0;
+  }
+
+  EC_RAW_POINT calculated;
+  // Check the validity of the token.
+  if (!mul_g_and_p(group, &calculated, &T, &key->xs, &token->S, &key->ys) ||
+      !ec_GFp_simple_points_equal(group, &calculated, &token->Ws)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_BAD_VALIDITY_CHECK);
+    return 0;
+  }
+
+  EC_RAW_POINT W0, W1;
+  if (!mul_g_and_p(group, &W0, &T, &key->x0, &token->S, &key->y0) ||
+      !mul_g_and_p(group, &W1, &T, &key->x1, &token->S, &key->y1)) {
+    return 0;
+  }
+
+  const int is_W0 = ec_GFp_simple_points_equal(group, &W0, &token->W);
+  const int is_W1 = ec_GFp_simple_points_equal(group, &W1, &token->W);
+  const int is_valid = is_W0 ^ is_W1;
+  if (!is_valid) {
+    // Invalid tokens will fail the validity check above.
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_INTERNAL_ERROR);
+    return 0;
+  }
+
+  *out_private_metadata = is_W1;
+  return 1;
+}
diff --git a/crypto/trust_token/privacy_pass.c b/crypto/trust_token/privacy_pass.c
deleted file mode 100644
index accb28a..0000000
--- a/crypto/trust_token/privacy_pass.c
+++ /dev/null
@@ -1,241 +0,0 @@
-/* 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/bn.h>
-#include <openssl/bytestring.h>
-#include <openssl/ec.h>
-#include <openssl/err.h>
-#include <openssl/mem.h>
-#include <openssl/nid.h>
-#include <openssl/trust_token.h>
-
-#include "../fipsmodule/bn/internal.h"
-#include "../fipsmodule/ec/internal.h"
-
-
-// Privacy Pass uses a custom elliptic curve construction described in
-// https://eprint.iacr.org/2020/072.pdf (section 7, construction 4). Ths
-// construction provides anonymous tokens with private metadata and validity
-// verification.
-
-// get_h returns a randomly selected point for the Privacy Pass protocol.
-//
-// x: 66591746412783875033873351891229753622964683369847172829242944646280287810
-//    81195403447871073952234683395256591180452378091073292247502091640572714366
-//    588045092
-// y: 12347430519393087872533727997980072129796839266949808299436682045034861065
-//    18810630511924722292325611253427311923464047364545304196431830383014967865
-//    162306253
-//
-// This point was generated with the following Python code.
-
-/*
-import hashlib
-
-SEED_H = 'PrivacyPass H'
-
-A = -3
-B = 0x051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00
-P = 2**521 - 1
-
-def get_y(x):
-  y2 = (x**3 + A*x + B) % P
-  y = pow(y2, (P+1)/4, P)
-  if (y*y) % P != y2:
-    raise ValueError("point not on curve")
-  return y
-
-def bit(h,i):
-  return (ord(h[i/8]) >> (i%8)) & 1
-
-b = 521
-def decode_point(so):
-  s = hashlib.sha256(so + '0').digest() + hashlib.sha256(so + '1').digest() + \
-      hashlib.sha256(so + '2').digest()
-
-  x = 0
-  for i in range(0,b):
-    x = x + (long(bit(s,i))<<i)
-  if x >= P:
-    raise ValueError("x out of range")
-  y = get_y(x)
-  if y & 1 != bit(s,b-1): y = P-y
-  return (x, y)
-
-
-def gen_point(seed):
-  v = hashlib.sha256(seed).digest()
-  it = 1
-  while True:
-    try:
-      x,y = decode_point(v)
-    except Exception, e:
-      print e
-      it += 1
-      v = hashlib.sha256(v).digest()
-      continue
-    print "Found in %d iterations:" % it
-    print "  x = %d" % x
-    print "  y = %d" % y
-    print " Encoded (hex): (%x, %x)" % (x, y)
-    return (x, y)
-
-if __name__ == "__main__":
-  gen_point(SEED_H)
-*/
-
-static EC_POINT *get_h(void) {
-  EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp521r1);
-  if (group == NULL) {
-    return NULL;
-  }
-
-  static const BN_ULONG kHGenX[] = {
-      TOBN(0x3d01749f, 0xc51e4724),
-      TOBN(0x31c28621, 0xf95c98b9),
-      TOBN(0x6dc5392a, 0xd4ce846e),
-      TOBN(0xda645354, 0x4ef9760d),
-      TOBN(0x5945d13e, 0x25337e4c),
-      TOBN(0xeb0f6bc0, 0x5c0ecefe),
-      TOBN(0xab291003, 0x6f4ef5bd),
-      TOBN(0xa9f79ebc, 0x126cefd1),
-      0x000001f0,
-  };
-  static const BIGNUM kX = STATIC_BIGNUM(kHGenX);
-
-  static const BN_ULONG kHGenY[] = {
-      TOBN(0xffa6a0ea, 0x966792cd),
-      TOBN(0x6e783d17, 0x08e3df3c),
-      TOBN(0xb5617012, 0x72ac6ab0),
-      TOBN(0xe0bcf350, 0x5c7e6641),
-      TOBN(0x53bc55ea, 0xad8f261d),
-      TOBN(0xbba93b9d, 0x70491eb4),
-      TOBN(0x5214756f, 0x36d9c7fa),
-      TOBN(0x1762517d, 0x325e29ac),
-      0x0000005c,
-  };
-  static const BIGNUM kY = STATIC_BIGNUM(kHGenY);
-
-  EC_POINT *h = EC_POINT_new(group);
-  if (h == NULL ||
-      !EC_POINT_set_affine_coordinates_GFp(group, h, &kX, &kY, NULL)) {
-    EC_POINT_free(h);
-    return NULL;
-  }
-  return h;
-}
-
-// generate_keypair generates a keypair for the Private Metadata construction.
-// |out_x| and |out_y| are set to the secret half of the keypair, while
-// |*out_pub| is set to the public half of the keypair. It returns one on
-// success and zero on failure.
-static int generate_keypair(EC_SCALAR *out_x, EC_SCALAR *out_y,
-                            EC_RAW_POINT *out_pub, const EC_GROUP *group) {
-  EC_POINT *h = get_h();
-  if (h == NULL) {
-    return 0;
-  }
-
-  static const uint8_t kDefaultAdditionalData[32] = {0};
-  EC_RAW_POINT tmp1, tmp2;
-  if (!ec_random_nonzero_scalar(group, out_x, kDefaultAdditionalData) ||
-      !ec_random_nonzero_scalar(group, out_y, kDefaultAdditionalData) ||
-      !ec_point_mul_scalar_base(group, &tmp1, out_x) ||
-      !ec_point_mul_scalar(group, &tmp2, &h->raw, out_y)) {
-    EC_POINT_free(h);
-    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
-    return 0;
-  }
-  group->meth->add(group, out_pub, &tmp1, &tmp2);
-
-  EC_POINT_free(h);
-  return 1;
-}
-
-static int point_to_cbb(CBB *out, const EC_GROUP *group,
-                        const EC_RAW_POINT *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;
-}
-
-int TRUST_TOKEN_generate_key(uint8_t *out_priv_key, size_t *out_priv_key_len,
-                             size_t max_priv_key_len, uint8_t *out_pub_key,
-                             size_t *out_pub_key_len, size_t max_pub_key_len,
-                             uint32_t id) {
-  int ok = 0;
-  CBB cbb;
-  CBB_zero(&cbb);
-  EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp521r1);
-  if (group == NULL) {
-    return 0;
-  }
-
-  EC_RAW_POINT pub0, pub1, pubs;
-  EC_SCALAR x0, y0, x1, y1, xs, ys;
-  if (!generate_keypair(&x0, &y0, &pub0, group) ||
-      !generate_keypair(&x1, &y1, &pub1, group) ||
-      !generate_keypair(&xs, &ys, &pubs, group)) {
-    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_KEYGEN_FAILURE);
-    goto err;
-  }
-
-  size_t scalar_len = BN_num_bytes(&group->order);
-  if (!CBB_init_fixed(&cbb, out_priv_key, max_priv_key_len) ||
-      !CBB_add_u32(&cbb, id)) {
-    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_BUFFER_TOO_SMALL);
-    goto err;
-  }
-
-  const EC_SCALAR *scalars[] = {&x0, &y0, &x1, &y1, &xs, &ys};
-  for (size_t i = 0; i < OPENSSL_ARRAY_SIZE(scalars); i++) {
-    uint8_t *buf;
-    if (!CBB_add_space(&cbb, &buf, scalar_len)) {
-      OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_BUFFER_TOO_SMALL);
-      goto err;
-    }
-    ec_scalar_to_bytes(group, buf, &scalar_len, scalars[i]);
-  }
-
-  if (!CBB_finish(&cbb, NULL, out_priv_key_len)) {
-    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_BUFFER_TOO_SMALL);
-    goto err;
-  }
-
-  CBB pub_cbb;
-  if (!CBB_init_fixed(&cbb, out_pub_key, max_pub_key_len) ||
-      !CBB_add_u32(&cbb, id) ||
-      !CBB_add_u16_length_prefixed(&cbb, &pub_cbb) ||
-      !point_to_cbb(&pub_cbb, group, &pub0) ||
-      !CBB_add_u16_length_prefixed(&cbb, &pub_cbb) ||
-      !point_to_cbb(&pub_cbb, group, &pub1) ||
-      !CBB_add_u16_length_prefixed(&cbb, &pub_cbb) ||
-      !point_to_cbb(&pub_cbb, group, &pubs) ||
-      !CBB_finish(&cbb, NULL, out_pub_key_len)) {
-    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_BUFFER_TOO_SMALL);
-    goto err;
-  }
-
-  ok = 1;
-
-err:
-  CBB_cleanup(&cbb);
-  return ok;
-}
diff --git a/crypto/trust_token/trust_token.c b/crypto/trust_token/trust_token.c
new file mode 100644
index 0000000..186fdbc
--- /dev/null
+++ b/crypto/trust_token/trust_token.c
@@ -0,0 +1,720 @@
+/* Copyright (c) 2019, 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/bytestring.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/mem.h>
+#include <openssl/sha.h>
+#include <openssl/trust_token.h>
+
+#include "internal.h"
+
+
+// The Trust Token API is described in
+// https://github.com/WICG/trust-token-api/blob/master/README.md and provides a
+// protocol for issuing and redeeming tokens built on top of the PMBTokens
+// construction.
+
+static int cbb_add_raw_point(CBB *cbb, const EC_GROUP *group,
+                             const EC_RAW_POINT *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_u16(cbb, len) && CBB_add_space(cbb, &p, len) &&
+         ec_point_to_bytes(group, point, POINT_CONVERSION_UNCOMPRESSED, p,
+                           len) == len;
+}
+
+static int cbs_get_raw_point(CBS *cbs, const EC_GROUP *group,
+                             EC_RAW_POINT *out) {
+  CBS child;
+  return CBS_get_u16_length_prefixed(cbs, &child) &&
+         ec_point_from_uncompressed(group, out, CBS_data(&child),
+                                    CBS_len(&child));
+}
+
+TRUST_TOKEN *TRUST_TOKEN_new(const uint8_t *data, size_t len) {
+  TRUST_TOKEN *ret = OPENSSL_malloc(sizeof(TRUST_TOKEN));
+  if (ret == NULL) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    return NULL;
+  }
+  OPENSSL_memset(ret, 0, sizeof(TRUST_TOKEN));
+  ret->data = OPENSSL_memdup(data, len);
+  if (len != 0 && ret->data == NULL) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    OPENSSL_free(ret);
+    return NULL;
+  }
+  ret->len = len;
+  return ret;
+}
+
+void TRUST_TOKEN_free(TRUST_TOKEN *token) {
+  if (token == NULL) {
+    return;
+  }
+  OPENSSL_free(token->data);
+  OPENSSL_free(token);
+}
+
+TRUST_TOKEN_CLIENT *TRUST_TOKEN_CLIENT_new(uint16_t max_batchsize) {
+  TRUST_TOKEN_CLIENT *ret = OPENSSL_malloc(sizeof(TRUST_TOKEN_CLIENT));
+  if (ret == NULL) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    return NULL;
+  }
+  OPENSSL_memset(ret, 0, sizeof(TRUST_TOKEN_CLIENT));
+  ret->max_batchsize = max_batchsize;
+  ret->pretokens = sk_PMBTOKEN_PRETOKEN_new_null();
+  if (ret->pretokens == NULL) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    OPENSSL_free(ret);
+    return NULL;
+  }
+  return ret;
+}
+
+void TRUST_TOKEN_CLIENT_free(TRUST_TOKEN_CLIENT *ctx) {
+  if (ctx == NULL) {
+    return;
+  }
+  EVP_PKEY_free(ctx->srr_key);
+  sk_PMBTOKEN_PRETOKEN_pop_free(ctx->pretokens, PMBTOKEN_PRETOKEN_free);
+  OPENSSL_free(ctx);
+}
+
+int TRUST_TOKEN_CLIENT_add_key(TRUST_TOKEN_CLIENT *ctx, size_t *out_key_index,
+                               const uint8_t *key, size_t key_len) {
+  EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp521r1);
+  if (group == NULL) {
+    return 0;
+  }
+
+  if (ctx->num_keys == OPENSSL_ARRAY_SIZE(ctx->keys)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_TOO_MANY_KEYS);
+    return 0;
+  }
+
+  struct trust_token_client_key_st *key_s = &ctx->keys[ctx->num_keys];
+
+  CBS cbs;
+  CBS_init(&cbs, key, key_len);
+  uint32_t key_id;
+  if (!CBS_get_u32(&cbs, &key_id) ||
+      !cbs_get_raw_point(&cbs, group, &key_s->pub0) ||
+      !cbs_get_raw_point(&cbs, group, &key_s->pub1) ||
+      !cbs_get_raw_point(&cbs, group, &key_s->pubs) ||
+      CBS_len(&cbs) != 0) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
+    return 0;
+  }
+  key_s->id = key_id;
+  *out_key_index = ctx->num_keys;
+  ctx->num_keys += 1;
+  return 1;
+}
+
+int TRUST_TOKEN_CLIENT_set_srr_key(TRUST_TOKEN_CLIENT *ctx, EVP_PKEY *key) {
+  EVP_PKEY_free(ctx->srr_key);
+  EVP_PKEY_up_ref(key);
+  ctx->srr_key = key;
+  return 1;
+}
+
+int TRUST_TOKEN_CLIENT_begin_issuance(TRUST_TOKEN_CLIENT *ctx, uint8_t **out,
+                                      size_t *out_len, size_t count) {
+  EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp521r1);
+  if (group == NULL) {
+    return 0;
+  }
+
+  if (count > ctx->max_batchsize) {
+    count = ctx->max_batchsize;
+  }
+
+  int ret = 0;
+  CBB request;
+  if (!CBB_init(&request, 0) ||
+      !CBB_add_u16(&request, count)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    goto err;
+  }
+
+  for (size_t i = 0; i < count; i++) {
+    PMBTOKEN_PRETOKEN *pretoken = pmbtoken_blind();
+    if (pretoken == NULL ||
+        !cbb_add_raw_point(&request, group, &pretoken->Tp) ||
+        !sk_PMBTOKEN_PRETOKEN_push(ctx->pretokens, pretoken)) {
+      PMBTOKEN_PRETOKEN_free(pretoken);
+      goto err;
+    }
+  }
+
+  if (!CBB_finish(&request, out, out_len)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    goto err;
+  }
+
+  ret = 1;
+
+err:
+  CBB_cleanup(&request);
+  return ret;
+}
+
+STACK_OF(TRUST_TOKEN) *
+    TRUST_TOKEN_CLIENT_finish_issuance(TRUST_TOKEN_CLIENT *ctx,
+                                       size_t *out_key_index,
+                                       const uint8_t *response,
+                                       size_t response_len) {
+  EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp521r1);
+  if (group == NULL) {
+    return 0;
+  }
+
+  CBS in;
+  CBS_init(&in, response, response_len);
+  uint16_t count;
+  uint32_t key_id;
+  if (!CBS_get_u16(&in, &count) ||
+      !CBS_get_u32(&in, &key_id)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
+    return NULL;
+  }
+
+  size_t key_index = 0;
+  const struct trust_token_client_key_st *key = NULL;
+  for (size_t i = 0; i < ctx->num_keys; i++) {
+    if (ctx->keys[i].id == key_id) {
+      key_index = i;
+      key = &ctx->keys[i];
+      break;
+    }
+  }
+
+  if (key == NULL) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_INVALID_KEY_ID);
+    return NULL;
+  }
+
+  if (count > sk_PMBTOKEN_PRETOKEN_num(ctx->pretokens)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
+    return NULL;
+  }
+
+  int ok = 0;
+  STACK_OF(TRUST_TOKEN) *tokens = sk_TRUST_TOKEN_new_null();
+  if (tokens == NULL) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    return NULL;
+  }
+
+  for (size_t i = 0; i < count; i++) {
+    uint8_t s[PMBTOKEN_NONCE_SIZE];
+    EC_RAW_POINT Wp, Wsp;
+    if (!CBS_copy_bytes(&in, s, PMBTOKEN_NONCE_SIZE) ||
+        !cbs_get_raw_point(&in, group, &Wp) ||
+        !cbs_get_raw_point(&in, group, &Wsp)) {
+      OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
+      goto err;
+    }
+
+    PMBTOKEN_PRETOKEN *pretoken = sk_PMBTOKEN_PRETOKEN_value(ctx->pretokens, i);
+    PMBTOKEN_TOKEN pmbtoken;
+    if (!pmbtoken_unblind(&pmbtoken, s, &Wp, &Wsp, pretoken)) {
+      OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
+      goto err;
+    }
+
+    int token_ok = 0;
+    TRUST_TOKEN *token = NULL;
+    CBB token_cbb;
+    if (!CBB_init(&token_cbb, 0) ||
+        !CBB_add_u32(&token_cbb, key_id) ||
+        !CBB_add_bytes(&token_cbb, pmbtoken.t, PMBTOKEN_NONCE_SIZE) ||
+        !cbb_add_raw_point(&token_cbb, group, &pmbtoken.S) ||
+        !cbb_add_raw_point(&token_cbb, group, &pmbtoken.W) ||
+        !cbb_add_raw_point(&token_cbb, group, &pmbtoken.Ws)) {
+      goto token_err;
+    }
+
+    token = TRUST_TOKEN_new(CBB_data(&token_cbb), CBB_len(&token_cbb));
+    if (token == NULL) {
+      OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+      goto token_err;
+    }
+
+    if (!sk_TRUST_TOKEN_push(tokens, token)) {
+      OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+      goto token_err;
+    }
+    token_ok = 1;
+
+  token_err:
+    CBB_cleanup(&token_cbb);
+    if (!token_ok) {
+      TRUST_TOKEN_free(token);
+      goto err;
+    }
+  }
+
+  sk_PMBTOKEN_PRETOKEN_pop_free(ctx->pretokens, PMBTOKEN_PRETOKEN_free);
+  ctx->pretokens = NULL;
+
+  *out_key_index = key_index;
+  ok = 1;
+
+err:
+  if (!ok) {
+    sk_TRUST_TOKEN_pop_free(tokens, TRUST_TOKEN_free);
+    return NULL;
+  } else {
+    return tokens;
+  }
+}
+
+int TRUST_TOKEN_CLIENT_begin_redemption(TRUST_TOKEN_CLIENT *ctx, uint8_t **out,
+                                        size_t *out_len,
+                                        const TRUST_TOKEN *token,
+                                        const uint8_t *data, size_t data_len,
+                                        uint64_t time) {
+  CBB request, token_inner, inner;
+  if (!CBB_init(&request, 0) ||
+      !CBB_add_u16_length_prefixed(&request, &token_inner) ||
+      !CBB_add_bytes(&token_inner, token->data, token->len) ||
+      !CBB_add_u16_length_prefixed(&request, &inner) ||
+      !CBB_add_bytes(&inner, data, data_len) ||
+      !CBB_add_u64(&request, time) ||
+      !CBB_finish(&request, out, out_len)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    CBB_cleanup(&request);
+    return 0;
+  }
+  return 1;
+}
+
+int TRUST_TOKEN_CLIENT_finish_redemption(TRUST_TOKEN_CLIENT *ctx,
+                                         uint8_t **out_srr, size_t *out_srr_len,
+                                         uint8_t **out_sig, size_t *out_sig_len,
+                                         const uint8_t *response,
+                                         size_t response_len) {
+  if (ctx->srr_key == NULL) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_NO_SRR_KEY_CONFIGURED);
+    return 0;
+  }
+
+  CBS in, srr, sig;
+  CBS_init(&in, response, response_len);
+  if (!CBS_get_u16_length_prefixed(&in, &srr) ||
+      !CBS_get_u16_length_prefixed(&in, &sig)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_ERROR);
+    return 0;
+  }
+
+  EVP_MD_CTX md_ctx;
+  EVP_MD_CTX_init(&md_ctx);
+  int sig_ok = EVP_DigestVerifyInit(&md_ctx, NULL, NULL, NULL, ctx->srr_key) &&
+               EVP_DigestVerify(&md_ctx, CBS_data(&sig), CBS_len(&sig),
+                                CBS_data(&srr), CBS_len(&srr));
+  EVP_MD_CTX_cleanup(&md_ctx);
+
+  if (!sig_ok) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_SRR_SIGNATURE_ERROR);
+    return 0;
+  }
+
+  uint8_t *srr_buf = NULL, *sig_buf = NULL;
+  size_t srr_len, sig_len;
+  if (!CBS_stow(&srr, &srr_buf, &srr_len) ||
+      !CBS_stow(&sig, &sig_buf, &sig_len)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    OPENSSL_free(srr_buf);
+    OPENSSL_free(sig_buf);
+    return 0;
+  }
+
+  *out_srr = srr_buf;
+  *out_srr_len = srr_len;
+  *out_sig = sig_buf;
+  *out_sig_len = sig_len;
+  return 1;
+}
+
+TRUST_TOKEN_ISSUER *TRUST_TOKEN_ISSUER_new(uint16_t max_batchsize) {
+  TRUST_TOKEN_ISSUER *ret = OPENSSL_malloc(sizeof(TRUST_TOKEN_ISSUER));
+  if (ret == NULL) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    return NULL;
+  }
+  OPENSSL_memset(ret, 0, sizeof(TRUST_TOKEN_ISSUER));
+  ret->max_batchsize = max_batchsize;
+  return ret;
+}
+
+void TRUST_TOKEN_ISSUER_free(TRUST_TOKEN_ISSUER *ctx) {
+  if (ctx == NULL) {
+    return;
+  }
+  EVP_PKEY_free(ctx->srr_key);
+  OPENSSL_free(ctx->metadata_key);
+  OPENSSL_free(ctx);
+}
+
+int TRUST_TOKEN_ISSUER_add_key(TRUST_TOKEN_ISSUER *ctx, const uint8_t *key,
+                               size_t key_len) {
+  EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp521r1);
+  if (group == NULL) {
+    return 0;
+  }
+
+  if (ctx->num_keys == OPENSSL_ARRAY_SIZE(ctx->keys)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_TOO_MANY_KEYS);
+    return 0;
+  }
+
+  CBS cbs, tmp;
+  CBS_init(&cbs, key, key_len);
+  uint32_t key_id;
+  if (!CBS_get_u32(&cbs, &key_id)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
+    return 0;
+  }
+
+  size_t scalar_len = BN_num_bytes(&group->order);
+  struct trust_token_issuer_key_st *key_s = &(ctx->keys[ctx->num_keys]);
+  EC_SCALAR *scalars[] = {&key_s->x0, &key_s->y0, &key_s->x1,
+                          &key_s->y1, &key_s->xs, &key_s->ys};
+  for (size_t i = 0; i < OPENSSL_ARRAY_SIZE(scalars); i++) {
+    if (!CBS_get_bytes(&cbs, &tmp, scalar_len) ||
+        !ec_scalar_from_bytes(group, scalars[i], CBS_data(&tmp),
+                              CBS_len(&tmp))) {
+      OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
+     return 0;
+    }
+  }
+  key_s->id = key_id;
+  ctx->num_keys += 1;
+  return 1;
+}
+
+int TRUST_TOKEN_ISSUER_set_srr_key(TRUST_TOKEN_ISSUER *ctx, EVP_PKEY *key) {
+  EVP_PKEY_free(ctx->srr_key);
+  EVP_PKEY_up_ref(key);
+  ctx->srr_key = key;
+  return 1;
+}
+
+int TRUST_TOKEN_ISSUER_set_metadata_key(TRUST_TOKEN_ISSUER *ctx,
+                                        const uint8_t *key, size_t len) {
+  if (len < 32) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_INVALID_METADATA_KEY);
+  }
+  OPENSSL_free(ctx->metadata_key);
+  ctx->metadata_key_len = 0;
+  ctx->metadata_key = OPENSSL_memdup(key, len);
+  if (ctx->metadata_key == NULL) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    return 0;
+  }
+  ctx->metadata_key_len = len;
+  return 1;
+}
+
+int TRUST_TOKEN_ISSUER_issue(const TRUST_TOKEN_ISSUER *ctx, uint8_t **out,
+                             size_t *out_len, uint8_t *out_tokens_issued,
+                             const uint8_t *request, size_t request_len,
+                             uint32_t public_metadata, uint8_t private_metadata,
+                             size_t max_issuance) {
+  EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp521r1);
+  if (group == NULL) {
+    return 0;
+  }
+
+  if (max_issuance > ctx->max_batchsize) {
+    max_issuance = ctx->max_batchsize;
+  }
+
+  int found_public_metadata = 0;
+  for (size_t i = 0; i < ctx->num_keys; i++) {
+    if (ctx->keys[i].id == public_metadata) {
+      found_public_metadata = 1;
+      break;
+    }
+  }
+
+  if (!found_public_metadata || private_metadata > 1) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_INVALID_METADATA);
+    return 0;
+  }
+
+  CBS in;
+  CBS_init(&in, request, request_len);
+
+  CBB response;
+  if (!CBB_init(&response, 0)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    return 0;
+  }
+
+  int ret = 0;
+  uint16_t count;
+  if (!CBS_get_u16(&in, &count)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
+    goto err;
+  }
+
+  if (count > max_issuance) {
+    count = max_issuance;
+  }
+
+  if (!CBB_add_u16(&response, count) ||
+      !CBB_add_u32(&response, public_metadata)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    goto err;
+  }
+
+  for (size_t i = 0; i < count; i++) {
+    EC_RAW_POINT Tp;
+    if (!cbs_get_raw_point(&in, group, &Tp)) {
+      OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
+      goto err;
+    }
+
+    uint8_t s[PMBTOKEN_NONCE_SIZE];
+    EC_RAW_POINT Wp, Wsp;
+    if (!pmbtoken_sign(ctx, s, &Wp, &Wsp, &Tp, public_metadata,
+                       private_metadata)) {
+      goto err;
+    }
+
+    if (!CBB_add_bytes(&response, s, PMBTOKEN_NONCE_SIZE) ||
+        !cbb_add_raw_point(&response, group, &Wp) ||
+        !cbb_add_raw_point(&response, group, &Wsp)) {
+      goto err;
+    }
+  }
+
+  *out_tokens_issued = count;
+
+  if (!CBB_finish(&response, out, out_len)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    goto err;
+  }
+
+  ret = 1;
+
+err:
+  CBB_cleanup(&response);
+  return ret;
+}
+
+// https://tools.ietf.org/html/rfc7049#section-2.1
+static int add_cbor_int_with_type(CBB *cbb, uint8_t major_type,
+                                  uint64_t value) {
+  if (value <= 23) {
+    return CBB_add_u8(cbb, value | major_type);
+  }
+  if (value <= 0xff) {
+    return CBB_add_u8(cbb, 0x18 | major_type) && CBB_add_u8(cbb, value);
+  }
+  if (value <= 0xffff) {
+    return CBB_add_u8(cbb, 0x19 | major_type) && CBB_add_u16(cbb, value);
+  }
+  if (value <= 0xffffffff) {
+    return CBB_add_u8(cbb, 0x1a | major_type) && CBB_add_u32(cbb, value);
+  }
+  if (value <= 0xffffffffffffffff) {
+    return CBB_add_u8(cbb, 0x1b | major_type) && CBB_add_u64(cbb, value);
+  }
+
+  return 0;
+}
+
+// https://tools.ietf.org/html/rfc7049#section-2.1
+static int add_cbor_int(CBB *cbb, uint64_t value) {
+  return add_cbor_int_with_type(cbb, 0, value);
+}
+
+// https://tools.ietf.org/html/rfc7049#section-2.1
+static int add_cbor_text(CBB *cbb, const char *data, size_t len) {
+  return add_cbor_int_with_type(cbb, 0x60, len) &&
+         CBB_add_bytes(cbb, (const uint8_t *)data, len);
+}
+
+// https://tools.ietf.org/html/rfc7049#section-2.1
+static int add_cbor_map(CBB *cbb, uint8_t size) {
+  return add_cbor_int_with_type(cbb, 0xa0, size);
+}
+
+static uint8_t get_metadata_obfuscator(const uint8_t *key, size_t key_len,
+                                       const uint8_t *client_data,
+                                       size_t client_data_len) {
+  uint8_t metadata_obfuscator[SHA256_DIGEST_LENGTH];
+  SHA256_CTX sha_ctx;
+  SHA256_Init(&sha_ctx);
+  SHA256_Update(&sha_ctx, key, key_len);
+  SHA256_Update(&sha_ctx, client_data, client_data_len);
+  SHA256_Final(metadata_obfuscator, &sha_ctx);
+  return metadata_obfuscator[0] >> 7;
+}
+
+int TRUST_TOKEN_ISSUER_redeem(const TRUST_TOKEN_ISSUER *ctx, uint8_t **out,
+                              size_t *out_len, TRUST_TOKEN **out_token,
+                              uint8_t **out_client_data,
+                              size_t *out_client_data_len,
+                              uint64_t *out_redemption_time,
+                              const uint8_t *request, size_t request_len,
+                              uint64_t lifetime) {
+  EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp521r1);
+  if (group == NULL) {
+    return 0;
+  }
+
+  CBS request_cbs, token_cbs;
+  CBS_init(&request_cbs, request, request_len);
+  if (!CBS_get_u16_length_prefixed(&request_cbs, &token_cbs)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_ERROR);
+    return 0;
+  }
+
+  uint32_t public_metadata = 0;
+  uint8_t private_metadata = 0;
+
+  // Parse the token. If there is an error, treat it as an invalid token.
+  PMBTOKEN_TOKEN pmbtoken;
+  if (!CBS_get_u32(&token_cbs, &public_metadata) ||
+      !CBS_copy_bytes(&token_cbs, pmbtoken.t, PMBTOKEN_NONCE_SIZE) ||
+      !cbs_get_raw_point(&token_cbs, group, &pmbtoken.S) ||
+      !cbs_get_raw_point(&token_cbs, group, &pmbtoken.W) ||
+      !cbs_get_raw_point(&token_cbs, group, &pmbtoken.Ws) ||
+      CBS_len(&token_cbs) != 0 ||
+      !pmbtoken_read(ctx, &private_metadata, &pmbtoken, public_metadata)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_INVALID_TOKEN);
+    return 0;
+  }
+
+  int ok = 0;
+  CBB response, srr;
+  uint8_t *srr_buf = NULL, *sig_buf = NULL, *client_data_buf = NULL;
+  size_t srr_len = 0, sig_len = 0, client_data_len = 0;
+  EVP_MD_CTX md_ctx;
+  EVP_MD_CTX_init(&md_ctx);
+  CBB_zero(&srr);
+  if (!CBB_init(&response, 0)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    goto err;
+  }
+
+  CBS client_data;
+  uint64_t redemption_time;
+  if (!CBS_get_u16_length_prefixed(&request_cbs, &client_data) ||
+      !CBS_get_u64(&request_cbs, &redemption_time)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_ERROR);
+    goto err;
+  }
+
+  uint8_t metadata_obfuscator =
+      get_metadata_obfuscator(ctx->metadata_key, ctx->metadata_key_len,
+                              CBS_data(&client_data), CBS_len(&client_data));
+
+  // The SRR is constructed as per the format described in
+  // https://docs.google.com/document/d/1TNnya6B8pyomDK2F1R9CL3dY10OAmqWlnCxsWyOBDVQ/edit#heading=h.7mkzvhpqb8l5
+
+  static const char kClientDataLabel[] = "client-data";
+  static const char kExpiryTimestampLabel[] = "expiry-timestamp";
+  static const char kMetadataLabel[] = "metadata";
+  static const char kPrivateLabel[] = "private";
+  static const char kPublicLabel[] = "public";
+
+  if (!CBB_init(&srr, 0) ||
+      !add_cbor_map(&srr, 3) ||  // SRR map
+      !add_cbor_text(&srr, kClientDataLabel, strlen(kClientDataLabel)) ||
+      !CBB_add_bytes(&srr, CBS_data(&client_data), CBS_len(&client_data)) ||
+      !add_cbor_text(&srr, kExpiryTimestampLabel,
+                     strlen(kExpiryTimestampLabel)) ||
+      !add_cbor_int(&srr, redemption_time + lifetime) ||
+      !add_cbor_text(&srr, kMetadataLabel, strlen(kMetadataLabel)) ||
+      !add_cbor_map(&srr, 2) ||  // Metadata map
+      !add_cbor_text(&srr, kPrivateLabel, strlen(kPrivateLabel)) ||
+      !add_cbor_int(&srr, private_metadata ^ metadata_obfuscator) ||
+      !add_cbor_text(&srr, kPublicLabel, strlen(kPublicLabel)) ||
+      !add_cbor_int(&srr, public_metadata) ||
+      !CBB_finish(&srr, &srr_buf, &srr_len)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    goto err;
+  }
+
+  if (!EVP_DigestSignInit(&md_ctx, NULL, NULL, NULL, ctx->srr_key) ||
+      !EVP_DigestSign(&md_ctx, NULL, &sig_len, srr_buf, srr_len)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_SRR_SIGNATURE_ERROR);
+    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;
+  }
+
+  if (!CBS_stow(&client_data, &client_data_buf, &client_data_len) ||
+      !CBB_finish(&response, out, out_len)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    goto err;
+  }
+
+  TRUST_TOKEN *token = TRUST_TOKEN_new(pmbtoken.t, PMBTOKEN_NONCE_SIZE);
+  if (token == NULL) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    goto err;
+  }
+  *out_token = token;
+  *out_client_data = client_data_buf;
+  *out_client_data_len = client_data_len;
+  *out_redemption_time = redemption_time;
+
+  ok = 1;
+
+err:
+  CBB_cleanup(&response);
+  CBB_cleanup(&srr);
+  OPENSSL_free(srr_buf);
+  OPENSSL_free(sig_buf);
+  EVP_MD_CTX_cleanup(&md_ctx);
+  if (!ok) {
+    OPENSSL_free(client_data_buf);
+  }
+  return ok;
+}
+
+int TRUST_TOKEN_decode_private_metadata(uint8_t *out_value, const uint8_t *key,
+                                        size_t key_len,
+                                        const uint8_t *client_data,
+                                        size_t client_data_len,
+                                        uint8_t encrypted_bit) {
+  uint8_t metadata_obfuscator =
+      get_metadata_obfuscator(key, key_len, client_data, client_data_len);
+  *out_value = encrypted_bit ^ metadata_obfuscator;
+  return 1;
+}
diff --git a/crypto/trust_token/trust_token_test.cc b/crypto/trust_token/trust_token_test.cc
index 0b24ee1..b9bd69e 100644
--- a/crypto/trust_token/trust_token_test.cc
+++ b/crypto/trust_token/trust_token_test.cc
@@ -12,10 +12,33 @@
  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
 
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+#include <algorithm>
+#include <limits>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+
 #include <gtest/gtest.h>
 
+#include <openssl/bytestring.h>
+#include <openssl/curve25519.h>
+#include <openssl/evp.h>
+#include <openssl/mem.h>
+#include <openssl/rand.h>
 #include <openssl/trust_token.h>
 
+#include "../internal.h"
+#include "internal.h"
+
+
+BSSL_NAMESPACE_BEGIN
+
+namespace {
 
 TEST(TrustTokenTest, KeyGen) {
   uint8_t priv_key[TRUST_TOKEN_MAX_PRIVATE_KEY_SIZE];
@@ -27,3 +50,398 @@
   ASSERT_EQ(400u, priv_key_len);
   ASSERT_EQ(409u, pub_key_len);
 }
+
+class TrustTokenProtocolTest : public ::testing::Test {
+ public:
+  // KeyID returns the key ID associated with key index |i|.
+  static uint32_t KeyID(size_t i) {
+    // Use a different value from the indices to that we do not mix them up.
+    return 7 + i;
+  }
+
+ protected:
+  void SetupContexts() {
+    client.reset(TRUST_TOKEN_CLIENT_new(client_max_batchsize));
+    ASSERT_TRUE(client);
+    issuer.reset(TRUST_TOKEN_ISSUER_new(issuer_max_batchsize));
+    ASSERT_TRUE(issuer);
+
+    for (size_t i = 0; i < 3; i++) {
+      uint8_t priv_key[TRUST_TOKEN_MAX_PRIVATE_KEY_SIZE];
+      uint8_t pub_key[TRUST_TOKEN_MAX_PUBLIC_KEY_SIZE];
+      size_t priv_key_len, pub_key_len, key_index;
+      ASSERT_TRUE(TRUST_TOKEN_generate_key(
+          priv_key, &priv_key_len, TRUST_TOKEN_MAX_PRIVATE_KEY_SIZE, pub_key,
+          &pub_key_len, TRUST_TOKEN_MAX_PUBLIC_KEY_SIZE, KeyID(i)));
+      ASSERT_TRUE(TRUST_TOKEN_CLIENT_add_key(client.get(), &key_index, pub_key,
+                                             pub_key_len));
+      ASSERT_EQ(i, key_index);
+      ASSERT_TRUE(
+          TRUST_TOKEN_ISSUER_add_key(issuer.get(), priv_key, priv_key_len));
+    }
+
+    uint8_t public_key[32], private_key[64];
+    ED25519_keypair(public_key, private_key);
+    bssl::UniquePtr<EVP_PKEY> priv(EVP_PKEY_new_raw_private_key(
+        EVP_PKEY_ED25519, nullptr, private_key, 32));
+    ASSERT_TRUE(priv);
+    bssl::UniquePtr<EVP_PKEY> pub(
+        EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, nullptr, public_key, 32));
+    ASSERT_TRUE(pub);
+
+    TRUST_TOKEN_CLIENT_set_srr_key(client.get(), pub.get());
+    TRUST_TOKEN_ISSUER_set_srr_key(issuer.get(), priv.get());
+    RAND_bytes(metadata_key, sizeof(metadata_key));
+    ASSERT_TRUE(TRUST_TOKEN_ISSUER_set_metadata_key(issuer.get(), metadata_key,
+                                                    sizeof(metadata_key)));
+  }
+
+  uint16_t client_max_batchsize = 10;
+  uint16_t issuer_max_batchsize = 10;
+  bssl::UniquePtr<TRUST_TOKEN_CLIENT> client;
+  bssl::UniquePtr<TRUST_TOKEN_ISSUER> issuer;
+  uint8_t metadata_key[32];
+};
+
+TEST_F(TrustTokenProtocolTest, InvalidToken) {
+  ASSERT_NO_FATAL_FAILURE(SetupContexts());
+
+  uint8_t *issue_msg = NULL, *issue_resp = NULL;
+  size_t msg_len, resp_len;
+
+  size_t key_index;
+  uint8_t tokens_issued;
+  ASSERT_TRUE(
+      TRUST_TOKEN_CLIENT_begin_issuance(client.get(), &issue_msg, &msg_len, 1));
+  bssl::UniquePtr<uint8_t> free_issue_msg(issue_msg);
+  ASSERT_TRUE(TRUST_TOKEN_ISSUER_issue(
+      issuer.get(), &issue_resp, &resp_len, &tokens_issued, issue_msg, msg_len,
+      /*public_metadata=*/KeyID(0), /*private_metadata=*/1,
+      /*max_issuance=*/10));
+  bssl::UniquePtr<uint8_t> free_msg(issue_resp);
+  bssl::UniquePtr<STACK_OF(TRUST_TOKEN)> tokens(
+      TRUST_TOKEN_CLIENT_finish_issuance(client.get(), &key_index, issue_resp,
+                                         resp_len));
+  ASSERT_TRUE(tokens);
+
+  for (TRUST_TOKEN *token : tokens.get()) {
+    // Corrupt the token.
+    token->data[0] ^= 0x42;
+
+    uint8_t *redeem_msg = NULL, *redeem_resp = NULL;
+    ASSERT_TRUE(TRUST_TOKEN_CLIENT_begin_redemption(
+        client.get(), &redeem_msg, &msg_len, token, NULL, 0, 0));
+    bssl::UniquePtr<uint8_t> free_redeem_msg(redeem_msg);
+    TRUST_TOKEN *rtoken;
+    uint8_t *client_data;
+    size_t client_data_len;
+    uint64_t redemption_time;
+    ASSERT_FALSE(TRUST_TOKEN_ISSUER_redeem(
+        issuer.get(), &redeem_resp, &resp_len, &rtoken, &client_data,
+        &client_data_len, &redemption_time, redeem_msg, msg_len, 600));
+    bssl::UniquePtr<uint8_t> free_redeem_resp(redeem_resp);
+  }
+}
+
+TEST_F(TrustTokenProtocolTest, TruncatedIssuanceRequest) {
+  ASSERT_NO_FATAL_FAILURE(SetupContexts());
+
+  uint8_t *issue_msg = NULL, *issue_resp = NULL;
+  size_t msg_len, resp_len;
+  ASSERT_TRUE(TRUST_TOKEN_CLIENT_begin_issuance(client.get(), &issue_msg,
+                                                &msg_len, 10));
+  bssl::UniquePtr<uint8_t> free_issue_msg(issue_msg);
+  msg_len = 10;
+  uint8_t tokens_issued;
+  ASSERT_FALSE(TRUST_TOKEN_ISSUER_issue(
+      issuer.get(), &issue_resp, &resp_len, &tokens_issued, issue_msg, msg_len,
+      /*public_metadata=*/KeyID(0), /*private_metadata=*/0,
+      /*max_issuance=*/10));
+  bssl::UniquePtr<uint8_t> free_msg(issue_resp);
+}
+
+TEST_F(TrustTokenProtocolTest, TruncatedIssuanceResponse) {
+  ASSERT_NO_FATAL_FAILURE(SetupContexts());
+
+  uint8_t *issue_msg = NULL, *issue_resp = NULL;
+  size_t msg_len, resp_len;
+  ASSERT_TRUE(TRUST_TOKEN_CLIENT_begin_issuance(client.get(), &issue_msg,
+                                                &msg_len, 10));
+  bssl::UniquePtr<uint8_t> free_issue_msg(issue_msg);
+  uint8_t tokens_issued;
+  ASSERT_TRUE(TRUST_TOKEN_ISSUER_issue(
+      issuer.get(), &issue_resp, &resp_len, &tokens_issued, issue_msg, msg_len,
+      /*public_metadata=*/KeyID(0), /*private_metadata=*/0,
+      /*max_issuance=*/10));
+  bssl::UniquePtr<uint8_t> free_msg(issue_resp);
+  resp_len = 10;
+  size_t key_index;
+  bssl::UniquePtr<STACK_OF(TRUST_TOKEN)> tokens(
+      TRUST_TOKEN_CLIENT_finish_issuance(client.get(), &key_index, issue_resp,
+                                         resp_len));
+  ASSERT_FALSE(tokens);
+}
+
+TEST_F(TrustTokenProtocolTest, TruncatedRedemptionRequest) {
+  ASSERT_NO_FATAL_FAILURE(SetupContexts());
+
+  uint8_t *issue_msg = NULL, *issue_resp = NULL;
+  size_t msg_len, resp_len;
+  ASSERT_TRUE(TRUST_TOKEN_CLIENT_begin_issuance(client.get(), &issue_msg,
+                                                &msg_len, 10));
+  bssl::UniquePtr<uint8_t> free_issue_msg(issue_msg);
+  uint8_t tokens_issued;
+  ASSERT_TRUE(TRUST_TOKEN_ISSUER_issue(
+      issuer.get(), &issue_resp, &resp_len, &tokens_issued, issue_msg, msg_len,
+      /*public_metadata=*/KeyID(0), /*private_metadata=*/0,
+      /*max_issuance=*/10));
+  bssl::UniquePtr<uint8_t> free_msg(issue_resp);
+  size_t key_index;
+  bssl::UniquePtr<STACK_OF(TRUST_TOKEN)> tokens(
+      TRUST_TOKEN_CLIENT_finish_issuance(client.get(), &key_index, issue_resp,
+                                         resp_len));
+  ASSERT_TRUE(tokens);
+
+  for (TRUST_TOKEN *token : tokens.get()) {
+    const uint8_t kClientData[] = "TEST CLIENT DATA";
+    uint64_t kRedemptionTime = 13374242;
+
+    uint8_t *redeem_msg = NULL, *redeem_resp = NULL;
+    ASSERT_TRUE(TRUST_TOKEN_CLIENT_begin_redemption(
+        client.get(), &redeem_msg, &msg_len, token, kClientData,
+        sizeof(kClientData) - 1, kRedemptionTime));
+    bssl::UniquePtr<uint8_t> free_redeem_msg(redeem_msg);
+    msg_len = 10;
+
+    TRUST_TOKEN *rtoken;
+    uint8_t *client_data;
+    size_t client_data_len;
+    uint64_t redemption_time;
+    ASSERT_FALSE(TRUST_TOKEN_ISSUER_redeem(
+        issuer.get(), &redeem_resp, &resp_len, &rtoken, &client_data,
+        &client_data_len, &redemption_time, redeem_msg, msg_len, 600));
+  }
+}
+
+TEST_F(TrustTokenProtocolTest, TruncatedRedemptionResponse) {
+  ASSERT_NO_FATAL_FAILURE(SetupContexts());
+
+  uint8_t *issue_msg = NULL, *issue_resp = NULL;
+  size_t msg_len, resp_len;
+  ASSERT_TRUE(TRUST_TOKEN_CLIENT_begin_issuance(client.get(), &issue_msg,
+                                                &msg_len, 10));
+  bssl::UniquePtr<uint8_t> free_issue_msg(issue_msg);
+  uint8_t tokens_issued;
+  ASSERT_TRUE(TRUST_TOKEN_ISSUER_issue(
+      issuer.get(), &issue_resp, &resp_len, &tokens_issued, issue_msg, msg_len,
+      /*public_metadata=*/KeyID(0), /*private_metadata=*/0,
+      /*max_issuance=*/10));
+  bssl::UniquePtr<uint8_t> free_msg(issue_resp);
+  size_t key_index;
+  bssl::UniquePtr<STACK_OF(TRUST_TOKEN)> tokens(
+      TRUST_TOKEN_CLIENT_finish_issuance(client.get(), &key_index, issue_resp,
+                                         resp_len));
+  ASSERT_TRUE(tokens);
+
+  for (TRUST_TOKEN *token : tokens.get()) {
+    const uint8_t kClientData[] = "TEST CLIENT DATA";
+    uint64_t kRedemptionTime = 13374242;
+
+    uint8_t *redeem_msg = NULL, *redeem_resp = NULL;
+    ASSERT_TRUE(TRUST_TOKEN_CLIENT_begin_redemption(
+        client.get(), &redeem_msg, &msg_len, token, kClientData,
+        sizeof(kClientData) - 1, kRedemptionTime));
+    bssl::UniquePtr<uint8_t> free_redeem_msg(redeem_msg);
+    TRUST_TOKEN *rtoken;
+    uint8_t *client_data;
+    size_t client_data_len;
+    uint64_t redemption_time;
+    ASSERT_TRUE(TRUST_TOKEN_ISSUER_redeem(
+        issuer.get(), &redeem_resp, &resp_len, &rtoken, &client_data,
+        &client_data_len, &redemption_time, redeem_msg, msg_len, 600));
+    bssl::UniquePtr<uint8_t> free_redeem_resp(redeem_resp);
+    bssl::UniquePtr<uint8_t> free_client_data(client_data);
+    bssl::UniquePtr<TRUST_TOKEN> free_rtoken(rtoken);
+
+    ASSERT_EQ(redemption_time, kRedemptionTime);
+    ASSERT_TRUE(sizeof(kClientData) - 1 == client_data_len);
+    ASSERT_EQ(OPENSSL_memcmp(kClientData, client_data, client_data_len), 0);
+    resp_len = 10;
+
+    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));
+    bssl::UniquePtr<uint8_t> free_srr(srr);
+    bssl::UniquePtr<uint8_t> free_sig(sig);
+  }
+}
+
+TEST_F(TrustTokenProtocolTest, IssuedWithBadKeyID) {
+  client.reset(TRUST_TOKEN_CLIENT_new(client_max_batchsize));
+  ASSERT_TRUE(client);
+  issuer.reset(TRUST_TOKEN_ISSUER_new(issuer_max_batchsize));
+  ASSERT_TRUE(issuer);
+
+  // We configure the client and the issuer with different key IDs and test
+  // that the client notices.
+  const uint32_t kClientKeyID = 0;
+  const uint32_t kIssuerKeyID = 42;
+
+  uint8_t priv_key[TRUST_TOKEN_MAX_PRIVATE_KEY_SIZE];
+  uint8_t pub_key[TRUST_TOKEN_MAX_PUBLIC_KEY_SIZE];
+  size_t priv_key_len, pub_key_len, key_index;
+  ASSERT_TRUE(TRUST_TOKEN_generate_key(
+      priv_key, &priv_key_len, TRUST_TOKEN_MAX_PRIVATE_KEY_SIZE, pub_key,
+      &pub_key_len, TRUST_TOKEN_MAX_PUBLIC_KEY_SIZE, kClientKeyID));
+  ASSERT_TRUE(TRUST_TOKEN_CLIENT_add_key(client.get(), &key_index, pub_key,
+                                         pub_key_len));
+  ASSERT_EQ(0UL, key_index);
+
+  ASSERT_TRUE(TRUST_TOKEN_generate_key(
+      priv_key, &priv_key_len, TRUST_TOKEN_MAX_PRIVATE_KEY_SIZE, pub_key,
+      &pub_key_len, TRUST_TOKEN_MAX_PUBLIC_KEY_SIZE, kIssuerKeyID));
+  ASSERT_TRUE(TRUST_TOKEN_ISSUER_add_key(issuer.get(), priv_key, priv_key_len));
+
+
+  uint8_t public_key[32], private_key[64];
+  ED25519_keypair(public_key, private_key);
+  bssl::UniquePtr<EVP_PKEY> priv(
+      EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, nullptr, private_key, 32));
+  ASSERT_TRUE(priv);
+  bssl::UniquePtr<EVP_PKEY> pub(
+      EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, nullptr, public_key, 32));
+  ASSERT_TRUE(pub);
+
+  TRUST_TOKEN_CLIENT_set_srr_key(client.get(), pub.get());
+  TRUST_TOKEN_ISSUER_set_srr_key(issuer.get(), priv.get());
+  RAND_bytes(metadata_key, sizeof(metadata_key));
+  ASSERT_TRUE(TRUST_TOKEN_ISSUER_set_metadata_key(issuer.get(), metadata_key,
+                                                  sizeof(metadata_key)));
+
+
+  uint8_t *issue_msg = NULL, *issue_resp = NULL;
+  size_t msg_len, resp_len;
+  ASSERT_TRUE(TRUST_TOKEN_CLIENT_begin_issuance(client.get(), &issue_msg,
+                                                &msg_len, 10));
+  bssl::UniquePtr<uint8_t> free_issue_msg(issue_msg);
+  uint8_t tokens_issued;
+  ASSERT_TRUE(TRUST_TOKEN_ISSUER_issue(
+      issuer.get(), &issue_resp, &resp_len, &tokens_issued, issue_msg, msg_len,
+      /*public_metadata=*/42, /*private_metadata=*/0, /*max_issuance=*/10));
+  bssl::UniquePtr<uint8_t> free_msg(issue_resp);
+  bssl::UniquePtr<STACK_OF(TRUST_TOKEN)> tokens(
+      TRUST_TOKEN_CLIENT_finish_issuance(client.get(), &key_index, issue_resp,
+                                         resp_len));
+  ASSERT_FALSE(tokens);
+}
+
+class TrustTokenMetadataTest
+    : public TrustTokenProtocolTest,
+      public testing::WithParamInterface<std::tuple<int, bool>> {};
+
+TEST_P(TrustTokenMetadataTest, SetAndGetMetadata) {
+  ASSERT_NO_FATAL_FAILURE(SetupContexts());
+
+  uint8_t *issue_msg = NULL, *issue_resp = NULL;
+  size_t msg_len, resp_len;
+  ASSERT_TRUE(TRUST_TOKEN_CLIENT_begin_issuance(client.get(), &issue_msg,
+                                                &msg_len, 10));
+  bssl::UniquePtr<uint8_t> free_issue_msg(issue_msg);
+  uint8_t tokens_issued;
+  ASSERT_TRUE(TRUST_TOKEN_ISSUER_issue(
+      issuer.get(), &issue_resp, &resp_len, &tokens_issued, issue_msg, msg_len,
+      std::get<0>(GetParam()), std::get<1>(GetParam()), /*max_issuance=*/1));
+  bssl::UniquePtr<uint8_t> free_msg(issue_resp);
+  size_t key_index;
+  bssl::UniquePtr<STACK_OF(TRUST_TOKEN)> tokens(
+      TRUST_TOKEN_CLIENT_finish_issuance(client.get(), &key_index, issue_resp,
+                                         resp_len));
+  ASSERT_TRUE(tokens);
+
+  for (TRUST_TOKEN *token : tokens.get()) {
+    const uint8_t kClientData[] = "TEST CLIENT DATA";
+    uint64_t kRedemptionTime = 13374242;
+
+    const uint8_t kExpectedSRR[] =
+        "\xa3\x6b\x63\x6c\x69\x65\x6e\x74\x2d\x64\x61\x74\x61\x54\x45\x53\x54"
+        "\x20\x43\x4c\x49\x45\x4e\x54\x20\x44\x41\x54\x41\x70\x65\x78\x70\x69"
+        "\x72\x79\x2d\x74\x69\x6d\x65\x73\x74\x61\x6d\x70\x1a\x00\xcc\x15\x7a"
+        "\x68\x6d\x65\x74\x61\x64\x61\x74\x61\xa2\x67\x70\x72\x69\x76\x61\x74"
+        "\x65\x00\x66\x70\x75\x62\x6c\x69\x63\x00";
+
+    uint8_t *redeem_msg = NULL, *redeem_resp = NULL;
+    ASSERT_TRUE(TRUST_TOKEN_CLIENT_begin_redemption(
+        client.get(), &redeem_msg, &msg_len, token, kClientData,
+        sizeof(kClientData) - 1, kRedemptionTime));
+    bssl::UniquePtr<uint8_t> free_redeem_msg(redeem_msg);
+    TRUST_TOKEN *rtoken;
+    uint8_t *client_data;
+    size_t client_data_len;
+    uint64_t redemption_time;
+    ASSERT_TRUE(TRUST_TOKEN_ISSUER_redeem(
+        issuer.get(), &redeem_resp, &resp_len, &rtoken, &client_data,
+        &client_data_len, &redemption_time, redeem_msg, msg_len, 600));
+    bssl::UniquePtr<uint8_t> free_redeem_resp(redeem_resp);
+    bssl::UniquePtr<uint8_t> free_client_data(client_data);
+    bssl::UniquePtr<TRUST_TOKEN> free_rtoken(rtoken);
+
+    ASSERT_EQ(redemption_time, kRedemptionTime);
+    ASSERT_TRUE(sizeof(kClientData) - 1 == client_data_len);
+    ASSERT_EQ(OPENSSL_memcmp(kClientData, client_data, client_data_len), 0);
+
+    uint8_t *srr = NULL, *sig = NULL;
+    size_t srr_len, sig_len;
+    ASSERT_TRUE(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);
+
+    uint8_t private_metadata;
+    ASSERT_TRUE(TRUST_TOKEN_decode_private_metadata(
+        &private_metadata, metadata_key, sizeof(metadata_key), kClientData,
+        sizeof(kClientData) - 1, srr[srr_len - 9]));
+    ASSERT_EQ(srr[srr_len - 1], std::get<0>(GetParam()));
+    ASSERT_EQ(private_metadata, std::get<1>(GetParam()));
+
+    // Clear out the metadata bits.
+    srr[srr_len - 9] = 0;
+    srr[srr_len - 1] = 0;
+
+    ASSERT_TRUE(sizeof(kExpectedSRR) - 1 == srr_len);
+    ASSERT_EQ(OPENSSL_memcmp(kExpectedSRR, srr, srr_len), 0);
+  }
+}
+
+TEST_P(TrustTokenMetadataTest, TooManyRequests) {
+  issuer_max_batchsize = 1;
+  ASSERT_NO_FATAL_FAILURE(SetupContexts());
+
+  uint8_t *issue_msg = NULL, *issue_resp = NULL;
+  size_t msg_len, resp_len;
+  ASSERT_TRUE(TRUST_TOKEN_CLIENT_begin_issuance(client.get(), &issue_msg,
+                                                &msg_len, 10));
+  bssl::UniquePtr<uint8_t> free_issue_msg(issue_msg);
+  uint8_t tokens_issued;
+  ASSERT_TRUE(TRUST_TOKEN_ISSUER_issue(
+      issuer.get(), &issue_resp, &resp_len, &tokens_issued, issue_msg, msg_len,
+      std::get<0>(GetParam()), std::get<1>(GetParam()), /*max_issuance=*/1));
+  bssl::UniquePtr<uint8_t> free_msg(issue_resp);
+  ASSERT_EQ(tokens_issued, issuer_max_batchsize);
+  size_t key_index;
+  bssl::UniquePtr<STACK_OF(TRUST_TOKEN)> tokens(
+      TRUST_TOKEN_CLIENT_finish_issuance(client.get(), &key_index, issue_resp,
+                                         resp_len));
+  ASSERT_TRUE(tokens);
+  ASSERT_EQ(sk_TRUST_TOKEN_num(tokens.get()), 1UL);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    TrustTokenAllMetadataTest, TrustTokenMetadataTest,
+    testing::Combine(testing::Values(TrustTokenProtocolTest::KeyID(0),
+                                     TrustTokenProtocolTest::KeyID(1),
+                                     TrustTokenProtocolTest::KeyID(2)),
+                     testing::Bool()));
+
+}  // namespace
+BSSL_NAMESPACE_END
diff --git a/include/openssl/base.h b/include/openssl/base.h
index 8d73f77..7218967 100644
--- a/include/openssl/base.h
+++ b/include/openssl/base.h
@@ -422,6 +422,9 @@
 typedef struct ssl_st SSL;
 typedef struct ssl_ticket_aead_method_st SSL_TICKET_AEAD_METHOD;
 typedef struct st_ERR_FNS ERR_FNS;
+typedef struct trust_token_st TRUST_TOKEN;
+typedef struct trust_token_client_st TRUST_TOKEN_CLIENT;
+typedef struct trust_token_issuer_st TRUST_TOKEN_ISSUER;
 typedef struct v3_ext_ctx X509V3_CTX;
 typedef struct x509_attributes_st X509_ATTRIBUTE;
 typedef struct x509_cert_aux_st X509_CERT_AUX;
diff --git a/include/openssl/trust_token.h b/include/openssl/trust_token.h
index bf5b5fa..473999d 100644
--- a/include/openssl/trust_token.h
+++ b/include/openssl/trust_token.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2019, Google Inc.
+/* 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
@@ -36,6 +36,23 @@
 //
 // WARNING: This API is unstable and subject to change.
 
+// 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.
+struct trust_token_st {
+  uint8_t *data;
+  size_t len;
+};
+
+DEFINE_STACK_OF(TRUST_TOKEN)
+
+// TRUST_TOKEN_new creates a newly-allocated |TRUST_TOKEN| with value |data| or
+// NULL on allocation failure.
+OPENSSL_EXPORT TRUST_TOKEN *TRUST_TOKEN_new(const uint8_t *data, size_t len);
+
+// TRUST_TOKEN_free releases memory associated with |token|.
+OPENSSL_EXPORT void TRUST_TOKEN_free(TRUST_TOKEN *token);
+
 #define TRUST_TOKEN_MAX_PRIVATE_KEY_SIZE 512
 #define TRUST_TOKEN_MAX_PUBLIC_KEY_SIZE 512
 
@@ -59,11 +76,202 @@
     uint32_t id);
 
 
+// Trust Token client implementation.
+//
+// These functions implements the client half of the Trust Token protocol. A
+// single |TRUST_TOKEN_CLIENT| can perform a single protocol operation.
+
+// TRUST_TOKEN_CLIENT_new returns a newly-allocated |TRUST_TOKEN_CLIENT|
+// configured to use a max batchsize of |max_batchsize| or NULL on error.
+// Issuance requests must be made in batches smaller than |max_batchsize|.
+OPENSSL_EXPORT TRUST_TOKEN_CLIENT *TRUST_TOKEN_CLIENT_new(
+    uint16_t max_batchsize);
+
+// TRUST_TOKEN_CLIENT_free releases memory associated with |ctx|.
+OPENSSL_EXPORT void TRUST_TOKEN_CLIENT_free(TRUST_TOKEN_CLIENT *ctx);
+
+// TRUST_TOKEN_CLIENT_add_key configures the |ctx| to support the public key
+// |key|. It sets |*out_key_index| to the index this key has been configured to.
+// It returns one on success or zero on error if the |key| can't be parsed or
+// too many keys have been configured.
+OPENSSL_EXPORT int TRUST_TOKEN_CLIENT_add_key(TRUST_TOKEN_CLIENT *ctx,
+                                              size_t *out_key_index,
+                                              const uint8_t *key,
+                                              size_t key_len);
+
+// TRUST_TOKEN_CLIENT_set_srr_key sets the public key used to verify the SRR. It
+// returns one on success and zero on error.
+OPENSSL_EXPORT int TRUST_TOKEN_CLIENT_set_srr_key(TRUST_TOKEN_CLIENT *ctx,
+                                                  EVP_PKEY *key);
+
+// TRUST_TOKEN_CLIENT_begin_issuance produces a request for |count| trust tokens
+// and serializes the request into a newly-allocated buffer, setting |*out| to
+// that buffer and |*out_len| to its length. The caller takes ownership of the
+// buffer and must call |OPENSSL_free| when done. It returns one on success and
+// zero on error.
+OPENSSL_EXPORT int TRUST_TOKEN_CLIENT_begin_issuance(TRUST_TOKEN_CLIENT *ctx,
+                                                     uint8_t **out,
+                                                     size_t *out_len,
+                                                     size_t count);
+
+// TRUST_TOKEN_CLIENT_finish_issuance consumes |response| from the issuer and
+// extracts the tokens, returning a list of tokens and the index of the key used
+// to sign the tokens in |*out_key_index|. The caller can use this to determine
+// what key was used in an issuance and to drop tokens if a new key commitment
+// arrives without the specified key present. The caller takes ownership of the
+// list and must call |sk_TRUST_TOKEN_pop_free| when done. The list is empty if
+// issuance fails.
+OPENSSL_EXPORT STACK_OF(TRUST_TOKEN) *
+    TRUST_TOKEN_CLIENT_finish_issuance(TRUST_TOKEN_CLIENT *ctx,
+                                       size_t *out_key_index,
+                                       const uint8_t *response,
+                                       size_t response_len);
+
+
+// TRUST_TOKEN_CLIENT_begin_redemption produces a request to redeem a token
+// |token| and receive a signature over |data| and serializes the request into
+// a newly-allocated buffer, setting |*out| to that buffer and |*out_len| to
+// its length. |time| is the number of seconds since the UNIX epoch and used to
+// verify the validity of the issuer's response. The caller takes ownership of
+// the buffer and must call |OPENSSL_free| when done. It returns one on success
+// or zero on error.
+OPENSSL_EXPORT int TRUST_TOKEN_CLIENT_begin_redemption(
+    TRUST_TOKEN_CLIENT *ctx, uint8_t **out, size_t *out_len,
+    const TRUST_TOKEN *token, const uint8_t *data, size_t data_len,
+    uint64_t time);
+
+// TRUST_TOKEN_CLIENT_finish_redemption consumes |response| from the issuer and
+// verifies the SRR. If valid, it returns one and sets |*out_srr| and
+// |*out_srr_len| (respectively, |*out_sig| and |*out_sig_len|) to a
+// newly-allocated buffer containing the SRR (respectively, the SRR signature).
+// Otherwise, it returns zero.
+OPENSSL_EXPORT int TRUST_TOKEN_CLIENT_finish_redemption(
+    TRUST_TOKEN_CLIENT *ctx, uint8_t **out_srr, size_t *out_srr_len,
+    uint8_t **out_sig, size_t *out_sig_len, const uint8_t *response,
+    size_t response_len);
+
+
+// Trust Token issuer implementation.
+//
+// These functions implement the issuer half of the Trust Token protocol. A
+// |TRUST_TOKEN_ISSUER| can be reused across multiple protocol operations. It
+// may be used concurrently on multiple threads by non-mutating functions,
+// provided no other thread is concurrently calling a mutating function.
+// Functions which take a |const| pointer are non-mutating and functions which
+// take a non-|const| pointer are mutating.
+
+// TRUST_TOKEN_ISSUER_new returns a newly-allocated |TRUST_TOKEN_ISSUER|
+// configured to use a max batchsize of |max_batchsize| or NULL on error.
+// Issuance requests must be made in batches smaller than |max_batchsize|.
+OPENSSL_EXPORT TRUST_TOKEN_ISSUER *TRUST_TOKEN_ISSUER_new(
+    uint16_t max_batchsize);
+
+// TRUST_TOKEN_ISSUER_free releases memory associated with |ctx|.
+OPENSSL_EXPORT void TRUST_TOKEN_ISSUER_free(TRUST_TOKEN_ISSUER *ctx);
+
+// TRUST_TOKEN_ISSUER_add_key configures the |ctx| to support the private key
+// |key|. It must be a private key returned by |TRUST_TOKEN_generate_key|. It
+// returns one on success or zero on error. This function may fail if the |key|
+// can't be parsed or too many keys have been configured.
+OPENSSL_EXPORT int TRUST_TOKEN_ISSUER_add_key(TRUST_TOKEN_ISSUER *ctx,
+                                              const uint8_t *key,
+                                              size_t key_len);
+
+// TRUST_TOKEN_ISSUER_set_srr_key sets the private key used to sign the SRR. It
+// returns one on success and zero on error.
+OPENSSL_EXPORT int TRUST_TOKEN_ISSUER_set_srr_key(TRUST_TOKEN_ISSUER *ctx,
+                                                  EVP_PKEY *key);
+
+// TRUST_TOKEN_ISSUER_set_metadata_key sets the key used to encrypt the private
+// metadata. The key is a randomly generated bytestring of at least 32 bytes
+// used to encode the private metadata bit in the SRR. It returns one on success
+// and zero on error.
+OPENSSL_EXPORT int TRUST_TOKEN_ISSUER_set_metadata_key(TRUST_TOKEN_ISSUER *ctx,
+                                                       const uint8_t *key,
+                                                       size_t len);
+
+// TRUST_TOKEN_ISSUER_issue ingests |request| for token issuance
+// and generates up to |max_issuance| valid tokens, producing a list of blinded
+// tokens and storing the response into a newly-allocated buffer and setting
+// |*out| to that buffer, |*out_len| to its length, and |*out_tokens_issued| to
+// the number of tokens issued. The tokens are issued with public metadata of
+// |public_metadata| and a private metadata value of |private_metadata|.
+// |public_metadata| must be one of the previously configured key IDs.
+// |private_metadata| must be 0 or 1. The caller takes ownership of the buffer
+// and must call |OPENSSL_free| when done. It returns one on success or zero on
+// error.
+OPENSSL_EXPORT int TRUST_TOKEN_ISSUER_issue(
+    const TRUST_TOKEN_ISSUER *ctx, uint8_t **out, size_t *out_len,
+    uint8_t *out_tokens_issued, const uint8_t *request, size_t request_len,
+    uint32_t public_metadata, uint8_t private_metadata, size_t max_issuance);
+
+// TRUST_TOKEN_ISSUER_redeem ingests a |request| for token redemption and
+// verifies the token. If the token is valid, a SRR is produced with a lifetime
+// of |lifetime| (in seconds), signing over the requested data from the request
+// and the value of the token, storing the result into a newly-allocated buffer
+// and setting |*out| to that buffer and |*out_len| to its length. The extracted
+// |TRUST_TOKEN| is stored into a newly-allocated buffer and stored in
+// |*out_token|. The extracted client data is stored into a newly-allocated
+// buffer and stored in |*out_client_data|. The extracted redemption time is
+// stored in |*out_redemption_time|. The caller takes ownership of each output
+// buffer and must call |OPENSSL_free| when done. It returns one on success or
+// zero on error.
+//
+// The caller must keep track of all values of |*out_token| and
+// |*out_client_data| and seen globally before returning the SRR to the client.
+// If either value has been repeated, the caller must discard the SRR and report
+// an error to the caller. Returning an SRR with replayed values allows an
+// attacker to double-spend tokens and query private metadata bits in SRRs.
+//
+// TODO(svaldez): The private metadata bit should not be leaked on replay. This
+// means callers cannot use eventual consistency to trade off double-spending
+// and distributed system performance. See https://crbug.com/boringssl/328.
+OPENSSL_EXPORT int TRUST_TOKEN_ISSUER_redeem(
+    const TRUST_TOKEN_ISSUER *ctx, uint8_t **out, size_t *out_len,
+    TRUST_TOKEN **out_token, uint8_t **out_client_data,
+    size_t *out_client_data_len, uint64_t *out_redemption_time,
+    const uint8_t *request, size_t request_len, uint64_t lifetime);
+
+// TRUST_TOKEN_decode_private_metadata decodes |encrypted_bit| using the
+// private metadata key specified by a |key| buffer of length |key_len| and the
+// client data specified by a |client_data| buffer of length |client_data_len|.
+// |*out_value is set to the decrypted value, either zero or one. It returns one
+// on success and zero on error.
+OPENSSL_EXPORT int TRUST_TOKEN_decode_private_metadata(
+    uint8_t *out_value, const uint8_t *key, size_t key_len,
+    const uint8_t *client_data, size_t client_data_len, uint8_t encrypted_bit);
+
+
 #if defined(__cplusplus)
 }  // extern C
+
+extern "C++" {
+
+BSSL_NAMESPACE_BEGIN
+
+BORINGSSL_MAKE_DELETER(TRUST_TOKEN, TRUST_TOKEN_free)
+BORINGSSL_MAKE_DELETER(TRUST_TOKEN_CLIENT, TRUST_TOKEN_CLIENT_free)
+BORINGSSL_MAKE_DELETER(TRUST_TOKEN_ISSUER, TRUST_TOKEN_ISSUER_free)
+
+
+BSSL_NAMESPACE_END
+
+}  // extern C++
 #endif
 
 #define TRUST_TOKEN_R_KEYGEN_FAILURE 100
 #define TRUST_TOKEN_R_BUFFER_TOO_SMALL 101
+#define TRUST_TOKEN_R_OVER_BATCHSIZE 102
+#define TRUST_TOKEN_R_DECODE_ERROR 103
+#define TRUST_TOKEN_R_SRR_SIGNATURE_ERROR 104
+#define TRUST_TOKEN_R_DECODE_FAILURE 105
+#define TRUST_TOKEN_R_INVALID_METADATA 106
+#define TRUST_TOKEN_R_TOO_MANY_KEYS 107
+#define TRUST_TOKEN_R_NO_KEYS_CONFIGURED 108
+#define TRUST_TOKEN_R_INVALID_KEY_ID 109
+#define TRUST_TOKEN_R_INVALID_TOKEN 110
+#define TRUST_TOKEN_R_BAD_VALIDITY_CHECK 111
+#define TRUST_TOKEN_R_NO_SRR_KEY_CONFIGURED 112
+#define TRUST_TOKEN_R_INVALID_METADATA_KEY 113
 
 #endif  // OPENSSL_HEADER_TRUST_TOKEN_H