external/boringssl: Sync to 3743aafdacff2f7b083615a043a37101f740fa53.

This includes the following changes:

https://boringssl.googlesource.com/boringssl/+log/2fb729d4f36beaf263ad85e24a790b571652679c..3743aafdacff2f7b083615a043a37101f740fa53

* Add SSL_CIPHER_get_protocol_id.
* Add TrustTokenV2.
* Add X509_get_pathlen and X509_REVOKED_get0_extensions.
* Add some accommodations for FreeRDP
* Require non-NULL store in X509_STORE_CTX_init.
Update-Note: X509_STORE_CTX_init will now fail when the store is NULL,
rather than report success, only to crash later in X509_verify_cert.
Breakage should thus be limited to code which was passing in a NULL
store but never used the resulting X509_STORE_CTX.
* Const-correct X509V3_CONF_METHOD.
Update-Note: External definitions of X509V3_CONF_METHOD will need fix
the types of their functions. There should not be any of these (probably
hide this struct), but if there are, this aligns with upstream OpenSSL.
* Avoid unions in X509_NAME logic.
* Bump OPENSSL_VERSION_NUMBER to 1.1.1.
Update-Note: Some OPENSSL_VERSION_NUMBER/OPENSSL_IS_BORINGSSL checks may
need to be updated. Hopefully even more can go away.
* Document more of x509.h.
* Fix potential leak in bssl::Array::Shrink.
* Remove ASN1_STRING_length_set.
Update-Note: Use ASN1_STRING_set instead, though this function appears
to be unused.
* Revert "Check AlgorithmIdentifier parameters for RSA and ECDSA signatures."
* Implement PSK variants of HPKE setup functions.
* acvp: support working with files.
* Document a few more functions in x509.h.
* Add subject key ID and authority key ID accessors.
* Remove sxnet and pkey_usage_period extensions.
Update-Note: Parsers for these two extensions are removed. Parsing the
types directly or passing NID_sxnet and NID_pkey_usage_period into
X509V3_get_d2i, or *_get_ext_d2i will no longer work.
* Const-correct various X509 functions.
* Make X509_set_not{Before,After} functions rather than macros.
* Add X509_get0_uids from OpenSSL 1.1.0.
* Bound RSA and DSA key sizes better.
Update-Note: Some invalid or overly large RSA and DSA keys may
previously have been accepted that are now rejected at parse time. For
public keys, this only moves the error from verification to parsing. In
some private key cases, we would previously allow signing with those
keys, but the resulting signatures would not be accepted by BoringSSL
anyway. This CL makes us behave more consistently.
* Add set1 versions of X509 timestamp setters.
* Consistently sort generated build files.
* delocate: use 64-bit GOT offsets in the large memory model.
* Update HPKE implementation and test vectors to draft-irtf-cfrg-hpke-05.
* Handle NULL arguments in some i2d_* functions.
* aarch64: support BTI and pointer authentication in assembly
* Support delegated credentials verison 06
* delocation: large memory model support.
* Enforce presence of ALPN when QUIC is in use.
Update-Note: If an SSL_QUIC_METHOD is set, connections will now fail if
ALPN is not negotiated. This new behavior can be detected by checking
if the value of BORINGSSL_API_VERSION is greater than 10.
* Fix the naming of alert error codes.
Update-Note: The renamed alerts will log slightly different strings, but
the constants used by external code are still there.
* Use golang.org/x/crypto in runner.
Update-Note: The tests now have a golang.org/x/crypto dependency. This
should be fetched transparently with Go modules. Monorepos with
different import path conventions may need to rewrite these imports.
* Disable ClientHello padding for QUIC.
* Add X509_SIG_get0 and X509_SIG_getm.
* Implement HPKE.
* Disallow TLS 1.3 compatibility mode in QUIC.
* Switch clang-format IncludeBlocks to Preserve.
* Fix unterminated clang-format off.
* Add line number to doc.go error messages.
* Kick the bots.
* Add a JSON output to generate_build_files.py.
* Add details of 20190808 FIPS certification.
* Link to ws2_32 more consistently.
* Allow explicitly-encoded X.509v1 versions for now.
* Opaquify PKCS8_PRIV_KEY_INFO.
Update-Note: Direct accesses of PKCS8_PRIV_KEY_INFO now need to use the
accessors. Code search suggests no one uses the fields. Even the
accessors are virtually unused (the one thing which uses it doesn't need
it).
* Implement i2d_PUBKEY and friends without crypto/asn1.
* Remove TRUST_TOKEN_experiment_v0.
Update-Note: This gets rid of TRUST_TOKEN_experiment_v0. Existing callers
should be updated to call TRUST_TOKEN_experiment_v1.
* Clarify in-place rules for low-level AES mode functions.
* acvp: add CMAC-AES support.
* acvp: add SP800-108 KDF support.
* Remove x509->name.
Update-Note: instead of x509->name, use X509_NAME_oneline and
X509_get_subject_name.
* Maybe build for AArch64 Windows.
* sha1-x86_64: fix CFI.
* Use |crypto_word_t| and |size_t| more consistently in ECC scalar recoding.
* Enable shaext path for sha1.
* Avoid relying on SSL_get_session's behavior during the handshake.
Update-Note: SSL_generate_key_block will now fail mid-handshake. It is
ambiguous which key block to use and, in some cases, we may not even be
able to compute the right key block.
* Add a -wait-for-debugger flag to runner.
* Add missing OPENSSL_EXPORT to X509_get_X509_PUBKEY.
* Const-correct various functions in crypto/asn1.
* Remove uneeded switch statement.
* Convert X.509 accessor macros to proper functions.
Update-Note: This should be compatible, but it is possible that someone,
e.g., passed in a bssl::UniquePtr<X509> to an accessor and relied on
operator->. Callers may also run afoul of const correctness. I mirrored
OpenSSL 1.1.1's consts, so it should at least be compatible with
third-party code.
* Remove X509_CINF_get_issuer and X509_CINF_get_extensions.
Update-Note: Two unused macros were removed. If there were uses, the
X509-level accessors can be used instead.
* Remove X509_get_signature_type.
Update-Note: If there are any calls to X509_get_signature_type, remove
them. It more-or-less always returned NID_undef.
* clang-format x509.h and run comment converter.
* Check AlgorithmIdentifier parameters for RSA and ECDSA signatures.
* Remove some unimplemented prototypes.
* Check the X.509 version when parsing.
Update-Note: The X.509 parser is now a bit stricter. This may break some
malformed certificates which were previously incorrectly accepted.
* Fix x509v3_cache_extensions error-handling.
Update-Note: The X.509 verifier now correctly rejects syntax errors in
important certificate extensions. This may break some malformed
certificates which were incorrectly accepted before.
* Work around Windows command-line limits in embed_test_data.go.
* Move crypto/x509 test data into its own directory.
* Test resumability of same, different, and default ticket keys.
* Fixes warning when redefining PATH_MAX when building with MINGW.
* Abstract fd operations better in tool.
* Use CMAKE_SIZEOF_VOID_P instead of CMAKE_CL_64
* Enforce the keyUsage extension in TLS 1.2 client certs.
Update-Note: Client certificates which do not support the
digitalSignature key usage will be rejected. They should either include
that bit or omit the keyUsage extension.
* Reword some comments.
* Add “Z Computation” KAT.
* acvptool: handle negative sizeConstraint.
* Let memory hooks override the size prefix.
* acvptool: go fmt
* Assert md_size > 0.
* Remove -enable-ed25519 compat hack.
* Add a |SSL_process_tls13_new_session_ticket|.
* Use ctr32 optimizations for AES_ctr128_encrypt.
* Test AES mode wrappers.
* Bump minimum CMake version.
* Modify how QUIC 0-RTT go/no-go decision is made.
* Remove RAND_set_urandom_fd.
Update-Note: RAND_set_urandom_fd no longer exists. This was only called
by Chromium, which now uses CRYPTO_pre_sandbox_init.
* Document that getrandom support must be consistent.
* Fix docs link for SSL_CTX_load_verify_locations
* Fix TRUST_TOKEN experiment_v1 SRR map.
* Add CRYPTO_pre_sandbox_init.
* Still query getauxval if reading /proc/cpuinfo fails.
* Add missing header to ec/wnaf.c
* Fix OPENSSL_TSAN typo.
* Fix p256-x86_64-table.h indentation.
* Enable avx2 implementation of sha1.
* Trim Z coordinates from the OPENSSL_SMALL P-256 tables.
* Use public multi-scalar mults in Trust Tokens where applicable.
* Use batched DLEQ proofs for Trust Token.
* Restrict when 0-RTT will be accepted in QUIC.
* Disable TLS 1.3 compatibility mode for QUIC.
* Use a 5-bit comb for some Trust Tokens multiplications.
* Use a (mostly) constant-time multi-scalar mult for Trust Tokens.
* Batch inversions in Trust Tokens.
* Rearrange the DLEQ logic slightly.
* Use token hash to encode private metadata for Trust Token Experiment V1.
* Introduce an EC_AFFINE abstraction.
* Make the fuzzer PRNG thread-safe.
* Disable fork-detect tests under TSAN.
* Introduce TRUST_TOKENS_experiment_v1.
* Route PMBToken calls through TRUST_TOKEN_METHOD.
* Introduce a TRUST_TOKEN_METHOD hook to select TRUST_TOKEN variations.
Update-Note: Pass TRUST_TOKEN_experiment_v0() into any existing code
that now needs a TRUST_TOKEN_METHOD.
* fork_detect: be robust to qemu.
* Move serialization of points inside pmbtoken.c.
* Introduce PMBTOKENS key abstractions.
* Fix the types used in token counts.
Update-Note: Fix callers of TRUST_TOKEN_ISSUER_issue to use size_t
instead of uint8_t. The max_batchsize changes should go through without
changes to the caller.
* Remove unused code from ghash-x86_64.pl.
* Switch the P-384 hash-to-curve to draft-07.
* Add hash-to-curve code for P384.
* Write down the expressions for all the NIST primes.
* Move fork_detect files into rand/
* Harden against fork via MADV_WIPEONFORK.
* Fix typo in comment.
* Use faster addition chains for P-256 field inversion.
* Tidy up third_party/fiat.
* Prefix g_pre_comp in p256.c as well.
* Add missing curve check to ec_hash_to_scalar_p521_xmd_sha512.
* Add a tool to compare the output of bssl speed.
* Benchmark ECDH slightly more accurately.
* Align remaining Intel copyright notice.
* Don't retain T in PMBTOKEN_PRETOKEN.
* Check for trailing data in TRUST_TOKEN_CLIENT_finish_issuance.
* Properly namespace everything in third_party/fiat/p256.c.
* Update fiat-crypto.
* Add missing ERR_LIB_TRUST_TOKEN constants.
* Add bssl speed support for hashtocurve and trusttoken.
* Implement DLEQ checks for Trust Token.
* Fix error-handling in EVP_BytesToKey.
* Fix Trust Token CBOR.
* Match parameter names between header and source.
* Trust Token Implementation.
* Include mem.h for |CRYPTO_memcmp|
* acvptool: add subprocess tests.
* Add SHA-512-256.
* Make ec_GFp_simple_cmp constant-time.
Update-Note: This does mean that we pay a 6M+2S Jacobian comparison
where comparing two publicly affine points should cost no field
operations at all. Code which compares two EC public keys for equality
will be slightly slower. I wouldn't expect this to matter (if you
actually use the public keys, you'll pay much much more) If it does, we
can restore this optimization by keeping better track of affine vs.
Jacobian forms. See https://crbug.com/boringssl/326.
* Tidy up CRYPTO_sysrand variants.
* Do a better job testing EC_POINT_cmp.
* Follow-up comments to hash_to_scalar.
* Add a hash_to_scalar variation of P-521's hash_to_field.
* Add SSL_SESSION_copy_without_early_data.
* Double-check secret EC point multiplications.
* Make ec_felem_equal constant-time.
* Fix hash-to-curve comment.
* Make ec_GFp_simple_is_on_curve constant-time.
* Implement draft-irtf-cfrg-hash-to-curve-06.
* Update list of tested SDE configurations.
* Only draw from RDRAND for additional_data if it's fast.
* Generalize bn_from_montgomery_small.
* Remove BIGNUM from uncompressed coordinate parsing.
* Add EC_RAW_POINT serialization function.
* Base EC_FELEM conversions on bytes rather than BIGNUMs.
* runner: Replace supportsVersions calls with allVersions.
* Enable QUIC for some perMessageTest runner tests
* Move BN_nnmod calls out of low-level group_set_curve.
* Clean up various EC inversion functions.
* Start to organize ec/internal.h a little.
* Fix CFI for AVX2 ChaCha20-Poly1305.
* Remove unused function prototype.
* Enable more runner tests for QUIC
* Require QUIC method with Transport Parameters and vice versa
* acvptool: support non-interactive mode.
* Add is_quic bit to SSL_SESSION
* Update SDE.
* Update tools.
* Add simpler getters for DH and DSA.
* Don't define default implementations for weak symbols.
* Don't automatically run all tests for ABI testing.
* Fix test build with recent Clang.
* Remove LCM dependency from RSA_check_key.
* Simplify bn_sub_part_words.
* No-op commit to test Windows SDE bots.
* ABI-test each AEAD.
* Add memory tracking and sanitization hooks
* Add X509_STORE_CTX_get0_chain.
* Add DH_set_length.
* Static assert that CRYPTO_MUTEX is sufficiently aligned.
* [bazel] Format toplevel BUILD file with buildifier
* Add |SSL_CTX_get0_chain|.
* Configure QUIC secrets inside set_{read,write}_state.
Update-Note: See b/151142920#comment9
Change-Id: I4bbb76e15b5d95615ea643bccf796db87fae4989
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/40244
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Steven Valdez <svaldez@google.com>
* Allow setting QUIC transport parameters after parsing the client's
* Fix comment for |BORINGSSL_self_test|.
* Trust Token Key Generation.
* Revise QUIC encryption secret APIs.
Update-Note: This is an incompatible change to SSL_QUIC_METHOD.
BORINGSSL_API_VERSION can be used to distinguish the two revisions.
* Fix ec_point_mul_scalar_public's documentation.

Test: atest CtsLibcoreTestCases CtsLibcoreTestCases
Change-Id: I754169828a3e1cb461bf0d4a64d4d7b46951d730
diff --git a/src/crypto/trust_token/internal.h b/src/crypto/trust_token/internal.h
new file mode 100644
index 0000000..c935888
--- /dev/null
+++ b/src/crypto/trust_token/internal.h
@@ -0,0 +1,291 @@
+/* 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>
+
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+
+// PMBTokens.
+//
+// PMBTokens is described in https://eprint.iacr.org/2020/072/20200324:214215
+// and provides anonymous tokens with private metadata. We implement the
+// construction with validity verification, described in appendix H,
+// construction 6.
+
+// PMBTOKEN_NONCE_SIZE is the size of nonces used as part of the PMBToken
+// protocol.
+#define PMBTOKEN_NONCE_SIZE 64
+
+typedef struct {
+  // TODO(https://crbug.com/boringssl/334): These should store |EC_PRECOMP| so
+  // that |TRUST_TOKEN_finish_issuance| can use |ec_point_mul_scalar_precomp|.
+  EC_AFFINE pub0;
+  EC_AFFINE pub1;
+  EC_AFFINE pubs;
+} PMBTOKEN_CLIENT_KEY;
+
+typedef struct {
+  EC_SCALAR x0;
+  EC_SCALAR y0;
+  EC_SCALAR x1;
+  EC_SCALAR y1;
+  EC_SCALAR xs;
+  EC_SCALAR ys;
+  EC_AFFINE pub0;
+  EC_PRECOMP pub0_precomp;
+  EC_AFFINE pub1;
+  EC_PRECOMP pub1_precomp;
+  EC_AFFINE pubs;
+  EC_PRECOMP pubs_precomp;
+} PMBTOKEN_ISSUER_KEY;
+
+// 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_AFFINE Tp;
+} PMBTOKEN_PRETOKEN;
+
+// PMBTOKEN_PRETOKEN_free releases the memory associated with |token|.
+OPENSSL_EXPORT void PMBTOKEN_PRETOKEN_free(PMBTOKEN_PRETOKEN *token);
+
+DEFINE_STACK_OF(PMBTOKEN_PRETOKEN)
+
+// The following functions implement the corresponding |TRUST_TOKENS_METHOD|
+// functions for |TRUST_TOKENS_experiment_v1|'s PMBTokens construction which
+// uses P-384.
+//
+// We use P-384 instead of our usual choice of P-256. See Appendix I which
+// describes two attacks which may affect smaller curves. In particular, p-1 for
+// P-256 is smooth, giving a low complexity for the p-1 attack. P-384's p-1 has
+// a 281-bit prime factor,
+// 3055465788140352002733946906144561090641249606160407884365391979704929268480326390471.
+// This lower-bounds the p-1 attack at O(2^140). The p+1 attack is lower-bounded
+// by O(p^(1/3)) or O(2^128), so we do not need to check the smoothness of p+1.
+int pmbtoken_exp1_generate_key(CBB *out_private, CBB *out_public);
+int pmbtoken_exp1_client_key_from_bytes(PMBTOKEN_CLIENT_KEY *key,
+                                        const uint8_t *in, size_t len);
+int pmbtoken_exp1_issuer_key_from_bytes(PMBTOKEN_ISSUER_KEY *key,
+                                        const uint8_t *in, size_t len);
+STACK_OF(PMBTOKEN_PRETOKEN) * pmbtoken_exp1_blind(CBB *cbb, size_t count);
+int pmbtoken_exp1_sign(const PMBTOKEN_ISSUER_KEY *key, CBB *cbb, CBS *cbs,
+                       size_t num_requested, size_t num_to_issue,
+                       uint8_t private_metadata);
+STACK_OF(TRUST_TOKEN) *
+    pmbtoken_exp1_unblind(const PMBTOKEN_CLIENT_KEY *key,
+                          const STACK_OF(PMBTOKEN_PRETOKEN) * pretokens,
+                          CBS *cbs, size_t count, uint32_t key_id);
+int pmbtoken_exp1_read(const PMBTOKEN_ISSUER_KEY *key,
+                       uint8_t out_nonce[PMBTOKEN_NONCE_SIZE],
+                       uint8_t *out_private_metadata, const uint8_t *token,
+                       size_t token_len);
+
+// pmbtoken_exp1_get_h_for_testing returns H in uncompressed coordinates. This
+// function is used to confirm H was computed as expected.
+OPENSSL_EXPORT int pmbtoken_exp1_get_h_for_testing(uint8_t out[97]);
+
+// The following functions implement the corresponding |TRUST_TOKENS_METHOD|
+// functions for |TRUST_TOKENS_experiment_v2|'s PMBTokens construction which
+// uses P-384.
+//
+// We use P-384 instead of our usual choice of P-256. See Appendix I which
+// describes two attacks which may affect smaller curves. In particular, p-1 for
+// P-256 is smooth, giving a low complexity for the p-1 attack. P-384's p-1 has
+// a 281-bit prime factor,
+// 3055465788140352002733946906144561090641249606160407884365391979704929268480326390471.
+// This lower-bounds the p-1 attack at O(2^140). The p+1 attack is lower-bounded
+// by O(p^(1/3)) or O(2^128), so we do not need to check the smoothness of p+1.
+int pmbtoken_exp2_generate_key(CBB *out_private, CBB *out_public);
+int pmbtoken_exp2_client_key_from_bytes(PMBTOKEN_CLIENT_KEY *key,
+                                        const uint8_t *in, size_t len);
+int pmbtoken_exp2_issuer_key_from_bytes(PMBTOKEN_ISSUER_KEY *key,
+                                        const uint8_t *in, size_t len);
+STACK_OF(PMBTOKEN_PRETOKEN) * pmbtoken_exp2_blind(CBB *cbb, size_t count);
+int pmbtoken_exp2_sign(const PMBTOKEN_ISSUER_KEY *key, CBB *cbb, CBS *cbs,
+                       size_t num_requested, size_t num_to_issue,
+                       uint8_t private_metadata);
+STACK_OF(TRUST_TOKEN) *
+    pmbtoken_exp2_unblind(const PMBTOKEN_CLIENT_KEY *key,
+                          const STACK_OF(PMBTOKEN_PRETOKEN) * pretokens,
+                          CBS *cbs, size_t count, uint32_t key_id);
+int pmbtoken_exp2_read(const PMBTOKEN_ISSUER_KEY *key,
+                       uint8_t out_nonce[PMBTOKEN_NONCE_SIZE],
+                       uint8_t *out_private_metadata, const uint8_t *token,
+                       size_t token_len);
+
+// pmbtoken_exp2_get_h_for_testing returns H in uncompressed coordinates. This
+// function is used to confirm H was computed as expected.
+OPENSSL_EXPORT int pmbtoken_exp2_get_h_for_testing(uint8_t out[97]);
+
+
+// Trust Tokens internals.
+
+struct trust_token_method_st {
+  // generate_key generates a fresh keypair and writes their serialized
+  // forms into |out_private| and |out_public|. It returns one on success and
+  // zero on failure.
+  int (*generate_key)(CBB *out_private, CBB *out_public);
+
+  // client_key_from_bytes decodes a client key from |in| and sets |key|
+  // to the resulting key. It returns one on success and zero
+  // on failure.
+  int (*client_key_from_bytes)(PMBTOKEN_CLIENT_KEY *key, const uint8_t *in,
+                               size_t len);
+
+  // issuer_key_from_bytes decodes a issuer key from |in| and sets |key|
+  // to the resulting key. It returns one on success and zero
+  // on failure.
+  int (*issuer_key_from_bytes)(PMBTOKEN_ISSUER_KEY *key, const uint8_t *in,
+                               size_t len);
+
+  // blind generates a new issuance request for |count| tokens. On
+  // success, it returns a newly-allocated |STACK_OF(PMBTOKEN_PRETOKEN)| and
+  // writes a request to the issuer to |cbb|. On failure, it returns NULL. The
+  // |STACK_OF(PMBTOKEN_PRETOKEN)|s should be passed to |pmbtoken_unblind| when
+  // the server responds.
+  //
+  // This function implements the AT.Usr0 operation.
+  STACK_OF(PMBTOKEN_PRETOKEN) *(*blind)(CBB *cbb, size_t count);
+
+  // sign parses a request for |num_requested| tokens from |cbs| and
+  // issues |num_to_issue| tokens with |key| and a private metadata value of
+  // |private_metadata|. It then writes the response to |cbb|. It returns one on
+  // success and zero on failure.
+  //
+  // This function implements the AT.Sig operation.
+  int (*sign)(const PMBTOKEN_ISSUER_KEY *key, CBB *cbb, CBS *cbs,
+              size_t num_requested, size_t num_to_issue,
+              uint8_t private_metadata);
+
+  // unblind processes an issuance response for |count| tokens from |cbs|
+  // and unblinds the signed tokens. |pretokens| are the pre-tokens returned
+  // from the corresponding |blind| call. On success, the function returns a
+  // newly-allocated |STACK_OF(TRUST_TOKEN)| containing the resulting tokens.
+  // Each token's serialization will have |key_id| prepended. Otherwise, it
+  // returns NULL.
+  //
+  // This function implements the AT.Usr1 operation.
+  STACK_OF(TRUST_TOKEN) *
+      (*unblind)(const PMBTOKEN_CLIENT_KEY *key,
+                 const STACK_OF(PMBTOKEN_PRETOKEN) * pretokens, CBS *cbs,
+                 size_t count, uint32_t key_id);
+
+  // read parses a PMBToken from |token| and verifies it using |key|. On
+  // success, it returns one and stores the nonce and private metadata bit in
+  // |out_nonce| and |*out_private_metadata|. Otherwise, it returns zero. Note
+  // that, unlike the output of |unblind|, |token| does not have a
+  // four-byte key ID prepended.
+  int (*read)(const PMBTOKEN_ISSUER_KEY *key,
+              uint8_t out_nonce[PMBTOKEN_NONCE_SIZE],
+              uint8_t *out_private_metadata, const uint8_t *token,
+              size_t token_len);
+
+  // whether the construction supports private metadata.
+  int has_private_metadata;
+
+  // max keys that can be configured.
+  size_t max_keys;
+
+  // whether the SRR is part of the protocol.
+  int has_srr;
+};
+
+// Structure representing a single Trust Token public key with the specified ID.
+struct trust_token_client_key_st {
+  uint32_t id;
+  PMBTOKEN_CLIENT_KEY key;
+};
+
+// Structure representing a single Trust Token private key with the specified
+// ID.
+struct trust_token_issuer_key_st {
+  uint32_t id;
+  PMBTOKEN_ISSUER_KEY key;
+};
+
+struct trust_token_client_st {
+  const TRUST_TOKEN_METHOD *method;
+
+  // 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[6];
+
+  // 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 {
+  const TRUST_TOKEN_METHOD *method;
+
+  // 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[6];
+
+  // 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;
+};
+
+
+#if defined(__cplusplus)
+}  // extern C
+
+extern "C++" {
+
+BSSL_NAMESPACE_BEGIN
+
+BORINGSSL_MAKE_DELETER(PMBTOKEN_PRETOKEN, PMBTOKEN_PRETOKEN_free)
+
+BSSL_NAMESPACE_END
+
+}  // extern C++
+#endif
+
+#endif  // OPENSSL_HEADER_TRUST_TOKEN_INTERNAL_H
diff --git a/src/crypto/trust_token/pmbtoken.c b/src/crypto/trust_token/pmbtoken.c
new file mode 100644
index 0000000..f9132e6
--- /dev/null
+++ b/src/crypto/trust_token/pmbtoken.c
@@ -0,0 +1,1399 @@
+/* Copyright (c) 2020, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+#include <openssl/trust_token.h>
+
+#include <openssl/bn.h>
+#include <openssl/bytestring.h>
+#include <openssl/ec.h>
+#include <openssl/err.h>
+#include <openssl/mem.h>
+#include <openssl/nid.h>
+#include <openssl/rand.h>
+#include <openssl/sha.h>
+
+#include "../ec_extra/internal.h"
+#include "../fipsmodule/bn/internal.h"
+#include "../fipsmodule/ec/internal.h"
+
+#include "internal.h"
+
+
+typedef int (*hash_t_func_t)(const EC_GROUP *group, EC_RAW_POINT *out,
+                             const uint8_t t[PMBTOKEN_NONCE_SIZE]);
+typedef int (*hash_s_func_t)(const EC_GROUP *group, EC_RAW_POINT *out,
+                             const EC_AFFINE *t,
+                             const uint8_t s[PMBTOKEN_NONCE_SIZE]);
+typedef int (*hash_c_func_t)(const EC_GROUP *group, EC_SCALAR *out,
+                             uint8_t *buf, size_t len);
+
+typedef struct {
+  const EC_GROUP *group;
+  EC_PRECOMP g_precomp;
+  EC_PRECOMP h_precomp;
+  EC_RAW_POINT h;
+  // hash_t implements the H_t operation in PMBTokens. It returns one on success
+  // and zero on error.
+  hash_t_func_t hash_t;
+  // hash_s implements the H_s operation in PMBTokens. It returns one on success
+  // and zero on error.
+  hash_s_func_t hash_s;
+  // hash_c implements the H_c operation in PMBTokens. It returns one on success
+  // and zero on error.
+  hash_c_func_t hash_c;
+  int prefix_point : 1;
+} PMBTOKEN_METHOD;
+
+static const uint8_t kDefaultAdditionalData[32] = {0};
+
+static int pmbtoken_init_method(PMBTOKEN_METHOD *method, int curve_nid,
+                                const uint8_t *h_bytes, size_t h_len,
+                                hash_t_func_t hash_t, hash_s_func_t hash_s,
+                                hash_c_func_t hash_c, int prefix_point) {
+  method->group = EC_GROUP_new_by_curve_name(curve_nid);
+  if (method->group == NULL) {
+    return 0;
+  }
+
+  method->hash_t = hash_t;
+  method->hash_s = hash_s;
+  method->hash_c = hash_c;
+  method->prefix_point = prefix_point;
+
+  EC_AFFINE h;
+  if (!ec_point_from_uncompressed(method->group, &h, h_bytes, h_len)) {
+    return 0;
+  }
+  ec_affine_to_jacobian(method->group, &method->h, &h);
+
+  if (!ec_init_precomp(method->group, &method->g_precomp,
+                       &method->group->generator->raw) ||
+      !ec_init_precomp(method->group, &method->h_precomp, &method->h)) {
+    return 0;
+  }
+  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(const PMBTOKEN_METHOD *method, EC_SCALAR *out_x,
+                            EC_SCALAR *out_y, EC_RAW_POINT *out_pub) {
+  if (!ec_random_nonzero_scalar(method->group, out_x, kDefaultAdditionalData) ||
+      !ec_random_nonzero_scalar(method->group, out_y, kDefaultAdditionalData) ||
+      !ec_point_mul_scalar_precomp(method->group, out_pub, &method->g_precomp,
+                                   out_x, &method->h_precomp, out_y, NULL,
+                                   NULL)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    return 0;
+  }
+  return 1;
+}
+
+static int point_to_cbb(CBB *out, const EC_GROUP *group,
+                        const EC_AFFINE *point) {
+  size_t len =
+      ec_point_to_bytes(group, point, POINT_CONVERSION_UNCOMPRESSED, NULL, 0);
+  if (len == 0) {
+    return 0;
+  }
+  uint8_t *p;
+  return CBB_add_space(out, &p, len) &&
+         ec_point_to_bytes(group, point, POINT_CONVERSION_UNCOMPRESSED, p,
+                           len) == len;
+}
+
+static int cbb_add_prefixed_point(CBB *out, const EC_GROUP *group,
+                                  const EC_AFFINE *point, int prefix_point) {
+  if (prefix_point) {
+    CBB child;
+    if (!CBB_add_u16_length_prefixed(out, &child) ||
+        !point_to_cbb(&child, group, point) ||
+        !CBB_flush(out)) {
+      return 0;
+    }
+  } else {
+    if (!point_to_cbb(out, group, point) ||
+        !CBB_flush(out)) {
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
+static int cbs_get_prefixed_point(CBS *cbs, const EC_GROUP *group,
+                                  EC_AFFINE *out, int prefix_point) {
+  CBS child;
+  if (prefix_point) {
+    if (!CBS_get_u16_length_prefixed(cbs, &child)) {
+      return 0;
+    }
+  } else {
+    size_t plen = 1 + 2 * BN_num_bytes(&group->field);
+    if (!CBS_get_bytes(cbs, &child, plen)) {
+      return 0;
+    }
+  }
+
+  if (!ec_point_from_uncompressed(group, out, CBS_data(&child),
+                                  CBS_len(&child))) {
+    return 0;
+  }
+  return 1;
+}
+
+static int mul_public_3(const EC_GROUP *group, EC_RAW_POINT *out,
+                        const EC_RAW_POINT *p0, const EC_SCALAR *scalar0,
+                        const EC_RAW_POINT *p1, const EC_SCALAR *scalar1,
+                        const EC_RAW_POINT *p2, const EC_SCALAR *scalar2) {
+  EC_RAW_POINT points[3] = {*p0, *p1, *p2};
+  EC_SCALAR scalars[3] = {*scalar0, *scalar1, *scalar2};
+  return ec_point_mul_scalar_public_batch(group, out, /*g_scalar=*/NULL, points,
+                                          scalars, 3);
+}
+
+void PMBTOKEN_PRETOKEN_free(PMBTOKEN_PRETOKEN *pretoken) {
+  OPENSSL_free(pretoken);
+}
+
+static int pmbtoken_generate_key(const PMBTOKEN_METHOD *method,
+                                 CBB *out_private, CBB *out_public) {
+  const EC_GROUP *group = method->group;
+  EC_RAW_POINT pub[3];
+  EC_SCALAR x0, y0, x1, y1, xs, ys;
+  if (!generate_keypair(method, &x0, &y0, &pub[0]) ||
+      !generate_keypair(method, &x1, &y1, &pub[1]) ||
+      !generate_keypair(method, &xs, &ys, &pub[2])) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_KEYGEN_FAILURE);
+    return 0;
+  }
+
+  const EC_SCALAR *scalars[] = {&x0, &y0, &x1, &y1, &xs, &ys};
+  size_t scalar_len = BN_num_bytes(&group->order);
+  for (size_t i = 0; i < OPENSSL_ARRAY_SIZE(scalars); i++) {
+    uint8_t *buf;
+    if (!CBB_add_space(out_private, &buf, scalar_len)) {
+      OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_BUFFER_TOO_SMALL);
+      return 0;
+    }
+    ec_scalar_to_bytes(group, buf, &scalar_len, scalars[i]);
+  }
+
+  EC_AFFINE pub_affine[3];
+  if (!ec_jacobian_to_affine_batch(group, pub_affine, pub, 3)) {
+    return 0;
+  }
+
+  if (!cbb_add_prefixed_point(out_public, group, &pub_affine[0],
+                              method->prefix_point) ||
+      !cbb_add_prefixed_point(out_public, group, &pub_affine[1],
+                              method->prefix_point) ||
+      !cbb_add_prefixed_point(out_public, group, &pub_affine[2],
+                              method->prefix_point)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_BUFFER_TOO_SMALL);
+    return 0;
+  }
+
+  return 1;
+}
+
+static int pmbtoken_client_key_from_bytes(const PMBTOKEN_METHOD *method,
+                                          PMBTOKEN_CLIENT_KEY *key,
+                                          const uint8_t *in, size_t len) {
+  CBS cbs;
+  CBS_init(&cbs, in, len);
+  if (!cbs_get_prefixed_point(&cbs, method->group, &key->pub0,
+                              method->prefix_point) ||
+      !cbs_get_prefixed_point(&cbs, method->group, &key->pub1,
+                              method->prefix_point) ||
+      !cbs_get_prefixed_point(&cbs, method->group, &key->pubs,
+                              method->prefix_point) ||
+      CBS_len(&cbs) != 0) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
+    return 0;
+  }
+
+  return 1;
+}
+
+static int pmbtoken_issuer_key_from_bytes(const PMBTOKEN_METHOD *method,
+                                          PMBTOKEN_ISSUER_KEY *key,
+                                          const uint8_t *in, size_t len) {
+  const EC_GROUP *group = method->group;
+  CBS cbs, tmp;
+  CBS_init(&cbs, in, len);
+  size_t scalar_len = BN_num_bytes(&group->order);
+  EC_SCALAR *scalars[] = {&key->x0, &key->y0, &key->x1,
+                          &key->y1, &key->xs, &key->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;
+    }
+  }
+
+  // Recompute the public key.
+  EC_RAW_POINT pub[3];
+  EC_AFFINE pub_affine[3];
+  if (!ec_point_mul_scalar_precomp(group, &pub[0], &method->g_precomp, &key->x0,
+                                   &method->h_precomp, &key->y0, NULL, NULL) ||
+      !ec_init_precomp(group, &key->pub0_precomp, &pub[0]) ||
+      !ec_point_mul_scalar_precomp(group, &pub[1], &method->g_precomp, &key->x1,
+                                   &method->h_precomp, &key->y1, NULL, NULL) ||
+      !ec_init_precomp(group, &key->pub1_precomp, &pub[1]) ||
+      !ec_point_mul_scalar_precomp(group, &pub[2], &method->g_precomp, &key->xs,
+                                   &method->h_precomp, &key->ys, NULL, NULL) ||
+      !ec_init_precomp(group, &key->pubs_precomp, &pub[2]) ||
+      !ec_jacobian_to_affine_batch(group, pub_affine, pub, 3)) {
+    return 0;
+  }
+
+  key->pub0 = pub_affine[0];
+  key->pub1 = pub_affine[1];
+  key->pubs = pub_affine[2];
+  return 1;
+}
+
+static STACK_OF(PMBTOKEN_PRETOKEN) *
+    pmbtoken_blind(const PMBTOKEN_METHOD *method, CBB *cbb, size_t count) {
+  const EC_GROUP *group = method->group;
+  STACK_OF(PMBTOKEN_PRETOKEN) *pretokens = sk_PMBTOKEN_PRETOKEN_new_null();
+  if (pretokens == NULL) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    goto err;
+  }
+
+  for (size_t i = 0; i < count; i++) {
+    // Insert |pretoken| into |pretokens| early to simplify error-handling.
+    PMBTOKEN_PRETOKEN *pretoken = OPENSSL_malloc(sizeof(PMBTOKEN_PRETOKEN));
+    if (pretoken == NULL ||
+        !sk_PMBTOKEN_PRETOKEN_push(pretokens, pretoken)) {
+      OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+      PMBTOKEN_PRETOKEN_free(pretoken);
+      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);
+
+    EC_RAW_POINT T, Tp;
+    if (!method->hash_t(group, &T, pretoken->t) ||
+        !ec_point_mul_scalar(group, &Tp, &T, &rinv) ||
+        !ec_jacobian_to_affine(group, &pretoken->Tp, &Tp)) {
+      goto err;
+    }
+
+    if (!cbb_add_prefixed_point(cbb, group, &pretoken->Tp,
+                                method->prefix_point)) {
+      goto err;
+    }
+  }
+
+  return pretokens;
+
+err:
+  sk_PMBTOKEN_PRETOKEN_pop_free(pretokens, PMBTOKEN_PRETOKEN_free);
+  return NULL;
+}
+
+static int scalar_to_cbb(CBB *out, const EC_GROUP *group,
+                         const EC_SCALAR *scalar) {
+  uint8_t *buf;
+  size_t scalar_len = BN_num_bytes(&group->order);
+  if (!CBB_add_space(out, &buf, scalar_len)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    return 0;
+  }
+  ec_scalar_to_bytes(group, buf, &scalar_len, scalar);
+  return 1;
+}
+
+static int scalar_from_cbs(CBS *cbs, const EC_GROUP *group, EC_SCALAR *out) {
+  size_t scalar_len = BN_num_bytes(&group->order);
+  CBS tmp;
+  if (!CBS_get_bytes(cbs, &tmp, scalar_len)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
+    return 0;
+  }
+
+  ec_scalar_from_bytes(group, out, CBS_data(&tmp), CBS_len(&tmp));
+  return 1;
+}
+
+static int hash_c_dleq(const PMBTOKEN_METHOD *method, EC_SCALAR *out,
+                       const EC_AFFINE *X, const EC_AFFINE *T,
+                       const EC_AFFINE *S, const EC_AFFINE *W,
+                       const EC_AFFINE *K0, const EC_AFFINE *K1) {
+  static const uint8_t kDLEQ2Label[] = "DLEQ2";
+
+  int ok = 0;
+  CBB cbb;
+  CBB_zero(&cbb);
+  uint8_t *buf = NULL;
+  size_t len;
+  if (!CBB_init(&cbb, 0) ||
+      !CBB_add_bytes(&cbb, kDLEQ2Label, sizeof(kDLEQ2Label)) ||
+      !point_to_cbb(&cbb, method->group, X) ||
+      !point_to_cbb(&cbb, method->group, T) ||
+      !point_to_cbb(&cbb, method->group, S) ||
+      !point_to_cbb(&cbb, method->group, W) ||
+      !point_to_cbb(&cbb, method->group, K0) ||
+      !point_to_cbb(&cbb, method->group, K1) ||
+      !CBB_finish(&cbb, &buf, &len) ||
+      !method->hash_c(method->group, out, buf, len)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    goto err;
+  }
+
+  ok = 1;
+
+err:
+  CBB_cleanup(&cbb);
+  OPENSSL_free(buf);
+  return ok;
+}
+
+static int hash_c_dleqor(const PMBTOKEN_METHOD *method, EC_SCALAR *out,
+                         const EC_AFFINE *X0, const EC_AFFINE *X1,
+                         const EC_AFFINE *T, const EC_AFFINE *S,
+                         const EC_AFFINE *W, const EC_AFFINE *K00,
+                         const EC_AFFINE *K01, const EC_AFFINE *K10,
+                         const EC_AFFINE *K11) {
+  static const uint8_t kDLEQOR2Label[] = "DLEQOR2";
+
+  int ok = 0;
+  CBB cbb;
+  CBB_zero(&cbb);
+  uint8_t *buf = NULL;
+  size_t len;
+  if (!CBB_init(&cbb, 0) ||
+      !CBB_add_bytes(&cbb, kDLEQOR2Label, sizeof(kDLEQOR2Label)) ||
+      !point_to_cbb(&cbb, method->group, X0) ||
+      !point_to_cbb(&cbb, method->group, X1) ||
+      !point_to_cbb(&cbb, method->group, T) ||
+      !point_to_cbb(&cbb, method->group, S) ||
+      !point_to_cbb(&cbb, method->group, W) ||
+      !point_to_cbb(&cbb, method->group, K00) ||
+      !point_to_cbb(&cbb, method->group, K01) ||
+      !point_to_cbb(&cbb, method->group, K10) ||
+      !point_to_cbb(&cbb, method->group, K11) ||
+      !CBB_finish(&cbb, &buf, &len) ||
+      !method->hash_c(method->group, out, buf, len)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    goto err;
+  }
+
+  ok = 1;
+
+err:
+  CBB_cleanup(&cbb);
+  OPENSSL_free(buf);
+  return ok;
+}
+
+static int hash_c_batch(const PMBTOKEN_METHOD *method, EC_SCALAR *out,
+                        const CBB *points, size_t index) {
+  static const uint8_t kDLEQBatchLabel[] = "DLEQ BATCH";
+  if (index > 0xffff) {
+    // The protocol supports only two-byte batches.
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_OVERFLOW);
+    return 0;
+  }
+
+  int ok = 0;
+  CBB cbb;
+  CBB_zero(&cbb);
+  uint8_t *buf = NULL;
+  size_t len;
+  if (!CBB_init(&cbb, 0) ||
+      !CBB_add_bytes(&cbb, kDLEQBatchLabel, sizeof(kDLEQBatchLabel)) ||
+      !CBB_add_bytes(&cbb, CBB_data(points), CBB_len(points)) ||
+      !CBB_add_u16(&cbb, (uint16_t)index) ||
+      !CBB_finish(&cbb, &buf, &len) ||
+      !method->hash_c(method->group, out, buf, len)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    goto err;
+  }
+
+  ok = 1;
+
+err:
+  CBB_cleanup(&cbb);
+  OPENSSL_free(buf);
+  return ok;
+}
+
+// The DLEQ2 and DLEQOR2 constructions are described in appendix B of
+// https://eprint.iacr.org/2020/072/20200324:214215. DLEQ2 is an instance of
+// DLEQOR2 with only one value (n=1).
+
+static int dleq_generate(const PMBTOKEN_METHOD *method, CBB *cbb,
+                         const PMBTOKEN_ISSUER_KEY *priv, const EC_RAW_POINT *T,
+                         const EC_RAW_POINT *S, const EC_RAW_POINT *W,
+                         const EC_RAW_POINT *Ws, uint8_t private_metadata) {
+  const EC_GROUP *group = method->group;
+
+  // We generate a DLEQ proof for the validity token and a DLEQOR2 proof for the
+  // private metadata token. To allow amortizing Jacobian-to-affine conversions,
+  // we compute Ki for both proofs first.
+  enum {
+    idx_T,
+    idx_S,
+    idx_W,
+    idx_Ws,
+    idx_Ks0,
+    idx_Ks1,
+    idx_Kb0,
+    idx_Kb1,
+    idx_Ko0,
+    idx_Ko1,
+    num_idx,
+  };
+  EC_RAW_POINT jacobians[num_idx];
+
+  // Setup the DLEQ proof.
+  EC_SCALAR ks0, ks1;
+  if (// ks0, ks1 <- Zp
+      !ec_random_nonzero_scalar(group, &ks0, kDefaultAdditionalData) ||
+      !ec_random_nonzero_scalar(group, &ks1, kDefaultAdditionalData) ||
+      // Ks = ks0*(G;T) + ks1*(H;S)
+      !ec_point_mul_scalar_precomp(group, &jacobians[idx_Ks0],
+                                   &method->g_precomp, &ks0, &method->h_precomp,
+                                   &ks1, NULL, NULL) ||
+      !ec_point_mul_scalar_batch(group, &jacobians[idx_Ks1], T, &ks0, S, &ks1,
+                                 NULL, NULL)) {
+    return 0;
+  }
+
+  // Setup the DLEQOR proof. First, select values of xb, yb (keys corresponding
+  // to the private metadata value) and pubo (public key corresponding to the
+  // other value) in constant time.
+  BN_ULONG mask = ((BN_ULONG)0) - (private_metadata & 1);
+  EC_PRECOMP pubo_precomp;
+  EC_SCALAR xb, yb;
+  ec_scalar_select(group, &xb, mask, &priv->x1, &priv->x0);
+  ec_scalar_select(group, &yb, mask, &priv->y1, &priv->y0);
+  ec_precomp_select(group, &pubo_precomp, mask, &priv->pub0_precomp,
+                    &priv->pub1_precomp);
+
+  EC_SCALAR k0, k1, minus_co, uo, vo;
+  if (// k0, k1 <- Zp
+      !ec_random_nonzero_scalar(group, &k0, kDefaultAdditionalData) ||
+      !ec_random_nonzero_scalar(group, &k1, kDefaultAdditionalData) ||
+      // Kb = k0*(G;T) + k1*(H;S)
+      !ec_point_mul_scalar_precomp(group, &jacobians[idx_Kb0],
+                                   &method->g_precomp, &k0, &method->h_precomp,
+                                   &k1, NULL, NULL) ||
+      !ec_point_mul_scalar_batch(group, &jacobians[idx_Kb1], T, &k0, S, &k1,
+                                 NULL, NULL) ||
+      // co, uo, vo <- Zp
+      !ec_random_nonzero_scalar(group, &minus_co, kDefaultAdditionalData) ||
+      !ec_random_nonzero_scalar(group, &uo, kDefaultAdditionalData) ||
+      !ec_random_nonzero_scalar(group, &vo, kDefaultAdditionalData) ||
+      // Ko = uo*(G;T) + vo*(H;S) - co*(pubo;W)
+      !ec_point_mul_scalar_precomp(group, &jacobians[idx_Ko0],
+                                   &method->g_precomp, &uo, &method->h_precomp,
+                                   &vo, &pubo_precomp, &minus_co) ||
+      !ec_point_mul_scalar_batch(group, &jacobians[idx_Ko1], T, &uo, S, &vo, W,
+                                 &minus_co)) {
+    return 0;
+  }
+
+  EC_AFFINE affines[num_idx];
+  jacobians[idx_T] = *T;
+  jacobians[idx_S] = *S;
+  jacobians[idx_W] = *W;
+  jacobians[idx_Ws] = *Ws;
+  if (!ec_jacobian_to_affine_batch(group, affines, jacobians, num_idx)) {
+    return 0;
+  }
+
+  // Select the K corresponding to K0 and K1 in constant-time.
+  EC_AFFINE K00, K01, K10, K11;
+  ec_affine_select(group, &K00, mask, &affines[idx_Ko0], &affines[idx_Kb0]);
+  ec_affine_select(group, &K01, mask, &affines[idx_Ko1], &affines[idx_Kb1]);
+  ec_affine_select(group, &K10, mask, &affines[idx_Kb0], &affines[idx_Ko0]);
+  ec_affine_select(group, &K11, mask, &affines[idx_Kb1], &affines[idx_Ko1]);
+
+  // Compute c = Hc(...) for the two proofs.
+  EC_SCALAR cs, c;
+  if (!hash_c_dleq(method, &cs, &priv->pubs, &affines[idx_T], &affines[idx_S],
+                   &affines[idx_Ws], &affines[idx_Ks0], &affines[idx_Ks1]) ||
+      !hash_c_dleqor(method, &c, &priv->pub0, &priv->pub1, &affines[idx_T],
+                     &affines[idx_S], &affines[idx_W], &K00, &K01, &K10,
+                     &K11)) {
+    return 0;
+  }
+
+  // Compute cb, ub, and ub for the two proofs. In each of these products, only
+  // one operand is in Montgomery form, so the product does not need to be
+  // converted.
+
+  EC_SCALAR cs_mont;
+  ec_scalar_to_montgomery(group, &cs_mont, &cs);
+
+  // us = ks0 + cs*xs
+  EC_SCALAR us, vs;
+  ec_scalar_mul_montgomery(group, &us, &priv->xs, &cs_mont);
+  ec_scalar_add(group, &us, &ks0, &us);
+
+  // vs = ks1 + cs*ys
+  ec_scalar_mul_montgomery(group, &vs, &priv->ys, &cs_mont);
+  ec_scalar_add(group, &vs, &ks1, &vs);
+
+  // Store DLEQ2 proof in transcript.
+  if (!scalar_to_cbb(cbb, group, &cs) ||
+      !scalar_to_cbb(cbb, group, &us) ||
+      !scalar_to_cbb(cbb, group, &vs)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    return 0;
+  }
+
+  // cb = c - co
+  EC_SCALAR cb, ub, vb;
+  ec_scalar_add(group, &cb, &c, &minus_co);
+
+  EC_SCALAR cb_mont;
+  ec_scalar_to_montgomery(group, &cb_mont, &cb);
+
+  // ub = k0 + cb*xb
+  ec_scalar_mul_montgomery(group, &ub, &xb, &cb_mont);
+  ec_scalar_add(group, &ub, &k0, &ub);
+
+  // vb = k1 + cb*yb
+  ec_scalar_mul_montgomery(group, &vb, &yb, &cb_mont);
+  ec_scalar_add(group, &vb, &k1, &vb);
+
+  // Select c, u, v in constant-time.
+  EC_SCALAR co, c0, c1, u0, u1, v0, v1;
+  ec_scalar_neg(group, &co, &minus_co);
+  ec_scalar_select(group, &c0, mask, &co, &cb);
+  ec_scalar_select(group, &u0, mask, &uo, &ub);
+  ec_scalar_select(group, &v0, mask, &vo, &vb);
+  ec_scalar_select(group, &c1, mask, &cb, &co);
+  ec_scalar_select(group, &u1, mask, &ub, &uo);
+  ec_scalar_select(group, &v1, mask, &vb, &vo);
+
+  // Store DLEQOR2 proof in transcript.
+  if (!scalar_to_cbb(cbb, group, &c0) ||
+      !scalar_to_cbb(cbb, group, &c1) ||
+      !scalar_to_cbb(cbb, group, &u0) ||
+      !scalar_to_cbb(cbb, group, &u1) ||
+      !scalar_to_cbb(cbb, group, &v0) ||
+      !scalar_to_cbb(cbb, group, &v1)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    return 0;
+  }
+
+  return 1;
+}
+
+static int dleq_verify(const PMBTOKEN_METHOD *method, CBS *cbs,
+                       const PMBTOKEN_CLIENT_KEY *pub, const EC_RAW_POINT *T,
+                       const EC_RAW_POINT *S, const EC_RAW_POINT *W,
+                       const EC_RAW_POINT *Ws) {
+  const EC_GROUP *group = method->group;
+  const EC_RAW_POINT *g = &group->generator->raw;
+
+  // We verify a DLEQ proof for the validity token and a DLEQOR2 proof for the
+  // private metadata token. To allow amortizing Jacobian-to-affine conversions,
+  // we compute Ki for both proofs first. Additionally, all inputs to this
+  // function are public, so we can use the faster variable-time
+  // multiplications.
+  enum {
+    idx_T,
+    idx_S,
+    idx_W,
+    idx_Ws,
+    idx_Ks0,
+    idx_Ks1,
+    idx_K00,
+    idx_K01,
+    idx_K10,
+    idx_K11,
+    num_idx,
+  };
+  EC_RAW_POINT jacobians[num_idx];
+
+  // Decode the DLEQ proof.
+  EC_SCALAR cs, us, vs;
+  if (!scalar_from_cbs(cbs, group, &cs) ||
+      !scalar_from_cbs(cbs, group, &us) ||
+      !scalar_from_cbs(cbs, group, &vs)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
+    return 0;
+  }
+
+  // Ks = us*(G;T) + vs*(H;S) - cs*(pubs;Ws)
+  EC_RAW_POINT pubs;
+  ec_affine_to_jacobian(group, &pubs, &pub->pubs);
+  EC_SCALAR minus_cs;
+  ec_scalar_neg(group, &minus_cs, &cs);
+  if (!mul_public_3(group, &jacobians[idx_Ks0], g, &us, &method->h, &vs, &pubs,
+                    &minus_cs) ||
+      !mul_public_3(group, &jacobians[idx_Ks1], T, &us, S, &vs, Ws,
+                    &minus_cs)) {
+    return 0;
+  }
+
+  // Decode the DLEQOR proof.
+  EC_SCALAR c0, c1, u0, u1, v0, v1;
+  if (!scalar_from_cbs(cbs, group, &c0) ||
+      !scalar_from_cbs(cbs, group, &c1) ||
+      !scalar_from_cbs(cbs, group, &u0) ||
+      !scalar_from_cbs(cbs, group, &u1) ||
+      !scalar_from_cbs(cbs, group, &v0) ||
+      !scalar_from_cbs(cbs, group, &v1)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
+    return 0;
+  }
+
+  EC_RAW_POINT pub0, pub1;
+  ec_affine_to_jacobian(group, &pub0, &pub->pub0);
+  ec_affine_to_jacobian(group, &pub1, &pub->pub1);
+  EC_SCALAR minus_c0, minus_c1;
+  ec_scalar_neg(group, &minus_c0, &c0);
+  ec_scalar_neg(group, &minus_c1, &c1);
+  if (// K0 = u0*(G;T) + v0*(H;S) - c0*(pub0;W)
+      !mul_public_3(group, &jacobians[idx_K00], g, &u0, &method->h, &v0, &pub0,
+                    &minus_c0) ||
+      !mul_public_3(group, &jacobians[idx_K01], T, &u0, S, &v0, W, &minus_c0) ||
+      // K1 = u1*(G;T) + v1*(H;S) - c1*(pub1;W)
+      !mul_public_3(group, &jacobians[idx_K10], g, &u1, &method->h, &v1, &pub1,
+                    &minus_c1) ||
+      !mul_public_3(group, &jacobians[idx_K11], T, &u1, S, &v1, W, &minus_c1)) {
+    return 0;
+  }
+
+  EC_AFFINE affines[num_idx];
+  jacobians[idx_T] = *T;
+  jacobians[idx_S] = *S;
+  jacobians[idx_W] = *W;
+  jacobians[idx_Ws] = *Ws;
+  if (!ec_jacobian_to_affine_batch(group, affines, jacobians, num_idx)) {
+    return 0;
+  }
+
+  // Check the DLEQ proof.
+  EC_SCALAR calculated;
+  if (!hash_c_dleq(method, &calculated, &pub->pubs, &affines[idx_T],
+                   &affines[idx_S], &affines[idx_Ws], &affines[idx_Ks0],
+                   &affines[idx_Ks1])) {
+    return 0;
+  }
+
+  // cs == calculated
+  if (!ec_scalar_equal_vartime(group, &cs, &calculated)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_INVALID_PROOF);
+    return 0;
+  }
+
+  // Check the DLEQOR proof.
+  if (!hash_c_dleqor(method, &calculated, &pub->pub0, &pub->pub1,
+                     &affines[idx_T], &affines[idx_S], &affines[idx_W],
+                     &affines[idx_K00], &affines[idx_K01], &affines[idx_K10],
+                     &affines[idx_K11])) {
+    return 0;
+  }
+
+  // c0 + c1 == calculated
+  EC_SCALAR c;
+  ec_scalar_add(group, &c, &c0, &c1);
+  if (!ec_scalar_equal_vartime(group, &c, &calculated)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_INVALID_PROOF);
+    return 0;
+  }
+
+  return 1;
+}
+
+static int pmbtoken_sign(const PMBTOKEN_METHOD *method,
+                         const PMBTOKEN_ISSUER_KEY *key, CBB *cbb, CBS *cbs,
+                         size_t num_requested, size_t num_to_issue,
+                         uint8_t private_metadata) {
+  const EC_GROUP *group = method->group;
+  if (num_requested < num_to_issue) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_INTERNAL_ERROR);
+    return 0;
+  }
+
+  if (num_to_issue > ((size_t)-1) / sizeof(EC_RAW_POINT) ||
+      num_to_issue > ((size_t)-1) / sizeof(EC_SCALAR)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_OVERFLOW);
+    return 0;
+  }
+
+  int ret = 0;
+  EC_RAW_POINT *Tps = OPENSSL_malloc(num_to_issue * sizeof(EC_RAW_POINT));
+  EC_RAW_POINT *Sps = OPENSSL_malloc(num_to_issue * sizeof(EC_RAW_POINT));
+  EC_RAW_POINT *Wps = OPENSSL_malloc(num_to_issue * sizeof(EC_RAW_POINT));
+  EC_RAW_POINT *Wsps = OPENSSL_malloc(num_to_issue * sizeof(EC_RAW_POINT));
+  EC_SCALAR *es = OPENSSL_malloc(num_to_issue * sizeof(EC_SCALAR));
+  CBB batch_cbb;
+  CBB_zero(&batch_cbb);
+  if (!Tps ||
+      !Sps ||
+      !Wps ||
+      !Wsps ||
+      !es ||
+      !CBB_init(&batch_cbb, 0) ||
+      !point_to_cbb(&batch_cbb, method->group, &key->pubs) ||
+      !point_to_cbb(&batch_cbb, method->group, &key->pub0) ||
+      !point_to_cbb(&batch_cbb, method->group, &key->pub1)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    goto err;
+  }
+
+  for (size_t i = 0; i < num_to_issue; i++) {
+    EC_AFFINE Tp_affine;
+    EC_RAW_POINT Tp;
+    if (!cbs_get_prefixed_point(cbs, group, &Tp_affine, method->prefix_point)) {
+      OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
+      goto err;
+    }
+    ec_affine_to_jacobian(group, &Tp, &Tp_affine);
+
+    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);
+
+    uint8_t s[PMBTOKEN_NONCE_SIZE];
+    RAND_bytes(s, PMBTOKEN_NONCE_SIZE);
+    // The |jacobians| and |affines| contain Sp, Wp, and Wsp.
+    EC_RAW_POINT jacobians[3];
+    EC_AFFINE affines[3];
+    if (!method->hash_s(group, &jacobians[0], &Tp_affine, s) ||
+        !ec_point_mul_scalar_batch(group, &jacobians[1], &Tp, &xb,
+                                   &jacobians[0], &yb, NULL, NULL) ||
+        !ec_point_mul_scalar_batch(group, &jacobians[2], &Tp, &key->xs,
+                                   &jacobians[0], &key->ys, NULL, NULL) ||
+        !ec_jacobian_to_affine_batch(group, affines, jacobians, 3) ||
+        !CBB_add_bytes(cbb, s, PMBTOKEN_NONCE_SIZE) ||
+        !cbb_add_prefixed_point(cbb, group, &affines[1], method->prefix_point) ||
+        !cbb_add_prefixed_point(cbb, group, &affines[2], method->prefix_point)) {
+      goto err;
+    }
+
+    if (!point_to_cbb(&batch_cbb, group, &Tp_affine) ||
+        !point_to_cbb(&batch_cbb, group, &affines[0]) ||
+        !point_to_cbb(&batch_cbb, group, &affines[1]) ||
+        !point_to_cbb(&batch_cbb, group, &affines[2])) {
+      OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+      goto err;
+    }
+    Tps[i] = Tp;
+    Sps[i] = jacobians[0];
+    Wps[i] = jacobians[1];
+    Wsps[i] = jacobians[2];
+
+    if (!CBB_flush(cbb)) {
+      goto err;
+    }
+  }
+
+  // The DLEQ batching construction is described in appendix B of
+  // https://eprint.iacr.org/2020/072/20200324:214215. Note the additional
+  // computations all act on public inputs.
+  for (size_t i = 0; i < num_to_issue; i++) {
+    if (!hash_c_batch(method, &es[i], &batch_cbb, i)) {
+      goto err;
+    }
+  }
+
+  EC_RAW_POINT Tp_batch, Sp_batch, Wp_batch, Wsp_batch;
+  if (!ec_point_mul_scalar_public_batch(group, &Tp_batch,
+                                        /*g_scalar=*/NULL, Tps, es,
+                                        num_to_issue) ||
+      !ec_point_mul_scalar_public_batch(group, &Sp_batch,
+                                        /*g_scalar=*/NULL, Sps, es,
+                                        num_to_issue) ||
+      !ec_point_mul_scalar_public_batch(group, &Wp_batch,
+                                        /*g_scalar=*/NULL, Wps, es,
+                                        num_to_issue) ||
+      !ec_point_mul_scalar_public_batch(group, &Wsp_batch,
+                                        /*g_scalar=*/NULL, Wsps, es,
+                                        num_to_issue)) {
+    goto err;
+  }
+
+  CBB proof;
+  if (!CBB_add_u16_length_prefixed(cbb, &proof) ||
+      !dleq_generate(method, &proof, key, &Tp_batch, &Sp_batch, &Wp_batch,
+                     &Wsp_batch, private_metadata) ||
+      !CBB_flush(cbb)) {
+    goto err;
+  }
+
+  // Skip over any unused requests.
+  size_t point_len = 1 + 2 * BN_num_bytes(&group->field);
+  size_t token_len = point_len;
+  if (method->prefix_point) {
+    token_len += 2;
+  }
+  if (!CBS_skip(cbs, token_len * (num_requested - num_to_issue))) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
+    goto err;
+  }
+
+  ret = 1;
+
+err:
+  OPENSSL_free(Tps);
+  OPENSSL_free(Sps);
+  OPENSSL_free(Wps);
+  OPENSSL_free(Wsps);
+  OPENSSL_free(es);
+  CBB_cleanup(&batch_cbb);
+  return ret;
+}
+
+static STACK_OF(TRUST_TOKEN) *
+    pmbtoken_unblind(const PMBTOKEN_METHOD *method,
+                     const PMBTOKEN_CLIENT_KEY *key,
+                     const STACK_OF(PMBTOKEN_PRETOKEN) * pretokens, CBS *cbs,
+                     size_t count, uint32_t key_id) {
+  const EC_GROUP *group = method->group;
+  if (count > sk_PMBTOKEN_PRETOKEN_num(pretokens)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
+    return NULL;
+  }
+
+  int ok = 0;
+  STACK_OF(TRUST_TOKEN) *ret = sk_TRUST_TOKEN_new_null();
+  if (ret == NULL) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    return NULL;
+  }
+
+  if (count > ((size_t)-1) / sizeof(EC_RAW_POINT) ||
+      count > ((size_t)-1) / sizeof(EC_SCALAR)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_OVERFLOW);
+    return 0;
+  }
+  EC_RAW_POINT *Tps = OPENSSL_malloc(count * sizeof(EC_RAW_POINT));
+  EC_RAW_POINT *Sps = OPENSSL_malloc(count * sizeof(EC_RAW_POINT));
+  EC_RAW_POINT *Wps = OPENSSL_malloc(count * sizeof(EC_RAW_POINT));
+  EC_RAW_POINT *Wsps = OPENSSL_malloc(count * sizeof(EC_RAW_POINT));
+  EC_SCALAR *es = OPENSSL_malloc(count * sizeof(EC_SCALAR));
+  CBB batch_cbb;
+  CBB_zero(&batch_cbb);
+  if (!Tps ||
+      !Sps ||
+      !Wps ||
+      !Wsps ||
+      !es ||
+      !CBB_init(&batch_cbb, 0) ||
+      !point_to_cbb(&batch_cbb, method->group, &key->pubs) ||
+      !point_to_cbb(&batch_cbb, method->group, &key->pub0) ||
+      !point_to_cbb(&batch_cbb, method->group, &key->pub1)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    goto err;
+  }
+
+  for (size_t i = 0; i < count; i++) {
+    const PMBTOKEN_PRETOKEN *pretoken =
+        sk_PMBTOKEN_PRETOKEN_value(pretokens, i);
+
+    uint8_t s[PMBTOKEN_NONCE_SIZE];
+    EC_AFFINE Wp_affine, Wsp_affine;
+    if (!CBS_copy_bytes(cbs, s, PMBTOKEN_NONCE_SIZE) ||
+        !cbs_get_prefixed_point(cbs, group, &Wp_affine, method->prefix_point) ||
+        !cbs_get_prefixed_point(cbs, group, &Wsp_affine,
+                                method->prefix_point)) {
+      OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
+      goto err;
+    }
+
+    ec_affine_to_jacobian(group, &Tps[i], &pretoken->Tp);
+    ec_affine_to_jacobian(group, &Wps[i], &Wp_affine);
+    ec_affine_to_jacobian(group, &Wsps[i], &Wsp_affine);
+    if (!method->hash_s(group, &Sps[i], &pretoken->Tp, s)) {
+      goto err;
+    }
+
+    EC_AFFINE Sp_affine;
+    if (!point_to_cbb(&batch_cbb, group, &pretoken->Tp) ||
+        !ec_jacobian_to_affine(group, &Sp_affine, &Sps[i]) ||
+        !point_to_cbb(&batch_cbb, group, &Sp_affine) ||
+        !point_to_cbb(&batch_cbb, group, &Wp_affine) ||
+        !point_to_cbb(&batch_cbb, group, &Wsp_affine)) {
+      OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+      goto err;
+    }
+
+    // Unblind the token.
+    EC_RAW_POINT jacobians[3];
+    EC_AFFINE affines[3];
+    if (!ec_point_mul_scalar(group, &jacobians[0], &Sps[i], &pretoken->r) ||
+        !ec_point_mul_scalar(group, &jacobians[1], &Wps[i], &pretoken->r) ||
+        !ec_point_mul_scalar(group, &jacobians[2], &Wsps[i], &pretoken->r) ||
+        !ec_jacobian_to_affine_batch(group, affines, jacobians, 3)) {
+      goto err;
+    }
+
+    // Serialize the token. Include |key_id| to avoid an extra copy in the layer
+    // above.
+    CBB token_cbb;
+    size_t point_len = 1 + 2 * BN_num_bytes(&group->field);
+    if (!CBB_init(&token_cbb, 4 + PMBTOKEN_NONCE_SIZE + 3 * (2 + point_len)) ||
+        !CBB_add_u32(&token_cbb, key_id) ||
+        !CBB_add_bytes(&token_cbb, pretoken->t, PMBTOKEN_NONCE_SIZE) ||
+        !cbb_add_prefixed_point(&token_cbb, group, &affines[0],
+                                method->prefix_point) ||
+        !cbb_add_prefixed_point(&token_cbb, group, &affines[1],
+                                method->prefix_point) ||
+        !cbb_add_prefixed_point(&token_cbb, group, &affines[2],
+                                method->prefix_point) ||
+        !CBB_flush(&token_cbb)) {
+      CBB_cleanup(&token_cbb);
+      goto err;
+    }
+
+    TRUST_TOKEN *token =
+        TRUST_TOKEN_new(CBB_data(&token_cbb), CBB_len(&token_cbb));
+    CBB_cleanup(&token_cbb);
+    if (token == NULL ||
+        !sk_TRUST_TOKEN_push(ret, token)) {
+      OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+      TRUST_TOKEN_free(token);
+      goto err;
+    }
+  }
+
+  // The DLEQ batching construction is described in appendix B of
+  // https://eprint.iacr.org/2020/072/20200324:214215. Note the additional
+  // computations all act on public inputs.
+  for (size_t i = 0; i < count; i++) {
+    if (!hash_c_batch(method, &es[i], &batch_cbb, i)) {
+      goto err;
+    }
+  }
+
+  EC_RAW_POINT Tp_batch, Sp_batch, Wp_batch, Wsp_batch;
+  if (!ec_point_mul_scalar_public_batch(group, &Tp_batch,
+                                        /*g_scalar=*/NULL, Tps, es, count) ||
+      !ec_point_mul_scalar_public_batch(group, &Sp_batch,
+                                        /*g_scalar=*/NULL, Sps, es, count) ||
+      !ec_point_mul_scalar_public_batch(group, &Wp_batch,
+                                        /*g_scalar=*/NULL, Wps, es, count) ||
+      !ec_point_mul_scalar_public_batch(group, &Wsp_batch,
+                                        /*g_scalar=*/NULL, Wsps, es, count)) {
+    goto err;
+  }
+
+  CBS proof;
+  if (!CBS_get_u16_length_prefixed(cbs, &proof) ||
+      !dleq_verify(method, &proof, key, &Tp_batch, &Sp_batch, &Wp_batch,
+                   &Wsp_batch) ||
+      CBS_len(&proof) != 0) {
+    goto err;
+  }
+
+  ok = 1;
+
+err:
+  OPENSSL_free(Tps);
+  OPENSSL_free(Sps);
+  OPENSSL_free(Wps);
+  OPENSSL_free(Wsps);
+  OPENSSL_free(es);
+  CBB_cleanup(&batch_cbb);
+  if (!ok) {
+    sk_TRUST_TOKEN_pop_free(ret, TRUST_TOKEN_free);
+    ret = NULL;
+  }
+  return ret;
+}
+
+static int pmbtoken_read(const PMBTOKEN_METHOD *method,
+                         const PMBTOKEN_ISSUER_KEY *key,
+                         uint8_t out_nonce[PMBTOKEN_NONCE_SIZE],
+                         uint8_t *out_private_metadata, const uint8_t *token,
+                         size_t token_len) {
+  const EC_GROUP *group = method->group;
+  CBS cbs;
+  CBS_init(&cbs, token, token_len);
+  EC_AFFINE S, W, Ws;
+  if (!CBS_copy_bytes(&cbs, out_nonce, PMBTOKEN_NONCE_SIZE) ||
+      !cbs_get_prefixed_point(&cbs, group, &S, method->prefix_point) ||
+      !cbs_get_prefixed_point(&cbs, group, &W, method->prefix_point) ||
+      !cbs_get_prefixed_point(&cbs, group, &Ws, method->prefix_point) ||
+      CBS_len(&cbs) != 0) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_INVALID_TOKEN);
+    return 0;
+  }
+
+
+  EC_RAW_POINT T;
+  if (!method->hash_t(group, &T, out_nonce)) {
+    return 0;
+  }
+
+  // We perform three multiplications with S and T. This is enough that it is
+  // worth using |ec_point_mul_scalar_precomp|.
+  EC_RAW_POINT S_jacobian;
+  EC_PRECOMP S_precomp, T_precomp;
+  ec_affine_to_jacobian(group, &S_jacobian, &S);
+  if (!ec_init_precomp(group, &S_precomp, &S_jacobian) ||
+      !ec_init_precomp(group, &T_precomp, &T)) {
+    return 0;
+  }
+
+  EC_RAW_POINT Ws_calculated;
+  // Check the validity of the token.
+  if (!ec_point_mul_scalar_precomp(group, &Ws_calculated, &T_precomp, &key->xs,
+                                   &S_precomp, &key->ys, NULL, NULL) ||
+      !ec_affine_jacobian_equal(group, &Ws, &Ws_calculated)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_BAD_VALIDITY_CHECK);
+    return 0;
+  }
+
+  EC_RAW_POINT W0, W1;
+  if (!ec_point_mul_scalar_precomp(group, &W0, &T_precomp, &key->x0, &S_precomp,
+                                   &key->y0, NULL, NULL) ||
+      !ec_point_mul_scalar_precomp(group, &W1, &T_precomp, &key->x1, &S_precomp,
+                                   &key->y1, NULL, NULL)) {
+    return 0;
+  }
+
+  const int is_W0 = ec_affine_jacobian_equal(group, &W, &W0);
+  const int is_W1 = ec_affine_jacobian_equal(group, &W, &W1);
+  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;
+}
+
+
+// PMBTokens experiment v1.
+
+static int pmbtoken_exp1_hash_t(const EC_GROUP *group, EC_RAW_POINT *out,
+                                const uint8_t t[PMBTOKEN_NONCE_SIZE]) {
+  const uint8_t kHashTLabel[] = "PMBTokens Experiment V1 HashT";
+  return ec_hash_to_curve_p384_xmd_sha512_sswu_draft07(
+      group, out, kHashTLabel, sizeof(kHashTLabel), t, PMBTOKEN_NONCE_SIZE);
+}
+
+static int pmbtoken_exp1_hash_s(const EC_GROUP *group, EC_RAW_POINT *out,
+                                const EC_AFFINE *t,
+                                const uint8_t s[PMBTOKEN_NONCE_SIZE]) {
+  const uint8_t kHashSLabel[] = "PMBTokens Experiment 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, PMBTOKEN_NONCE_SIZE) ||
+      !CBB_finish(&cbb, &buf, &len) ||
+      !ec_hash_to_curve_p384_xmd_sha512_sswu_draft07(
+          group, out, kHashSLabel, sizeof(kHashSLabel), buf, len)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    goto err;
+  }
+
+  ret = 1;
+
+err:
+  OPENSSL_free(buf);
+  CBB_cleanup(&cbb);
+  return ret;
+}
+
+static int pmbtoken_exp1_hash_c(const EC_GROUP *group, EC_SCALAR *out,
+                                uint8_t *buf, size_t len) {
+  const uint8_t kHashCLabel[] = "PMBTokens Experiment V1 HashC";
+  return ec_hash_to_scalar_p384_xmd_sha512_draft07(
+      group, out, kHashCLabel, sizeof(kHashCLabel), buf, len);
+}
+
+static int pmbtoken_exp1_ok = 0;
+static PMBTOKEN_METHOD pmbtoken_exp1_method;
+static CRYPTO_once_t pmbtoken_exp1_method_once = CRYPTO_ONCE_INIT;
+
+static void pmbtoken_exp1_init_method_impl(void) {
+  // This is the output of |ec_hash_to_scalar_p384_xmd_sha512_draft07| with DST
+  // "PMBTokens Experiment V1 HashH" and message "generator".
+  static const uint8_t kH[] = {
+      0x04, 0x82, 0xd5, 0x68, 0xf5, 0x39, 0xf6, 0x08, 0x19, 0xa1, 0x75,
+      0x9f, 0x98, 0xb5, 0x10, 0xf5, 0x0b, 0x9d, 0x2b, 0xe1, 0x64, 0x4d,
+      0x02, 0x76, 0x18, 0x11, 0xf8, 0x2f, 0xd3, 0x33, 0x25, 0x1f, 0x2c,
+      0xb8, 0xf6, 0xf1, 0x9e, 0x93, 0x85, 0x79, 0xb3, 0xb7, 0x81, 0xa3,
+      0xe6, 0x23, 0xc3, 0x1c, 0xff, 0x03, 0xd9, 0x40, 0x6c, 0xec, 0xe0,
+      0x4d, 0xea, 0xdf, 0x9d, 0x94, 0xd1, 0x87, 0xab, 0x27, 0xf7, 0x4f,
+      0x53, 0xea, 0xa3, 0x18, 0x72, 0xb9, 0xd1, 0x56, 0xa0, 0x4e, 0x81,
+      0xaa, 0xeb, 0x1c, 0x22, 0x6d, 0x39, 0x1c, 0x5e, 0xb1, 0x27, 0xfc,
+      0x87, 0xc3, 0x95, 0xd0, 0x13, 0xb7, 0x0b, 0x5c, 0xc7,
+  };
+
+  pmbtoken_exp1_ok =
+      pmbtoken_init_method(&pmbtoken_exp1_method, NID_secp384r1, kH, sizeof(kH),
+                           pmbtoken_exp1_hash_t, pmbtoken_exp1_hash_s,
+                           pmbtoken_exp1_hash_c, 1);
+}
+
+static int pmbtoken_exp1_init_method(void) {
+  CRYPTO_once(&pmbtoken_exp1_method_once, pmbtoken_exp1_init_method_impl);
+  if (!pmbtoken_exp1_ok) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_INTERNAL_ERROR);
+    return 0;
+  }
+  return 1;
+}
+
+int pmbtoken_exp1_generate_key(CBB *out_private, CBB *out_public) {
+  if (!pmbtoken_exp1_init_method()) {
+    return 0;
+  }
+
+  return pmbtoken_generate_key(&pmbtoken_exp1_method, out_private, out_public);
+}
+
+int pmbtoken_exp1_client_key_from_bytes(PMBTOKEN_CLIENT_KEY *key,
+                                        const uint8_t *in, size_t len) {
+  if (!pmbtoken_exp1_init_method()) {
+    return 0;
+  }
+  return pmbtoken_client_key_from_bytes(&pmbtoken_exp1_method, key, in, len);
+}
+
+int pmbtoken_exp1_issuer_key_from_bytes(PMBTOKEN_ISSUER_KEY *key,
+                                        const uint8_t *in, size_t len) {
+  if (!pmbtoken_exp1_init_method()) {
+    return 0;
+  }
+  return pmbtoken_issuer_key_from_bytes(&pmbtoken_exp1_method, key, in, len);
+}
+
+STACK_OF(PMBTOKEN_PRETOKEN) * pmbtoken_exp1_blind(CBB *cbb, size_t count) {
+  if (!pmbtoken_exp1_init_method()) {
+    return NULL;
+  }
+  return pmbtoken_blind(&pmbtoken_exp1_method, cbb, count);
+}
+
+int pmbtoken_exp1_sign(const PMBTOKEN_ISSUER_KEY *key, CBB *cbb, CBS *cbs,
+                       size_t num_requested, size_t num_to_issue,
+                       uint8_t private_metadata) {
+  if (!pmbtoken_exp1_init_method()) {
+    return 0;
+  }
+  return pmbtoken_sign(&pmbtoken_exp1_method, key, cbb, cbs, num_requested,
+                       num_to_issue, private_metadata);
+}
+
+STACK_OF(TRUST_TOKEN) *
+    pmbtoken_exp1_unblind(const PMBTOKEN_CLIENT_KEY *key,
+                          const STACK_OF(PMBTOKEN_PRETOKEN) * pretokens,
+                          CBS *cbs, size_t count, uint32_t key_id) {
+  if (!pmbtoken_exp1_init_method()) {
+    return NULL;
+  }
+  return pmbtoken_unblind(&pmbtoken_exp1_method, key, pretokens, cbs, count,
+                          key_id);
+}
+
+int pmbtoken_exp1_read(const PMBTOKEN_ISSUER_KEY *key,
+                       uint8_t out_nonce[PMBTOKEN_NONCE_SIZE],
+                       uint8_t *out_private_metadata, const uint8_t *token,
+                       size_t token_len) {
+  if (!pmbtoken_exp1_init_method()) {
+    return 0;
+  }
+  return pmbtoken_read(&pmbtoken_exp1_method, key, out_nonce,
+                       out_private_metadata, token, token_len);
+}
+
+int pmbtoken_exp1_get_h_for_testing(uint8_t out[97]) {
+  if (!pmbtoken_exp1_init_method()) {
+    return 0;
+  }
+  EC_AFFINE h;
+  return ec_jacobian_to_affine(pmbtoken_exp1_method.group, &h,
+                               &pmbtoken_exp1_method.h) &&
+         ec_point_to_bytes(pmbtoken_exp1_method.group, &h,
+                           POINT_CONVERSION_UNCOMPRESSED, out, 97) == 97;
+}
+
+// PMBTokens experiment v2.
+
+static int pmbtoken_exp2_hash_t(const EC_GROUP *group, EC_RAW_POINT *out,
+                                const uint8_t t[PMBTOKEN_NONCE_SIZE]) {
+  const uint8_t kHashTLabel[] = "PMBTokens Experiment V2 HashT";
+  return ec_hash_to_curve_p384_xmd_sha512_sswu_draft07(
+      group, out, kHashTLabel, sizeof(kHashTLabel), t, PMBTOKEN_NONCE_SIZE);
+}
+
+static int pmbtoken_exp2_hash_s(const EC_GROUP *group, EC_RAW_POINT *out,
+                                const EC_AFFINE *t,
+                                const uint8_t s[PMBTOKEN_NONCE_SIZE]) {
+  const uint8_t kHashSLabel[] = "PMBTokens Experiment V2 HashS";
+  int ret = 0;
+  CBB cbb;
+  uint8_t *buf = NULL;
+  size_t len;
+  if (!CBB_init(&cbb, 0) ||
+      !point_to_cbb(&cbb, group, t) ||
+      !CBB_add_bytes(&cbb, s, PMBTOKEN_NONCE_SIZE) ||
+      !CBB_finish(&cbb, &buf, &len) ||
+      !ec_hash_to_curve_p384_xmd_sha512_sswu_draft07(
+          group, out, kHashSLabel, sizeof(kHashSLabel), buf, len)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    goto err;
+  }
+
+  ret = 1;
+
+err:
+  OPENSSL_free(buf);
+  CBB_cleanup(&cbb);
+  return ret;
+}
+
+static int pmbtoken_exp2_hash_c(const EC_GROUP *group, EC_SCALAR *out,
+                                uint8_t *buf, size_t len) {
+  const uint8_t kHashCLabel[] = "PMBTokens Experiment V2 HashC";
+  return ec_hash_to_scalar_p384_xmd_sha512_draft07(
+      group, out, kHashCLabel, sizeof(kHashCLabel), buf, len);
+}
+
+static int pmbtoken_exp2_ok = 0;
+static PMBTOKEN_METHOD pmbtoken_exp2_method;
+static CRYPTO_once_t pmbtoken_exp2_method_once = CRYPTO_ONCE_INIT;
+
+static void pmbtoken_exp2_init_method_impl(void) {
+  // This is the output of |ec_hash_to_scalar_p384_xmd_sha512_draft07| with DST
+  // "PMBTokens Experiment V2 HashH" and message "generator".
+  static const uint8_t kH[] = {
+      0x04, 0xbc, 0x27, 0x24, 0x99, 0xfa, 0xc9, 0xa4, 0x74, 0x6f, 0xf9,
+      0x07, 0x81, 0x55, 0xf8, 0x1f, 0x6f, 0xda, 0x09, 0xe7, 0x8c, 0x5d,
+      0x9e, 0x4e, 0x14, 0x7c, 0x53, 0x14, 0xbc, 0x7e, 0x29, 0x57, 0x92,
+      0x17, 0x94, 0x6e, 0xd2, 0xdf, 0xa5, 0x31, 0x1b, 0x4e, 0xb7, 0xfc,
+      0x93, 0xe3, 0x6e, 0x14, 0x1f, 0x4f, 0x14, 0xf3, 0xe5, 0x47, 0x61,
+      0x1c, 0x2c, 0x72, 0x25, 0xf0, 0x4a, 0x45, 0x23, 0x2d, 0x57, 0x93,
+      0x0e, 0xb2, 0x55, 0xb8, 0x57, 0x25, 0x4c, 0x1e, 0xdb, 0xfd, 0x58,
+      0x70, 0x17, 0x9a, 0xbb, 0x9e, 0x5e, 0x93, 0x9e, 0x92, 0xd3, 0xe8,
+      0x25, 0x62, 0xbf, 0x59, 0xb2, 0xd2, 0x3d, 0x71, 0xff
+  };
+
+  pmbtoken_exp2_ok =
+      pmbtoken_init_method(&pmbtoken_exp2_method, NID_secp384r1, kH, sizeof(kH),
+                           pmbtoken_exp2_hash_t, pmbtoken_exp2_hash_s,
+                           pmbtoken_exp2_hash_c, 0);
+}
+
+static int pmbtoken_exp2_init_method(void) {
+  CRYPTO_once(&pmbtoken_exp2_method_once, pmbtoken_exp2_init_method_impl);
+  if (!pmbtoken_exp2_ok) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_INTERNAL_ERROR);
+    return 0;
+  }
+  return 1;
+}
+
+int pmbtoken_exp2_generate_key(CBB *out_private, CBB *out_public) {
+  if (!pmbtoken_exp2_init_method()) {
+    return 0;
+  }
+
+  return pmbtoken_generate_key(&pmbtoken_exp2_method, out_private, out_public);
+}
+
+int pmbtoken_exp2_client_key_from_bytes(PMBTOKEN_CLIENT_KEY *key,
+                                        const uint8_t *in, size_t len) {
+  if (!pmbtoken_exp2_init_method()) {
+    return 0;
+  }
+  return pmbtoken_client_key_from_bytes(&pmbtoken_exp2_method, key, in, len);
+}
+
+int pmbtoken_exp2_issuer_key_from_bytes(PMBTOKEN_ISSUER_KEY *key,
+                                        const uint8_t *in, size_t len) {
+  if (!pmbtoken_exp2_init_method()) {
+    return 0;
+  }
+  return pmbtoken_issuer_key_from_bytes(&pmbtoken_exp2_method, key, in, len);
+}
+
+STACK_OF(PMBTOKEN_PRETOKEN) * pmbtoken_exp2_blind(CBB *cbb, size_t count) {
+  if (!pmbtoken_exp2_init_method()) {
+    return NULL;
+  }
+  return pmbtoken_blind(&pmbtoken_exp2_method, cbb, count);
+}
+
+int pmbtoken_exp2_sign(const PMBTOKEN_ISSUER_KEY *key, CBB *cbb, CBS *cbs,
+                       size_t num_requested, size_t num_to_issue,
+                       uint8_t private_metadata) {
+  if (!pmbtoken_exp2_init_method()) {
+    return 0;
+  }
+  return pmbtoken_sign(&pmbtoken_exp2_method, key, cbb, cbs, num_requested,
+                       num_to_issue, private_metadata);
+}
+
+STACK_OF(TRUST_TOKEN) *
+    pmbtoken_exp2_unblind(const PMBTOKEN_CLIENT_KEY *key,
+                          const STACK_OF(PMBTOKEN_PRETOKEN) * pretokens,
+                          CBS *cbs, size_t count, uint32_t key_id) {
+  if (!pmbtoken_exp2_init_method()) {
+    return NULL;
+  }
+  return pmbtoken_unblind(&pmbtoken_exp2_method, key, pretokens, cbs, count,
+                          key_id);
+}
+
+int pmbtoken_exp2_read(const PMBTOKEN_ISSUER_KEY *key,
+                       uint8_t out_nonce[PMBTOKEN_NONCE_SIZE],
+                       uint8_t *out_private_metadata, const uint8_t *token,
+                       size_t token_len) {
+  if (!pmbtoken_exp2_init_method()) {
+    return 0;
+  }
+  return pmbtoken_read(&pmbtoken_exp2_method, key, out_nonce,
+                       out_private_metadata, token, token_len);
+}
+
+int pmbtoken_exp2_get_h_for_testing(uint8_t out[97]) {
+  if (!pmbtoken_exp2_init_method()) {
+    return 0;
+  }
+  EC_AFFINE h;
+  return ec_jacobian_to_affine(pmbtoken_exp2_method.group, &h,
+                               &pmbtoken_exp2_method.h) &&
+         ec_point_to_bytes(pmbtoken_exp2_method.group, &h,
+                           POINT_CONVERSION_UNCOMPRESSED, out, 97) == 97;
+}
diff --git a/src/crypto/trust_token/trust_token.c b/src/crypto/trust_token/trust_token.c
new file mode 100644
index 0000000..fea619e
--- /dev/null
+++ b/src/crypto/trust_token/trust_token.c
@@ -0,0 +1,726 @@
+/* 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.
+
+const TRUST_TOKEN_METHOD *TRUST_TOKEN_experiment_v1(void) {
+  static const TRUST_TOKEN_METHOD kMethod = {
+      pmbtoken_exp1_generate_key,
+      pmbtoken_exp1_client_key_from_bytes,
+      pmbtoken_exp1_issuer_key_from_bytes,
+      pmbtoken_exp1_blind,
+      pmbtoken_exp1_sign,
+      pmbtoken_exp1_unblind,
+      pmbtoken_exp1_read,
+      1, /* has_private_metadata */
+      3, /* max_keys */
+      1, /* has_srr */
+  };
+  return &kMethod;
+}
+
+const TRUST_TOKEN_METHOD *TRUST_TOKEN_experiment_v2_pp(void) {
+  static const TRUST_TOKEN_METHOD kMethod = {
+      pmbtoken_exp2_generate_key,
+      pmbtoken_exp2_client_key_from_bytes,
+      pmbtoken_exp2_issuer_key_from_bytes,
+      pmbtoken_exp2_blind,
+      pmbtoken_exp2_sign,
+      pmbtoken_exp2_unblind,
+      pmbtoken_exp2_read,
+      0, /* has_private_metadata */
+      6, /* max_keys */
+      0, /* has_srr */
+  };
+  return &kMethod;
+}
+
+const TRUST_TOKEN_METHOD *TRUST_TOKEN_experiment_v2_pmb(void) {
+  static const TRUST_TOKEN_METHOD kMethod = {
+      pmbtoken_exp2_generate_key,
+      pmbtoken_exp2_client_key_from_bytes,
+      pmbtoken_exp2_issuer_key_from_bytes,
+      pmbtoken_exp2_blind,
+      pmbtoken_exp2_sign,
+      pmbtoken_exp2_unblind,
+      pmbtoken_exp2_read,
+      1, /* has_private_metadata */
+      3, /* max_keys */
+      0, /* has_srr */
+  };
+  return &kMethod;
+}
+
+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);
+}
+
+int TRUST_TOKEN_generate_key(const TRUST_TOKEN_METHOD *method,
+                             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) {
+  // Prepend the key ID in front of the PMBTokens format.
+  int ret = 0;
+  CBB priv_cbb, pub_cbb;
+  CBB_zero(&priv_cbb);
+  CBB_zero(&pub_cbb);
+  if (!CBB_init_fixed(&priv_cbb, out_priv_key, max_priv_key_len) ||
+      !CBB_init_fixed(&pub_cbb, out_pub_key, max_pub_key_len) ||
+      !CBB_add_u32(&priv_cbb, id) ||
+      !CBB_add_u32(&pub_cbb, id)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_BUFFER_TOO_SMALL);
+    goto err;
+  }
+
+  if (!method->generate_key(&priv_cbb, &pub_cbb)) {
+    goto err;
+  }
+
+  if (!CBB_finish(&priv_cbb, NULL, out_priv_key_len) ||
+      !CBB_finish(&pub_cbb, NULL, out_pub_key_len)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_BUFFER_TOO_SMALL);
+    goto err;
+  }
+
+  ret = 1;
+
+err:
+  CBB_cleanup(&priv_cbb);
+  CBB_cleanup(&pub_cbb);
+  return ret;
+}
+
+TRUST_TOKEN_CLIENT *TRUST_TOKEN_CLIENT_new(const TRUST_TOKEN_METHOD *method,
+                                           size_t max_batchsize) {
+  if (max_batchsize > 0xffff) {
+    // The protocol supports only two-byte token counts.
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_OVERFLOW);
+    return NULL;
+  }
+
+  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->method = method;
+  ret->max_batchsize = (uint16_t)max_batchsize;
+  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) {
+  if (ctx->num_keys == OPENSSL_ARRAY_SIZE(ctx->keys) ||
+      ctx->num_keys >= ctx->method->max_keys) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_TOO_MANY_KEYS);
+    return 0;
+  }
+
+  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) ||
+      !ctx->method->client_key_from_bytes(&key_s->key, CBS_data(&cbs),
+                                          CBS_len(&cbs))) {
+    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) {
+  if (!ctx->method->has_srr) {
+    return 1;
+  }
+  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) {
+  if (count > ctx->max_batchsize) {
+    count = ctx->max_batchsize;
+  }
+
+  int ret = 0;
+  CBB request;
+  STACK_OF(PMBTOKEN_PRETOKEN) *pretokens = NULL;
+  if (!CBB_init(&request, 0) ||
+      !CBB_add_u16(&request, count)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    goto err;
+  }
+
+  pretokens = ctx->method->blind(&request, count);
+  if (pretokens == NULL) {
+    goto err;
+  }
+
+  if (!CBB_finish(&request, out, out_len)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    goto err;
+  }
+
+  sk_PMBTOKEN_PRETOKEN_pop_free(ctx->pretokens, PMBTOKEN_PRETOKEN_free);
+  ctx->pretokens = pretokens;
+  pretokens = NULL;
+  ret = 1;
+
+err:
+  CBB_cleanup(&request);
+  sk_PMBTOKEN_PRETOKEN_pop_free(pretokens, PMBTOKEN_PRETOKEN_free);
+  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) {
+  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;
+  }
+
+  STACK_OF(TRUST_TOKEN) *tokens =
+      ctx->method->unblind(&key->key, ctx->pretokens, &in, count, key_id);
+  if (tokens == NULL) {
+    return NULL;
+  }
+
+  if (CBS_len(&in) != 0) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
+    sk_TRUST_TOKEN_pop_free(tokens, TRUST_TOKEN_free);
+    return NULL;
+  }
+
+  sk_PMBTOKEN_PRETOKEN_pop_free(ctx->pretokens, PMBTOKEN_PRETOKEN_free);
+  ctx->pretokens = NULL;
+
+  *out_key_index = key_index;
+  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_rr, size_t *out_rr_len,
+                                         uint8_t **out_sig, size_t *out_sig_len,
+                                         const uint8_t *response,
+                                         size_t response_len) {
+  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;
+  }
+
+  if (ctx->method->has_srr) {
+    if (ctx->srr_key == NULL) {
+      OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_NO_SRR_KEY_CONFIGURED);
+      return 0;
+    }
+
+    EVP_MD_CTX md_ctx;
+    EVP_MD_CTX_init(&md_ctx);
+    int sig_ok =
+        EVP_DigestVerifyInit(&md_ctx, NULL, NULL, NULL, ctx->srr_key) &&
+        EVP_DigestVerify(&md_ctx, CBS_data(&sig), CBS_len(&sig), CBS_data(&srr),
+                         CBS_len(&srr));
+    EVP_MD_CTX_cleanup(&md_ctx);
+
+    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_rr = srr_buf;
+  *out_rr_len = srr_len;
+  *out_sig = sig_buf;
+  *out_sig_len = sig_len;
+  return 1;
+}
+
+TRUST_TOKEN_ISSUER *TRUST_TOKEN_ISSUER_new(const TRUST_TOKEN_METHOD *method,
+                                           size_t max_batchsize) {
+  if (max_batchsize > 0xffff) {
+    // The protocol supports only two-byte token counts.
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_OVERFLOW);
+    return NULL;
+  }
+
+  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->method = method;
+  ret->max_batchsize = (uint16_t)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) {
+  if (ctx->num_keys == OPENSSL_ARRAY_SIZE(ctx->keys) ||
+      ctx->num_keys >= ctx->method->max_keys) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_TOO_MANY_KEYS);
+    return 0;
+  }
+
+  struct trust_token_issuer_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) ||
+      !ctx->method->issuer_key_from_bytes(&key_s->key, CBS_data(&cbs),
+                                          CBS_len(&cbs))) {
+    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;
+}
+
+static const struct trust_token_issuer_key_st *trust_token_issuer_get_key(
+    const TRUST_TOKEN_ISSUER *ctx, uint32_t key_id) {
+  for (size_t i = 0; i < ctx->num_keys; i++) {
+    if (ctx->keys[i].id == key_id) {
+      return &ctx->keys[i];
+    }
+  }
+  return NULL;
+}
+
+int TRUST_TOKEN_ISSUER_issue(const TRUST_TOKEN_ISSUER *ctx, uint8_t **out,
+                             size_t *out_len, size_t *out_tokens_issued,
+                             const uint8_t *request, size_t request_len,
+                             uint32_t public_metadata, uint8_t private_metadata,
+                             size_t max_issuance) {
+  if (max_issuance > ctx->max_batchsize) {
+    max_issuance = ctx->max_batchsize;
+  }
+
+  const struct trust_token_issuer_key_st *key =
+      trust_token_issuer_get_key(ctx, public_metadata);
+  if (key == NULL || private_metadata > 1 ||
+      (!ctx->method->has_private_metadata && private_metadata != 0)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_INVALID_METADATA);
+    return 0;
+  }
+
+  CBS in;
+  uint16_t num_requested;
+  CBS_init(&in, request, request_len);
+  if (!CBS_get_u16(&in, &num_requested)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
+    return 0;
+  }
+
+  size_t num_to_issue = num_requested;
+  if (num_to_issue > max_issuance) {
+    num_to_issue = max_issuance;
+  }
+
+  int ret = 0;
+  CBB response;
+  if (!CBB_init(&response, 0) ||
+      !CBB_add_u16(&response, num_to_issue) ||
+      !CBB_add_u32(&response, public_metadata)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    goto err;
+  }
+
+  if (!ctx->method->sign(&key->key, &response, &in, num_requested, num_to_issue,
+                         private_metadata)) {
+    goto err;
+  }
+
+  if (CBS_len(&in) != 0) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
+    goto err;
+  }
+
+  if (!CBB_finish(&response, out, out_len)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    goto err;
+  }
+
+  *out_tokens_issued = num_to_issue;
+  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_bytes(CBB *cbb, const uint8_t *data, size_t len) {
+  return add_cbor_int_with_type(cbb, 0x40, len) &&
+         CBB_add_bytes(cbb, data, len);
+}
+
+// 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) {
+  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;
+
+  CBS token_copy = token_cbs;
+
+  // Parse the token. If there is an error, treat it as an invalid token.
+  if (!CBS_get_u32(&token_cbs, &public_metadata)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_INVALID_TOKEN);
+    return 0;
+  }
+
+  const struct trust_token_issuer_key_st *key =
+      trust_token_issuer_get_key(ctx, public_metadata);
+  uint8_t nonce[PMBTOKEN_NONCE_SIZE];
+  if (key == NULL ||
+      !ctx->method->read(&key->key, nonce, &private_metadata,
+                         CBS_data(&token_cbs), CBS_len(&token_cbs))) {
+    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;
+  }
+
+  const uint8_t kTokenHashDSTLabel[] = "TrustTokenV0 TokenHash";
+  uint8_t token_hash[SHA256_DIGEST_LENGTH];
+  SHA256_CTX sha_ctx;
+  SHA256_Init(&sha_ctx);
+  SHA256_Update(&sha_ctx, kTokenHashDSTLabel, sizeof(kTokenHashDSTLabel));
+  SHA256_Update(&sha_ctx, CBS_data(&token_copy), CBS_len(&token_copy));
+  SHA256_Final(token_hash, &sha_ctx);
+
+  uint8_t metadata_obfuscator = get_metadata_obfuscator(
+      ctx->metadata_key, ctx->metadata_key_len, token_hash, sizeof(token_hash));
+
+  // 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";
+  static const char kTokenHashLabel[] = "token-hash";
+
+  // CBOR requires map keys to be sorted by length then sorted lexically.
+  // https://tools.ietf.org/html/rfc7049#section-3.9
+  assert(strlen(kMetadataLabel) < strlen(kTokenHashLabel));
+  assert(strlen(kTokenHashLabel) < strlen(kClientDataLabel));
+  assert(strlen(kClientDataLabel) < strlen(kExpiryTimestampLabel));
+  assert(strlen(kPublicLabel) < strlen(kPrivateLabel));
+
+  size_t map_entries = 4;
+
+  if (!CBB_init(&srr, 0) ||
+      !add_cbor_map(&srr, map_entries) ||  // SRR map
+      !add_cbor_text(&srr, kMetadataLabel, strlen(kMetadataLabel)) ||
+      !add_cbor_map(&srr, 2) ||  // Metadata map
+      !add_cbor_text(&srr, kPublicLabel, strlen(kPublicLabel)) ||
+      !add_cbor_int(&srr, public_metadata) ||
+      !add_cbor_text(&srr, kPrivateLabel, strlen(kPrivateLabel)) ||
+      !add_cbor_int(&srr, private_metadata ^ metadata_obfuscator) ||
+      !add_cbor_text(&srr, kTokenHashLabel, strlen(kTokenHashLabel)) ||
+      !add_cbor_bytes(&srr, token_hash, sizeof(token_hash)) ||
+      !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) ||
+      !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(nonce, 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(const TRUST_TOKEN_METHOD *method,
+                                        uint8_t *out_value, const uint8_t *key,
+                                        size_t key_len, const uint8_t *nonce,
+                                        size_t nonce_len,
+                                        uint8_t encrypted_bit) {
+  uint8_t metadata_obfuscator =
+      get_metadata_obfuscator(key, key_len, nonce, nonce_len);
+  *out_value = encrypted_bit ^ metadata_obfuscator;
+  return 1;
+}
diff --git a/src/crypto/trust_token/trust_token_test.cc b/src/crypto/trust_token/trust_token_test.cc
new file mode 100644
index 0000000..b282500
--- /dev/null
+++ b/src/crypto/trust_token/trust_token_test.cc
@@ -0,0 +1,781 @@
+/* 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 <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/sha.h>
+#include <openssl/trust_token.h>
+
+#include "../ec_extra/internal.h"
+#include "../fipsmodule/ec/internal.h"
+#include "../internal.h"
+#include "../test/test_util.h"
+#include "internal.h"
+
+
+BSSL_NAMESPACE_BEGIN
+
+namespace {
+
+TEST(TrustTokenTest, KeyGenExp1) {
+  uint8_t priv_key[TRUST_TOKEN_MAX_PRIVATE_KEY_SIZE];
+  uint8_t pub_key[TRUST_TOKEN_MAX_PUBLIC_KEY_SIZE];
+  size_t priv_key_len, pub_key_len;
+  ASSERT_TRUE(TRUST_TOKEN_generate_key(
+      TRUST_TOKEN_experiment_v1(), priv_key, &priv_key_len,
+      TRUST_TOKEN_MAX_PRIVATE_KEY_SIZE, pub_key, &pub_key_len,
+      TRUST_TOKEN_MAX_PUBLIC_KEY_SIZE, 0x0001));
+  ASSERT_EQ(292u, priv_key_len);
+  ASSERT_EQ(301u, pub_key_len);
+}
+
+TEST(TrustTokenTest, KeyGenExp2PP) {
+  uint8_t priv_key[TRUST_TOKEN_MAX_PRIVATE_KEY_SIZE];
+  uint8_t pub_key[TRUST_TOKEN_MAX_PUBLIC_KEY_SIZE];
+  size_t priv_key_len, pub_key_len;
+  ASSERT_TRUE(TRUST_TOKEN_generate_key(
+      TRUST_TOKEN_experiment_v2_pp(), priv_key, &priv_key_len,
+      TRUST_TOKEN_MAX_PRIVATE_KEY_SIZE, pub_key, &pub_key_len,
+      TRUST_TOKEN_MAX_PUBLIC_KEY_SIZE, 0x0001));
+  ASSERT_EQ(292u, priv_key_len);
+  ASSERT_EQ(295u, pub_key_len);
+}
+
+TEST(TrustTokenTest, KeyGenExp2PMB) {
+  uint8_t priv_key[TRUST_TOKEN_MAX_PRIVATE_KEY_SIZE];
+  uint8_t pub_key[TRUST_TOKEN_MAX_PUBLIC_KEY_SIZE];
+  size_t priv_key_len, pub_key_len;
+  ASSERT_TRUE(TRUST_TOKEN_generate_key(
+      TRUST_TOKEN_experiment_v2_pmb(), priv_key, &priv_key_len,
+      TRUST_TOKEN_MAX_PRIVATE_KEY_SIZE, pub_key, &pub_key_len,
+      TRUST_TOKEN_MAX_PUBLIC_KEY_SIZE, 0x0001));
+  ASSERT_EQ(292u, priv_key_len);
+  ASSERT_EQ(295u, pub_key_len);
+}
+
+// Test that H in |TRUST_TOKEN_experiment_v1| was computed correctly.
+TEST(TrustTokenTest, HExp1) {
+  const EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp384r1);
+  ASSERT_TRUE(group);
+
+  const uint8_t kHGen[] = "generator";
+  const uint8_t kHLabel[] = "PMBTokens Experiment V1 HashH";
+
+  bssl::UniquePtr<EC_POINT> expected_h(EC_POINT_new(group));
+  ASSERT_TRUE(expected_h);
+  ASSERT_TRUE(ec_hash_to_curve_p384_xmd_sha512_sswu_draft07(
+      group, &expected_h->raw, kHLabel, sizeof(kHLabel), kHGen, sizeof(kHGen)));
+  uint8_t expected_bytes[1 + 2 * EC_MAX_BYTES];
+  size_t expected_len =
+      EC_POINT_point2oct(group, expected_h.get(), POINT_CONVERSION_UNCOMPRESSED,
+                         expected_bytes, sizeof(expected_bytes), nullptr);
+
+  uint8_t h[97];
+  ASSERT_TRUE(pmbtoken_exp1_get_h_for_testing(h));
+  EXPECT_EQ(Bytes(h), Bytes(expected_bytes, expected_len));
+}
+
+// Test that H in |TRUST_TOKEN_experiment_v2_pmb| was computed correctly.
+TEST(TrustTokenTest, HExp2) {
+  const EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp384r1);
+  ASSERT_TRUE(group);
+
+  const uint8_t kHGen[] = "generator";
+  const uint8_t kHLabel[] = "PMBTokens Experiment V2 HashH";
+
+  bssl::UniquePtr<EC_POINT> expected_h(EC_POINT_new(group));
+  ASSERT_TRUE(expected_h);
+  ASSERT_TRUE(ec_hash_to_curve_p384_xmd_sha512_sswu_draft07(
+      group, &expected_h->raw, kHLabel, sizeof(kHLabel), kHGen, sizeof(kHGen)));
+  uint8_t expected_bytes[1 + 2 * EC_MAX_BYTES];
+  size_t expected_len =
+      EC_POINT_point2oct(group, expected_h.get(), POINT_CONVERSION_UNCOMPRESSED,
+                         expected_bytes, sizeof(expected_bytes), nullptr);
+
+  uint8_t h[97];
+  ASSERT_TRUE(pmbtoken_exp2_get_h_for_testing(h));
+  EXPECT_EQ(Bytes(h), Bytes(expected_bytes, expected_len));
+}
+
+static std::vector<const TRUST_TOKEN_METHOD *> AllMethods() {
+  return {
+    TRUST_TOKEN_experiment_v1(),
+    TRUST_TOKEN_experiment_v2_pp(),
+    TRUST_TOKEN_experiment_v2_pmb()
+  };
+}
+
+class TrustTokenProtocolTestBase : public ::testing::Test {
+ public:
+  explicit TrustTokenProtocolTestBase(const TRUST_TOKEN_METHOD *method)
+      : method_(method) {}
+
+  // 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;
+  }
+
+  const TRUST_TOKEN_METHOD *method() { return method_; }
+
+ protected:
+  void SetupContexts() {
+    client.reset(TRUST_TOKEN_CLIENT_new(method(), client_max_batchsize));
+    ASSERT_TRUE(client);
+    issuer.reset(TRUST_TOKEN_ISSUER_new(method(), issuer_max_batchsize));
+    ASSERT_TRUE(issuer);
+
+    for (size_t i = 0; i < method()->max_keys; i++) {
+      uint8_t priv_key[TRUST_TOKEN_MAX_PRIVATE_KEY_SIZE];
+      uint8_t pub_key[TRUST_TOKEN_MAX_PUBLIC_KEY_SIZE];
+      size_t priv_key_len, pub_key_len, key_index;
+      ASSERT_TRUE(TRUST_TOKEN_generate_key(
+          method(), 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)));
+  }
+
+  const TRUST_TOKEN_METHOD *method_;
+  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];
+};
+
+class TrustTokenProtocolTest
+    : public TrustTokenProtocolTestBase,
+      public testing::WithParamInterface<const TRUST_TOKEN_METHOD *> {
+ public:
+  TrustTokenProtocolTest() : TrustTokenProtocolTestBase(GetParam()) {}
+};
+
+INSTANTIATE_TEST_SUITE_P(TrustTokenAllProtocolTest, TrustTokenProtocolTest,
+                         testing::ValuesIn(AllMethods()));
+
+TEST_P(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;
+  size_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=*/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_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_P(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;
+  size_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_P(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);
+  size_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_P(TrustTokenProtocolTest, ExtraDataIssuanceResponse) {
+  ASSERT_NO_FATAL_FAILURE(SetupContexts());
+
+  uint8_t *request = NULL, *response = NULL;
+  size_t request_len, response_len;
+  ASSERT_TRUE(TRUST_TOKEN_CLIENT_begin_issuance(client.get(), &request,
+                                                &request_len, 10));
+  bssl::UniquePtr<uint8_t> free_request(request);
+  size_t tokens_issued;
+  ASSERT_TRUE(TRUST_TOKEN_ISSUER_issue(issuer.get(), &response, &response_len,
+                                       &tokens_issued, request, request_len,
+                                       /*public_metadata=*/KeyID(0),
+                                       /*private_metadata=*/0,
+                                       /*max_issuance=*/10));
+  bssl::UniquePtr<uint8_t> free_response(response);
+  std::vector<uint8_t> response2(response, response + response_len);
+  response2.push_back(0);
+  size_t key_index;
+  bssl::UniquePtr<STACK_OF(TRUST_TOKEN)> tokens(
+      TRUST_TOKEN_CLIENT_finish_issuance(client.get(), &key_index,
+                                         response2.data(), response2.size()));
+  ASSERT_FALSE(tokens);
+}
+
+TEST_P(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);
+  size_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[] = "\x70TEST 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_P(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);
+  size_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[] = "\x70TEST 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_EQ(Bytes(kClientData, sizeof(kClientData) - 1),
+              Bytes(client_data, client_data_len));
+    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_P(TrustTokenProtocolTest, IssuedWithBadKeyID) {
+  client.reset(TRUST_TOKEN_CLIENT_new(method(), client_max_batchsize));
+  ASSERT_TRUE(client);
+  issuer.reset(TRUST_TOKEN_ISSUER_new(method(), 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(
+      method(), 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(
+      method(), 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);
+  size_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 TrustTokenProtocolTestBase,
+      public testing::WithParamInterface<
+          std::tuple<const TRUST_TOKEN_METHOD *, int, bool>> {
+ public:
+  TrustTokenMetadataTest()
+      : TrustTokenProtocolTestBase(std::get<0>(GetParam())) {}
+
+  int public_metadata() { return std::get<1>(GetParam()); }
+  bool private_metadata() { return std::get<2>(GetParam()); }
+};
+
+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);
+  size_t tokens_issued;
+  bool result = TRUST_TOKEN_ISSUER_issue(
+      issuer.get(), &issue_resp, &resp_len, &tokens_issued, issue_msg, msg_len,
+      public_metadata(), private_metadata(), /*max_issuance=*/1);
+  if (!method()->has_private_metadata && private_metadata()) {
+    ASSERT_FALSE(result);
+    return;
+  }
+  ASSERT_TRUE(result);
+  bssl::UniquePtr<uint8_t> free_msg(issue_resp);
+  size_t key_index;
+  bssl::UniquePtr<STACK_OF(TRUST_TOKEN)> tokens(
+      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[] = "\x70TEST CLIENT DATA";
+    uint64_t kRedemptionTime = 13374242;
+
+    const uint8_t kExpectedSRR[] =
+        "\xa4\x68\x6d\x65\x74\x61\x64\x61\x74\x61\xa2\x66\x70\x75\x62\x6c\x69"
+        "\x63\x00\x67\x70\x72\x69\x76\x61\x74\x65\x00\x6a\x74\x6f\x6b\x65\x6e"
+        "\x2d\x68\x61\x73\x68\x58\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+        "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+        "\x00\x00\x00\x00\x00\x6b\x63\x6c\x69\x65\x6e\x74\x2d\x64\x61\x74\x61"
+        "\x70\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";
+
+    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_EQ(Bytes(kClientData, sizeof(kClientData) - 1),
+              Bytes(client_data, client_data_len));
+
+    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);
+
+    const uint8_t kTokenHashDSTLabel[] = "TrustTokenV0 TokenHash";
+    uint8_t token_hash[SHA256_DIGEST_LENGTH];
+    SHA256_CTX sha_ctx;
+    SHA256_Init(&sha_ctx);
+    SHA256_Update(&sha_ctx, kTokenHashDSTLabel, sizeof(kTokenHashDSTLabel));
+    SHA256_Update(&sha_ctx, token->data, token->len);
+    SHA256_Final(token_hash, &sha_ctx);
+
+    // Check the token hash is in the SRR.
+    ASSERT_EQ(Bytes(token_hash), Bytes(srr + 41, sizeof(token_hash)));
+
+    uint8_t decode_private_metadata;
+    ASSERT_TRUE(TRUST_TOKEN_decode_private_metadata(
+        method(), &decode_private_metadata, metadata_key, sizeof(metadata_key),
+        token_hash, sizeof(token_hash), srr[27]));
+    ASSERT_EQ(srr[18], public_metadata());
+    ASSERT_EQ(decode_private_metadata, private_metadata());
+
+    // Clear out the metadata bits.
+    srr[18] = 0;
+    srr[27] = 0;
+
+    // Clear out the token hash.
+    OPENSSL_memset(srr + 41, 0, sizeof(token_hash));
+
+    ASSERT_EQ(Bytes(kExpectedSRR, sizeof(kExpectedSRR) - 1),
+              Bytes(srr, srr_len));
+  }
+}
+
+TEST_P(TrustTokenMetadataTest, TooManyRequests) {
+  if (!method()->has_private_metadata && private_metadata()) {
+    return;
+  }
+
+  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);
+  size_t tokens_issued;
+  ASSERT_TRUE(TRUST_TOKEN_ISSUER_issue(
+      issuer.get(), &issue_resp, &resp_len, &tokens_issued, issue_msg, msg_len,
+      public_metadata(), private_metadata(), /*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);
+}
+
+
+TEST_P(TrustTokenMetadataTest, TruncatedProof) {
+  if (!method()->has_private_metadata && private_metadata()) {
+    return;
+  }
+
+  ASSERT_NO_FATAL_FAILURE(SetupContexts());
+
+  uint8_t *issue_msg = NULL, *issue_resp = NULL;
+  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);
+  size_t tokens_issued;
+  ASSERT_TRUE(TRUST_TOKEN_ISSUER_issue(
+      issuer.get(), &issue_resp, &resp_len, &tokens_issued, issue_msg, msg_len,
+      public_metadata(), private_metadata(), /*max_issuance=*/1));
+  bssl::UniquePtr<uint8_t> free_msg(issue_resp);
+
+  CBS real_response;
+  CBS_init(&real_response, issue_resp, resp_len);
+  uint16_t count;
+  uint32_t public_metadata;
+  bssl::ScopedCBB bad_response;
+  ASSERT_TRUE(CBB_init(bad_response.get(), 0));
+  ASSERT_TRUE(CBS_get_u16(&real_response, &count));
+  ASSERT_TRUE(CBB_add_u16(bad_response.get(), count));
+  ASSERT_TRUE(CBS_get_u32(&real_response, &public_metadata));
+  ASSERT_TRUE(CBB_add_u32(bad_response.get(), public_metadata));
+
+  const EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp384r1);
+  size_t token_length =
+      PMBTOKEN_NONCE_SIZE + 2 * (1 + 2 * BN_num_bytes(&group->field));
+  if (method() == TRUST_TOKEN_experiment_v1()) {
+    token_length += 4;
+  }
+  for (size_t i = 0; i < count; i++) {
+    ASSERT_TRUE(CBB_add_bytes(bad_response.get(), CBS_data(&real_response),
+                              token_length));
+    ASSERT_TRUE(CBS_skip(&real_response, token_length));
+  }
+
+  CBS tmp;
+  ASSERT_TRUE(CBS_get_u16_length_prefixed(&real_response, &tmp));
+  CBB dleq;
+  ASSERT_TRUE(CBB_add_u16_length_prefixed(bad_response.get(), &dleq));
+  ASSERT_TRUE(CBB_add_bytes(&dleq, CBS_data(&tmp), CBS_len(&tmp) - 2));
+  ASSERT_TRUE(CBB_flush(bad_response.get()));
+
+  uint8_t *bad_buf;
+  size_t bad_len;
+  ASSERT_TRUE(CBB_finish(bad_response.get(), &bad_buf, &bad_len));
+  bssl::UniquePtr<uint8_t> free_bad(bad_buf);
+
+  size_t key_index;
+  bssl::UniquePtr<STACK_OF(TRUST_TOKEN)> tokens(
+      TRUST_TOKEN_CLIENT_finish_issuance(client.get(), &key_index, bad_buf,
+                                         bad_len));
+  ASSERT_FALSE(tokens);
+}
+
+TEST_P(TrustTokenMetadataTest, ExcessDataProof) {
+  if (!method()->has_private_metadata && private_metadata()) {
+    return;
+  }
+
+  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);
+  size_t tokens_issued;
+  ASSERT_TRUE(TRUST_TOKEN_ISSUER_issue(
+      issuer.get(), &issue_resp, &resp_len, &tokens_issued, issue_msg, msg_len,
+      public_metadata(), private_metadata(), /*max_issuance=*/1));
+  bssl::UniquePtr<uint8_t> free_msg(issue_resp);
+
+  CBS real_response;
+  CBS_init(&real_response, issue_resp, resp_len);
+  uint16_t count;
+  uint32_t public_metadata;
+  bssl::ScopedCBB bad_response;
+  ASSERT_TRUE(CBB_init(bad_response.get(), 0));
+  ASSERT_TRUE(CBS_get_u16(&real_response, &count));
+  ASSERT_TRUE(CBB_add_u16(bad_response.get(), count));
+  ASSERT_TRUE(CBS_get_u32(&real_response, &public_metadata));
+  ASSERT_TRUE(CBB_add_u32(bad_response.get(), public_metadata));
+
+  const EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp384r1);
+  size_t token_length =
+      PMBTOKEN_NONCE_SIZE + 2 * (1 + 2 * BN_num_bytes(&group->field));
+  if (method() == TRUST_TOKEN_experiment_v1()) {
+    token_length += 4;
+  }
+  for (size_t i = 0; i < count; i++) {
+    ASSERT_TRUE(CBB_add_bytes(bad_response.get(), CBS_data(&real_response),
+                              token_length));
+    ASSERT_TRUE(CBS_skip(&real_response, token_length));
+  }
+
+  CBS tmp;
+  ASSERT_TRUE(CBS_get_u16_length_prefixed(&real_response, &tmp));
+  CBB dleq;
+  ASSERT_TRUE(CBB_add_u16_length_prefixed(bad_response.get(), &dleq));
+  ASSERT_TRUE(CBB_add_bytes(&dleq, CBS_data(&tmp), CBS_len(&tmp)));
+  ASSERT_TRUE(CBB_add_u16(&dleq, 42));
+  ASSERT_TRUE(CBB_flush(bad_response.get()));
+
+  uint8_t *bad_buf;
+  size_t bad_len;
+  ASSERT_TRUE(CBB_finish(bad_response.get(), &bad_buf, &bad_len));
+  bssl::UniquePtr<uint8_t> free_bad(bad_buf);
+
+  size_t key_index;
+  bssl::UniquePtr<STACK_OF(TRUST_TOKEN)> tokens(
+      TRUST_TOKEN_CLIENT_finish_issuance(client.get(), &key_index, bad_buf,
+                                         bad_len));
+  ASSERT_FALSE(tokens);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    TrustTokenAllMetadataTest, TrustTokenMetadataTest,
+    testing::Combine(testing::ValuesIn(AllMethods()),
+                     testing::Values(TrustTokenProtocolTest::KeyID(0),
+                                     TrustTokenProtocolTest::KeyID(1),
+                                     TrustTokenProtocolTest::KeyID(2)),
+                     testing::Bool()));
+
+class TrustTokenBadKeyTest
+    : public TrustTokenProtocolTestBase,
+      public testing::WithParamInterface<
+          std::tuple<const TRUST_TOKEN_METHOD *, bool, int>> {
+ public:
+  TrustTokenBadKeyTest()
+      : TrustTokenProtocolTestBase(std::get<0>(GetParam())) {}
+
+  bool private_metadata() { return std::get<1>(GetParam()); }
+  int corrupted_key() { return std::get<2>(GetParam()); }
+};
+
+TEST_P(TrustTokenBadKeyTest, BadKey) {
+  if (!method()->has_private_metadata && private_metadata()) {
+    return;
+  }
+
+  ASSERT_NO_FATAL_FAILURE(SetupContexts());
+
+  uint8_t *issue_msg = NULL, *issue_resp = NULL;
+  size_t msg_len, resp_len;
+  ASSERT_TRUE(TRUST_TOKEN_CLIENT_begin_issuance(client.get(), &issue_msg,
+                                                &msg_len, 10));
+  bssl::UniquePtr<uint8_t> free_issue_msg(issue_msg);
+
+  struct trust_token_issuer_key_st *key = &issuer->keys[0];
+  EC_SCALAR *scalars[] = {&key->key.x0, &key->key.y0, &key->key.x1,
+                          &key->key.y1, &key->key.xs, &key->key.ys};
+
+  // Corrupt private key scalar.
+  scalars[corrupted_key()]->bytes[0] ^= 42;
+
+  size_t tokens_issued;
+  ASSERT_TRUE(TRUST_TOKEN_ISSUER_issue(
+      issuer.get(), &issue_resp, &resp_len, &tokens_issued, issue_msg, msg_len,
+      /*public_metadata=*/7, private_metadata(), /*max_issuance=*/1));
+  bssl::UniquePtr<uint8_t> free_msg(issue_resp);
+  size_t key_index;
+  bssl::UniquePtr<STACK_OF(TRUST_TOKEN)> tokens(
+      TRUST_TOKEN_CLIENT_finish_issuance(client.get(), &key_index, issue_resp,
+                                         resp_len));
+
+  // If the unused private key is corrupted, then the DLEQ proof should succeed.
+  if ((corrupted_key() / 2 == 0 && private_metadata() == true) ||
+      (corrupted_key() / 2 == 1 && private_metadata() == false)) {
+    ASSERT_TRUE(tokens);
+  } else {
+    ASSERT_FALSE(tokens);
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(TrustTokenAllBadKeyTest, TrustTokenBadKeyTest,
+                         testing::Combine(testing::ValuesIn(AllMethods()),
+                                          testing::Bool(),
+                                          testing::Values(0, 1, 2, 3, 4, 5)));
+
+}  // namespace
+BSSL_NAMESPACE_END