external/boringssl: Sync to bc2a2013e03754a89a701739a7b58c422391efa2.

Third time's the charm.

This includes the following changes:

https://boringssl.googlesource.com/boringssl/+log/c9827e073f64e353c4891ecc2c73721882543ee0..bc2a2013e03754a89a701739a7b58c422391efa2

Test: atest CtsLibcoreTestCases
Test: atest CtsLibcoreOkHttpTestCases
Change-Id: I7943c83d12237ec6e4dc54fb3d5a9cecb909e6e7
diff --git a/src/ssl/CMakeLists.txt b/src/ssl/CMakeLists.txt
index dc89dca..0fb532e 100644
--- a/src/ssl/CMakeLists.txt
+++ b/src/ssl/CMakeLists.txt
@@ -50,6 +50,7 @@
 
   span_test.cc
   ssl_test.cc
+  ssl_c_test.c
 
   $<TARGET_OBJECTS:boringssl_gtest_main>
 )
diff --git a/src/ssl/d1_pkt.cc b/src/ssl/d1_pkt.cc
index be595b0..dfb8a67 100644
--- a/src/ssl/d1_pkt.cc
+++ b/src/ssl/d1_pkt.cc
@@ -256,7 +256,7 @@
   if (ret <= 0) {
     return ret;
   }
-  ssl->s3->alert_dispatch = 0;
+  ssl->s3->alert_dispatch = false;
 
   // If the alert is fatal, flush the BIO now.
   if (ssl->s3->send_alert[0] == SSL3_AL_FATAL) {
diff --git a/src/ssl/handoff.cc b/src/ssl/handoff.cc
index 0928015..db5886a 100644
--- a/src/ssl/handoff.cc
+++ b/src/ssl/handoff.cc
@@ -450,6 +450,10 @@
   s3->aead_write_ctx->SetVersionIfNullCipher(ssl->version);
   s3->hs->cert_request = cert_request;
 
+  // TODO(davidben): When handoff for TLS 1.3 is added, serialize
+  // |early_data_reason| and stabilize the constants.
+  s3->early_data_reason = ssl_early_data_protocol_version;
+
   Array<uint8_t> key_block;
   if ((type == handback_after_session_resumption ||
        type == handback_after_handshake) &&
diff --git a/src/ssl/handshake.cc b/src/ssl/handshake.cc
index 89be48f..b8e0070 100644
--- a/src/ssl/handshake.cc
+++ b/src/ssl/handshake.cc
@@ -648,6 +648,7 @@
         return -1;
 
       case ssl_hs_early_data_rejected:
+        assert(ssl->s3->early_data_reason != ssl_early_data_unknown);
         ssl->s3->rwstate = SSL_EARLY_DATA_REJECTED;
         // Cause |SSL_write| to start failing immediately.
         hs->can_early_write = false;
diff --git a/src/ssl/handshake_client.cc b/src/ssl/handshake_client.cc
index b0de670..a53e430 100644
--- a/src/ssl/handshake_client.cc
+++ b/src/ssl/handshake_client.cc
@@ -1071,13 +1071,8 @@
       return ssl_hs_error;
     }
 
-    bool sig_ok = ssl_public_key_verify(ssl, signature, signature_algorithm,
-                                        hs->peer_pubkey.get(), transcript_data);
-#if defined(BORINGSSL_UNSAFE_FUZZER_MODE)
-    sig_ok = true;
-    ERR_clear_error();
-#endif
-    if (!sig_ok) {
+    if (!ssl_public_key_verify(ssl, signature, signature_algorithm,
+                               hs->peer_pubkey.get(), transcript_data)) {
       // bad signature
       OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_SIGNATURE);
       ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECRYPT_ERROR);
diff --git a/src/ssl/handshake_server.cc b/src/ssl/handshake_server.cc
index 4622ad0..36aa560 100644
--- a/src/ssl/handshake_server.cc
+++ b/src/ssl/handshake_server.cc
@@ -503,6 +503,54 @@
   return true;
 }
 
+static bool extract_sni(SSL_HANDSHAKE *hs, uint8_t *out_alert,
+                        const SSL_CLIENT_HELLO *client_hello) {
+  SSL *const ssl = hs->ssl;
+  CBS sni;
+  if (!ssl_client_hello_get_extension(client_hello, &sni,
+                                      TLSEXT_TYPE_server_name)) {
+    // No SNI extension to parse.
+    return true;
+  }
+
+  CBS server_name_list, host_name;
+  uint8_t name_type;
+  if (!CBS_get_u16_length_prefixed(&sni, &server_name_list) ||
+      !CBS_get_u8(&server_name_list, &name_type) ||
+      // Although the server_name extension was intended to be extensible to
+      // new name types and multiple names, OpenSSL 1.0.x had a bug which meant
+      // different name types will cause an error. Further, RFC 4366 originally
+      // defined syntax inextensibly. RFC 6066 corrected this mistake, but
+      // adding new name types is no longer feasible.
+      //
+      // Act as if the extensibility does not exist to simplify parsing.
+      !CBS_get_u16_length_prefixed(&server_name_list, &host_name) ||
+      CBS_len(&server_name_list) != 0 ||
+      CBS_len(&sni) != 0) {
+    *out_alert = SSL_AD_DECODE_ERROR;
+    return false;
+  }
+
+  if (name_type != TLSEXT_NAMETYPE_host_name ||
+      CBS_len(&host_name) == 0 ||
+      CBS_len(&host_name) > TLSEXT_MAXLEN_host_name ||
+      CBS_contains_zero_byte(&host_name)) {
+    *out_alert = SSL_AD_UNRECOGNIZED_NAME;
+    return false;
+  }
+
+  // Copy the hostname as a string.
+  char *raw = nullptr;
+  if (!CBS_strdup(&host_name, &raw)) {
+    *out_alert = SSL_AD_INTERNAL_ERROR;
+    return false;
+  }
+  ssl->s3->hostname.reset(raw);
+
+  hs->should_ack_sni = true;
+  return true;
+}
+
 static enum ssl_hs_wait_t do_read_client_hello(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
 
@@ -526,6 +574,12 @@
     return ssl_hs_handoff;
   }
 
+  uint8_t alert = SSL_AD_DECODE_ERROR;
+  if (!extract_sni(hs, &alert, &client_hello)) {
+    ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
+    return ssl_hs_error;
+  }
+
   // Run the early callback.
   if (ssl->ctx->select_certificate_cb != NULL) {
     switch (ssl->ctx->select_certificate_cb(&client_hello)) {
@@ -553,7 +607,6 @@
     hs->apply_jdk11_workaround = true;
   }
 
-  uint8_t alert = SSL_AD_DECODE_ERROR;
   if (!negotiate_version(hs, &alert, &client_hello)) {
     ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
     return ssl_hs_error;
@@ -635,6 +688,8 @@
     return ssl_hs_ok;
   }
 
+  ssl->s3->early_data_reason = ssl_early_data_protocol_version;
+
   SSL_CLIENT_HELLO client_hello;
   if (!ssl_client_hello_init(ssl, &client_hello, msg)) {
     return ssl_hs_error;
@@ -1408,14 +1463,8 @@
     return ssl_hs_error;
   }
 
-  bool sig_ok =
-      ssl_public_key_verify(ssl, signature, signature_algorithm,
-                            hs->peer_pubkey.get(), hs->transcript.buffer());
-#if defined(BORINGSSL_UNSAFE_FUZZER_MODE)
-  sig_ok = true;
-  ERR_clear_error();
-#endif
-  if (!sig_ok) {
+  if (!ssl_public_key_verify(ssl, signature, signature_algorithm,
+                             hs->peer_pubkey.get(), hs->transcript.buffer())) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_SIGNATURE);
     ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECRYPT_ERROR);
     return ssl_hs_error;
diff --git a/src/ssl/internal.h b/src/ssl/internal.h
index ee2952a..b355c7f 100644
--- a/src/ssl/internal.h
+++ b/src/ssl/internal.h
@@ -465,6 +465,9 @@
 #define SSL_HANDSHAKE_MAC_SHA256 0x2
 #define SSL_HANDSHAKE_MAC_SHA384 0x4
 
+// SSL_MAX_MD_SIZE is size of the largest hash function used in TLS, SHA-384.
+#define SSL_MAX_MD_SIZE 48
+
 // An SSLCipherPreferenceList contains a list of SSL_CIPHERs with equal-
 // preference groups. For TLS clients, the groups are moot because the server
 // picks the cipher and groups cannot be expressed on the wire. However, for
@@ -560,6 +563,12 @@
 // it returns zero.
 size_t ssl_cipher_get_record_split_len(const SSL_CIPHER *cipher);
 
+// ssl_choose_tls13_cipher returns an |SSL_CIPHER| corresponding with the best
+// available from |cipher_suites| compatible with |version| and |group_id|. It
+// returns NULL if there isn't a compatible cipher.
+const SSL_CIPHER *ssl_choose_tls13_cipher(CBS cipher_suites, uint16_t version,
+                                          uint16_t group_id);
+
 
 // Transcript layer.
 
@@ -1446,13 +1455,13 @@
   uint16_t max_version = 0;
 
   size_t hash_len = 0;
-  uint8_t secret[EVP_MAX_MD_SIZE] = {0};
-  uint8_t early_traffic_secret[EVP_MAX_MD_SIZE] = {0};
-  uint8_t client_handshake_secret[EVP_MAX_MD_SIZE] = {0};
-  uint8_t server_handshake_secret[EVP_MAX_MD_SIZE] = {0};
-  uint8_t client_traffic_secret_0[EVP_MAX_MD_SIZE] = {0};
-  uint8_t server_traffic_secret_0[EVP_MAX_MD_SIZE] = {0};
-  uint8_t expected_client_finished[EVP_MAX_MD_SIZE] = {0};
+  uint8_t secret[SSL_MAX_MD_SIZE] = {0};
+  uint8_t early_traffic_secret[SSL_MAX_MD_SIZE] = {0};
+  uint8_t client_handshake_secret[SSL_MAX_MD_SIZE] = {0};
+  uint8_t server_handshake_secret[SSL_MAX_MD_SIZE] = {0};
+  uint8_t client_traffic_secret_0[SSL_MAX_MD_SIZE] = {0};
+  uint8_t server_traffic_secret_0[SSL_MAX_MD_SIZE] = {0};
+  uint8_t expected_client_finished[SSL_MAX_MD_SIZE] = {0};
 
   union {
     // sent is a bitset where the bits correspond to elements of kExtensions
@@ -2029,7 +2038,7 @@
   // check_client_CA_list returns one if |names| is a good list of X.509
   // distinguished names and zero otherwise. This is used to ensure that we can
   // reject unparsable values at handshake time when using crypto/x509.
-  int (*check_client_CA_list)(STACK_OF(CRYPTO_BUFFER) *names);
+  bool (*check_client_CA_list)(STACK_OF(CRYPTO_BUFFER) *names);
 
   // cert_clear frees and NULLs all X509 certificate-related state.
   void (*cert_clear)(CERT *cert);
@@ -2046,35 +2055,35 @@
 
   // session_cache_objects fills out |sess->x509_peer| and |sess->x509_chain|
   // from |sess->certs| and erases |sess->x509_chain_without_leaf|. It returns
-  // one on success or zero on error.
-  int (*session_cache_objects)(SSL_SESSION *session);
+  // true on success or false on error.
+  bool (*session_cache_objects)(SSL_SESSION *session);
   // session_dup duplicates any needed fields from |session| to |new_session|.
-  // It returns one on success or zero on error.
-  int (*session_dup)(SSL_SESSION *new_session, const SSL_SESSION *session);
+  // It returns true on success or false on error.
+  bool (*session_dup)(SSL_SESSION *new_session, const SSL_SESSION *session);
   // session_clear frees any X509-related state from |session|.
   void (*session_clear)(SSL_SESSION *session);
   // session_verify_cert_chain verifies the certificate chain in |session|,
-  // sets |session->verify_result| and returns one on success or zero on
+  // sets |session->verify_result| and returns true on success or false on
   // error.
-  int (*session_verify_cert_chain)(SSL_SESSION *session, SSL_HANDSHAKE *ssl,
-                                   uint8_t *out_alert);
+  bool (*session_verify_cert_chain)(SSL_SESSION *session, SSL_HANDSHAKE *ssl,
+                                    uint8_t *out_alert);
 
   // hs_flush_cached_ca_names drops any cached |X509_NAME|s from |hs|.
   void (*hs_flush_cached_ca_names)(SSL_HANDSHAKE *hs);
-  // ssl_new does any neccessary initialisation of |hs|. It returns one on
-  // success or zero on error.
-  int (*ssl_new)(SSL_HANDSHAKE *hs);
+  // ssl_new does any necessary initialisation of |hs|. It returns true on
+  // success or false on error.
+  bool (*ssl_new)(SSL_HANDSHAKE *hs);
   // ssl_free frees anything created by |ssl_new|.
   void (*ssl_config_free)(SSL_CONFIG *cfg);
   // ssl_flush_cached_client_CA drops any cached |X509_NAME|s from |ssl|.
   void (*ssl_flush_cached_client_CA)(SSL_CONFIG *cfg);
   // ssl_auto_chain_if_needed runs the deprecated auto-chaining logic if
   // necessary. On success, it updates |ssl|'s certificate configuration as
-  // needed and returns one. Otherwise, it returns zero.
-  int (*ssl_auto_chain_if_needed)(SSL_HANDSHAKE *hs);
-  // ssl_ctx_new does any neccessary initialisation of |ctx|. It returns one on
-  // success or zero on error.
-  int (*ssl_ctx_new)(SSL_CTX *ctx);
+  // needed and returns true. Otherwise, it returns false.
+  bool (*ssl_auto_chain_if_needed)(SSL_HANDSHAKE *hs);
+  // ssl_ctx_new does any necessary initialisation of |ctx|. It returns true on
+  // success or false on error.
+  bool (*ssl_ctx_new)(SSL_CTX *ctx);
   // ssl_ctx_free frees anything created by |ssl_ctx_new|.
   void (*ssl_ctx_free)(SSL_CTX *ctx);
   // ssl_ctx_flush_cached_client_CA drops any cached |X509_NAME|s from |ctx|.
@@ -2164,8 +2173,6 @@
   // the receive half of the connection.
   UniquePtr<ERR_SAVE_STATE> read_error;
 
-  int alert_dispatch = 0;
-
   int total_renegotiations = 0;
 
   // This holds a variable that indicates what we were doing when a 0 or -1 is
@@ -2221,6 +2228,10 @@
   // session_reused indicates whether a session was resumed.
   bool session_reused : 1;
 
+  // delegated_credential_used is whether we presented a delegated credential to
+  // the peer.
+  bool delegated_credential_used : 1;
+
   bool send_connection_binding : 1;
 
   // In a client, this means that the server supported Channel ID and that a
@@ -2244,6 +2255,13 @@
   // token_binding_negotiated is set if Token Binding was negotiated.
   bool token_binding_negotiated : 1;
 
+  // pq_experimental_signal_seen is true if the peer was observed
+  // sending/echoing the post-quantum experiment signal.
+  bool pq_experiment_signal_seen : 1;
+
+  // alert_dispatch is true there is an alert in |send_alert| to be sent.
+  bool alert_dispatch : 1;
+
   // hs_buf is the buffer of handshake data to process.
   UniquePtr<BUF_MEM> hs_buf;
 
@@ -2266,6 +2284,9 @@
   // which resumed a session.
   int32_t ticket_age_skew = 0;
 
+  // ssl_early_data_reason stores details on why 0-RTT was accepted or rejected.
+  enum ssl_early_data_reason_t early_data_reason = ssl_early_data_unknown;
+
   // aead_read_ctx is the current read cipher state.
   UniquePtr<SSLAEADContext> aead_read_ctx;
 
@@ -2276,14 +2297,12 @@
   // one.
   UniquePtr<SSL_HANDSHAKE> hs;
 
-  uint8_t write_traffic_secret[EVP_MAX_MD_SIZE] = {0};
-  uint8_t read_traffic_secret[EVP_MAX_MD_SIZE] = {0};
-  uint8_t exporter_secret[EVP_MAX_MD_SIZE] = {0};
-  uint8_t early_exporter_secret[EVP_MAX_MD_SIZE] = {0};
+  uint8_t write_traffic_secret[SSL_MAX_MD_SIZE] = {0};
+  uint8_t read_traffic_secret[SSL_MAX_MD_SIZE] = {0};
+  uint8_t exporter_secret[SSL_MAX_MD_SIZE] = {0};
   uint8_t write_traffic_secret_len = 0;
   uint8_t read_traffic_secret_len = 0;
   uint8_t exporter_secret_len = 0;
-  uint8_t early_exporter_secret_len = 0;
 
   // Connection binding to prevent renegotiation attacks
   uint8_t previous_client_finished[12] = {0};
@@ -2674,7 +2693,8 @@
 
 void ssl_update_cache(SSL_HANDSHAKE *hs, int mode);
 
-int ssl_send_alert(SSL *ssl, int level, int desc);
+void ssl_send_alert(SSL *ssl, int level, int desc);
+int ssl_send_alert_impl(SSL *ssl, int level, int desc);
 bool ssl3_get_message(const SSL *ssl, SSLMessage *out);
 ssl_open_record_t ssl3_open_handshake(SSL *ssl, size_t *out_consumed,
                                       uint8_t *out_alert, Span<uint8_t> in);
@@ -3170,6 +3190,11 @@
   // If enable_early_data is true, early data can be sent and accepted.
   bool enable_early_data : 1;
 
+  // pq_experiment_signal indicates that an empty extension should be sent
+  // (for clients) or echoed (for servers) to indicate participation in an
+  // experiment of post-quantum key exchanges.
+  bool pq_experiment_signal : 1;
+
  private:
   ~ssl_ctx_st();
   friend void SSL_CTX_free(SSL_CTX *);
diff --git a/src/ssl/s3_both.cc b/src/ssl/s3_both.cc
index 27e9454..842ec67 100644
--- a/src/ssl/s3_both.cc
+++ b/src/ssl/s3_both.cc
@@ -116,6 +116,8 @@
 #include <limits.h>
 #include <string.h>
 
+#include <tuple>
+
 #include <openssl/buf.h>
 #include <openssl/bytestring.h>
 #include <openssl/err.h>
@@ -652,4 +654,72 @@
   }
 }
 
+// CipherScorer produces a "score" for each possible cipher suite offered by
+// the client.
+class CipherScorer {
+ public:
+  CipherScorer(uint16_t group_id)
+      : aes_is_fine_(EVP_has_aes_hardware()),
+        security_128_is_fine_(group_id != SSL_CURVE_CECPQ2 &&
+                              group_id != SSL_CURVE_CECPQ2b) {}
+
+  typedef std::tuple<bool, bool, bool> Score;
+
+  // MinScore returns a |Score| that will compare less than the score of all
+  // cipher suites.
+  Score MinScore() const {
+    return Score(false, false, false);
+  }
+
+  Score Evaluate(const SSL_CIPHER *a) const {
+    return Score(
+        // Something is always preferable to nothing.
+        true,
+        // Either 128-bit is fine, or 256-bit is preferred.
+        security_128_is_fine_ || a->algorithm_enc != SSL_AES128GCM,
+        // Either AES is fine, or else ChaCha20 is preferred.
+        aes_is_fine_ || a->algorithm_enc == SSL_CHACHA20POLY1305);
+  }
+
+ private:
+  const bool aes_is_fine_;
+  const bool security_128_is_fine_;
+};
+
+const SSL_CIPHER *ssl_choose_tls13_cipher(CBS cipher_suites, uint16_t version,
+                                          uint16_t group_id) {
+  if (CBS_len(&cipher_suites) % 2 != 0) {
+    return nullptr;
+  }
+
+  const SSL_CIPHER *best = nullptr;
+  CipherScorer scorer(group_id);
+  CipherScorer::Score best_score = scorer.MinScore();
+
+  while (CBS_len(&cipher_suites) > 0) {
+    uint16_t cipher_suite;
+    if (!CBS_get_u16(&cipher_suites, &cipher_suite)) {
+      return nullptr;
+    }
+
+    // Limit to TLS 1.3 ciphers we know about.
+    const SSL_CIPHER *candidate = SSL_get_cipher_by_value(cipher_suite);
+    if (candidate == nullptr ||
+        SSL_CIPHER_get_min_version(candidate) > version ||
+        SSL_CIPHER_get_max_version(candidate) < version) {
+      continue;
+    }
+
+    const CipherScorer::Score candidate_score = scorer.Evaluate(candidate);
+    // |candidate_score| must be larger to displace the current choice. That way
+    // the client's order controls between ciphers with an equal score.
+    if (candidate_score > best_score) {
+      best = candidate;
+      best_score = candidate_score;
+    }
+  }
+
+  return best;
+}
+
 BSSL_NAMESPACE_END
diff --git a/src/ssl/s3_lib.cc b/src/ssl/s3_lib.cc
index 0e0770c..41dd588 100644
--- a/src/ssl/s3_lib.cc
+++ b/src/ssl/s3_lib.cc
@@ -172,13 +172,16 @@
       has_message(false),
       initial_handshake_complete(false),
       session_reused(false),
+      delegated_credential_used(false),
       send_connection_binding(false),
       channel_id_valid(false),
       key_update_pending(false),
       wpend_pending(false),
       early_data_accepted(false),
       tls13_downgrade(false),
-      token_binding_negotiated(false) {}
+      token_binding_negotiated(false),
+      pq_experiment_signal_seen(false),
+      alert_dispatch(false) {}
 
 SSL3_STATE::~SSL3_STATE() {}
 
diff --git a/src/ssl/s3_pkt.cc b/src/ssl/s3_pkt.cc
index abc6798..a54bb00 100644
--- a/src/ssl/s3_pkt.cc
+++ b/src/ssl/s3_pkt.cc
@@ -118,6 +118,7 @@
 #include <openssl/mem.h>
 #include <openssl/rand.h>
 
+#include "../crypto/err/internal.h"
 #include "../crypto/internal.h"
 #include "internal.h"
 
@@ -381,7 +382,24 @@
   return ssl_open_record_success;
 }
 
-int ssl_send_alert(SSL *ssl, int level, int desc) {
+void ssl_send_alert(SSL *ssl, int level, int desc) {
+  // This function is called in response to a fatal error from the peer. Ignore
+  // any failures writing the alert and report only the original error. In
+  // particular, if the transport uses |SSL_write|, our existing error will be
+  // clobbered so we must save and restore the error queue. See
+  // https://crbug.com/959305.
+  //
+  // TODO(davidben): Return the alert out of the handshake, rather than calling
+  // this function internally everywhere.
+  //
+  // TODO(davidben): This does not allow retrying if the alert hit EAGAIN. See
+  // https://crbug.com/boringssl/130.
+  UniquePtr<ERR_SAVE_STATE> err_state(ERR_save_state());
+  ssl_send_alert_impl(ssl, level, desc);
+  ERR_restore_state(err_state.get());
+}
+
+int ssl_send_alert_impl(SSL *ssl, int level, int desc) {
   // It is illegal to send an alert when we've already sent a closing one.
   if (ssl->s3->write_shutdown != ssl_shutdown_none) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_PROTOCOL_IS_SHUTDOWN);
@@ -396,7 +414,7 @@
     ssl->s3->write_shutdown = ssl_shutdown_error;
   }
 
-  ssl->s3->alert_dispatch = 1;
+  ssl->s3->alert_dispatch = true;
   ssl->s3->send_alert[0] = level;
   ssl->s3->send_alert[1] = desc;
   if (ssl->s3->write_buffer.empty()) {
@@ -423,7 +441,7 @@
     }
   }
 
-  ssl->s3->alert_dispatch = 0;
+  ssl->s3->alert_dispatch = false;
 
   // If the alert is fatal, flush the BIO now.
   if (ssl->s3->send_alert[0] == SSL3_AL_FATAL) {
diff --git a/src/ssl/ssl_c_test.c b/src/ssl/ssl_c_test.c
new file mode 100644
index 0000000..02f8655
--- /dev/null
+++ b/src/ssl/ssl_c_test.c
@@ -0,0 +1,15 @@
+#include <openssl/ssl.h>
+
+int BORINGSSL_enum_c_type_test(void);
+
+int BORINGSSL_enum_c_type_test(void) {
+#if defined(__cplusplus)
+#error "This is testing the behaviour of the C compiler."
+#error "It's pointless to build it in C++ mode."
+#endif
+
+  // In C++, the enums in ssl.h are explicitly typed as ints to allow them to
+  // be predeclared. This function confirms that the C compiler believes them
+  // to be the same size as ints. They may differ in signedness, however.
+  return sizeof(enum ssl_private_key_result_t) == sizeof(int);
+}
diff --git a/src/ssl/ssl_cert.cc b/src/ssl/ssl_cert.cc
index 54df38f..b565a35 100644
--- a/src/ssl/ssl_cert.cc
+++ b/src/ssl/ssl_cert.cc
@@ -1010,3 +1010,7 @@
 
   return cert_set_dc(ssl->config->cert.get(), dc, pkey, key_method);
 }
+
+int SSL_delegated_credential_used(const SSL *ssl) {
+  return ssl->s3->delegated_credential_used;
+}
diff --git a/src/ssl/ssl_key_share.cc b/src/ssl/ssl_key_share.cc
index 78d2aa1..826fb1a 100644
--- a/src/ssl/ssl_key_share.cc
+++ b/src/ssl/ssl_key_share.cc
@@ -31,7 +31,7 @@
 
 #include "internal.h"
 #include "../crypto/internal.h"
-
+#include "../third_party/sike/sike.h"
 
 BSSL_NAMESPACE_BEGIN
 
@@ -300,6 +300,87 @@
   HRSS_private_key hrss_private_key_;
 };
 
+class CECPQ2bKeyShare : public SSLKeyShare {
+ public:
+  uint16_t GroupID() const override { return SSL_CURVE_CECPQ2b; }
+
+  bool Offer(CBB *out) override {
+    uint8_t public_x25519[32] = {0};
+    X25519_keypair(public_x25519, private_x25519_);
+    if (!SIKE_keypair(private_sike_, public_sike_)) {
+      return false;
+    }
+
+    return CBB_add_bytes(out, public_x25519, sizeof(public_x25519)) &&
+           CBB_add_bytes(out, public_sike_, sizeof(public_sike_));
+  }
+
+  bool Accept(CBB *out_public_key, Array<uint8_t> *out_secret,
+              uint8_t *out_alert, Span<const uint8_t> peer_key) override {
+    uint8_t public_x25519[32];
+    uint8_t private_x25519[32];
+    uint8_t sike_ciphertext[SIKE_CT_BYTESZ] = {0};
+
+    *out_alert = SSL_AD_INTERNAL_ERROR;
+
+    if (peer_key.size() != sizeof(public_x25519) + SIKE_PUB_BYTESZ) {
+      *out_alert = SSL_AD_DECODE_ERROR;
+      OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT);
+      return false;
+    }
+
+    Array<uint8_t> secret;
+    if (!secret.Init(sizeof(private_x25519_) + SIKE_SS_BYTESZ)) {
+      OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
+      return false;
+    }
+
+    X25519_keypair(public_x25519, private_x25519);
+    if (!X25519(secret.data(), private_x25519, peer_key.data())) {
+      *out_alert = SSL_AD_DECODE_ERROR;
+      OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT);
+      return false;
+    }
+
+    SIKE_encaps(secret.data() + sizeof(private_x25519_), sike_ciphertext,
+                peer_key.data() + sizeof(public_x25519));
+    *out_secret = std::move(secret);
+
+    return CBB_add_bytes(out_public_key, public_x25519,
+                         sizeof(public_x25519)) &&
+           CBB_add_bytes(out_public_key, sike_ciphertext,
+                         sizeof(sike_ciphertext));
+  }
+
+  bool Finish(Array<uint8_t> *out_secret, uint8_t *out_alert,
+              Span<const uint8_t> peer_key) override {
+    *out_alert = SSL_AD_INTERNAL_ERROR;
+
+    Array<uint8_t> secret;
+    if (!secret.Init(sizeof(private_x25519_) + SIKE_SS_BYTESZ)) {
+      OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
+      return false;
+    }
+
+    if (peer_key.size() != 32 + SIKE_CT_BYTESZ ||
+        !X25519(secret.data(), private_x25519_, peer_key.data())) {
+      *out_alert = SSL_AD_DECODE_ERROR;
+      OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT);
+      return false;
+    }
+
+    SIKE_decaps(secret.data() + sizeof(private_x25519_), peer_key.data() + 32,
+                public_sike_, private_sike_);
+    *out_secret = std::move(secret);
+    return true;
+  }
+
+ private:
+  uint8_t private_x25519_[32];
+  uint8_t private_sike_[SIKE_PRV_BYTESZ];
+  uint8_t public_sike_[SIKE_PUB_BYTESZ];
+};
+
 CONSTEXPR_ARRAY NamedGroup kNamedGroups[] = {
     {NID_secp224r1, SSL_CURVE_SECP224R1, "P-224", "secp224r1"},
     {NID_X9_62_prime256v1, SSL_CURVE_SECP256R1, "P-256", "prime256v1"},
@@ -307,6 +388,7 @@
     {NID_secp521r1, SSL_CURVE_SECP521R1, "P-521", "secp521r1"},
     {NID_X25519, SSL_CURVE_X25519, "X25519", "x25519"},
     {NID_CECPQ2, SSL_CURVE_CECPQ2, "CECPQ2", "CECPQ2"},
+    {NID_CECPQ2b, SSL_CURVE_CECPQ2b, "CECPQ2b", "CECPQ2b"},
 };
 
 }  // namespace
@@ -333,6 +415,8 @@
       return UniquePtr<SSLKeyShare>(New<X25519KeyShare>());
     case SSL_CURVE_CECPQ2:
       return UniquePtr<SSLKeyShare>(New<CECPQ2KeyShare>());
+    case SSL_CURVE_CECPQ2b:
+      return UniquePtr<SSLKeyShare>(New<CECPQ2bKeyShare>());
     default:
       return nullptr;
   }
diff --git a/src/ssl/ssl_lib.cc b/src/ssl/ssl_lib.cc
index f9910f7..00ee7da 100644
--- a/src/ssl/ssl_lib.cc
+++ b/src/ssl/ssl_lib.cc
@@ -569,7 +569,8 @@
       false_start_allowed_without_alpn(false),
       ignore_tls13_downgrade(false),
       handoff(false),
-      enable_early_data(false) {
+      enable_early_data(false),
+      pq_experiment_signal(false) {
   CRYPTO_MUTEX_init(&lock);
   CRYPTO_new_ex_data(&ex_data);
 }
@@ -1195,7 +1196,7 @@
 
   if (ssl->s3->write_shutdown != ssl_shutdown_close_notify) {
     // Send a close_notify.
-    if (ssl_send_alert(ssl, SSL3_AL_WARNING, SSL_AD_CLOSE_NOTIFY) <= 0) {
+    if (ssl_send_alert_impl(ssl, SSL3_AL_WARNING, SSL_AD_CLOSE_NOTIFY) <= 0) {
       return -1;
     }
   } else if (ssl->s3->alert_dispatch) {
@@ -1242,7 +1243,15 @@
     return ssl->method->dispatch_alert(ssl);
   }
 
-  return ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
+  return ssl_send_alert_impl(ssl, SSL3_AL_FATAL, alert);
+}
+
+void SSL_CTX_enable_pq_experiment_signal(SSL_CTX *ctx) {
+  ctx->pq_experiment_signal = true;
+}
+
+int SSL_pq_experiment_signal_seen(const SSL *ssl) {
+  return ssl->s3->pq_experiment_signal_seen;
 }
 
 int SSL_set_quic_transport_params(SSL *ssl, const uint8_t *params,
@@ -1294,6 +1303,10 @@
   ssl->s3->wpend_pending = false;
 }
 
+enum ssl_early_data_reason_t SSL_get_early_data_reason(const SSL *ssl) {
+  return ssl->s3->early_data_reason;
+}
+
 static int bio_retry_reason_to_error(int reason) {
   switch (reason) {
     case BIO_RR_CONNECT:
diff --git a/src/ssl/ssl_privkey.cc b/src/ssl/ssl_privkey.cc
index 1ddb1b1..23f8d12 100644
--- a/src/ssl/ssl_privkey.cc
+++ b/src/ssl/ssl_privkey.cc
@@ -236,9 +236,16 @@
                            uint16_t sigalg, EVP_PKEY *pkey,
                            Span<const uint8_t> in) {
   ScopedEVP_MD_CTX ctx;
-  return setup_ctx(ssl, ctx.get(), pkey, sigalg, true /* verify */) &&
-         EVP_DigestVerify(ctx.get(), signature.data(), signature.size(),
-                          in.data(), in.size());
+  if (!setup_ctx(ssl, ctx.get(), pkey, sigalg, true /* verify */)) {
+    return false;
+  }
+  bool ok = EVP_DigestVerify(ctx.get(), signature.data(), signature.size(),
+                             in.data(), in.size());
+#if defined(BORINGSSL_UNSAFE_FUZZER_MODE)
+  ok = true;
+  ERR_clear_error();
+#endif
+  return ok;
 }
 
 enum ssl_private_key_result_t ssl_private_key_decrypt(SSL_HANDSHAKE *hs,
diff --git a/src/ssl/ssl_session.cc b/src/ssl/ssl_session.cc
index 927dd1b..bb04b1a 100644
--- a/src/ssl/ssl_session.cc
+++ b/src/ssl/ssl_session.cc
@@ -1044,6 +1044,11 @@
   }
 }
 
+int SSL_SESSION_early_data_capable(const SSL_SESSION *session) {
+  return ssl_session_protocol_version(session) >= TLS1_3_VERSION &&
+         session->ticket_max_early_data != 0;
+}
+
 SSL_SESSION *SSL_magic_pending_session_ptr(void) {
   return (SSL_SESSION *)&g_pending_session_magic;
 }
diff --git a/src/ssl/ssl_test.cc b/src/ssl/ssl_test.cc
index d01b649..6f180c7 100644
--- a/src/ssl/ssl_test.cc
+++ b/src/ssl/ssl_test.cc
@@ -737,103 +737,77 @@
   return true;
 }
 
-static bool TestSSL_SESSIONEncoding(const char *input_b64) {
-  const uint8_t *cptr;
-  uint8_t *ptr;
+TEST(SSLTest, SessionEncoding) {
+  for (const char *input_b64 : {
+           kOpenSSLSession,
+           kCustomSession,
+           kBoringSSLSession,
+       }) {
+    SCOPED_TRACE(std::string(input_b64));
+    // Decode the input.
+    std::vector<uint8_t> input;
+    ASSERT_TRUE(DecodeBase64(&input, input_b64));
 
-  // Decode the input.
-  std::vector<uint8_t> input;
-  if (!DecodeBase64(&input, input_b64)) {
-    return false;
+    // Verify the SSL_SESSION decodes.
+    bssl::UniquePtr<SSL_CTX> ssl_ctx(SSL_CTX_new(TLS_method()));
+    ASSERT_TRUE(ssl_ctx);
+    bssl::UniquePtr<SSL_SESSION> session(
+        SSL_SESSION_from_bytes(input.data(), input.size(), ssl_ctx.get()));
+    ASSERT_TRUE(session) << "SSL_SESSION_from_bytes failed";
+
+    // Verify the SSL_SESSION encoding round-trips.
+    size_t encoded_len;
+    bssl::UniquePtr<uint8_t> encoded;
+    uint8_t *encoded_raw;
+    ASSERT_TRUE(SSL_SESSION_to_bytes(session.get(), &encoded_raw, &encoded_len))
+        << "SSL_SESSION_to_bytes failed";
+    encoded.reset(encoded_raw);
+    EXPECT_EQ(Bytes(encoded.get(), encoded_len), Bytes(input))
+        << "SSL_SESSION_to_bytes did not round-trip";
+
+    // Verify the SSL_SESSION also decodes with the legacy API.
+    const uint8_t *cptr = input.data();
+    session.reset(d2i_SSL_SESSION(NULL, &cptr, input.size()));
+    ASSERT_TRUE(session) << "d2i_SSL_SESSION failed";
+    EXPECT_EQ(cptr, input.data() + input.size());
+
+    // Verify the SSL_SESSION encoding round-trips via the legacy API.
+    int len = i2d_SSL_SESSION(session.get(), NULL);
+    ASSERT_GT(len, 0) << "i2d_SSL_SESSION failed";
+    ASSERT_EQ(static_cast<size_t>(len), input.size())
+        << "i2d_SSL_SESSION(NULL) returned invalid length";
+
+    encoded.reset((uint8_t *)OPENSSL_malloc(input.size()));
+    ASSERT_TRUE(encoded);
+
+    uint8_t *ptr = encoded.get();
+    len = i2d_SSL_SESSION(session.get(), &ptr);
+    ASSERT_GT(len, 0) << "i2d_SSL_SESSION failed";
+    ASSERT_EQ(static_cast<size_t>(len), input.size())
+        << "i2d_SSL_SESSION(NULL) returned invalid length";
+    ASSERT_EQ(ptr, encoded.get() + input.size())
+        << "i2d_SSL_SESSION did not advance ptr correctly";
+    EXPECT_EQ(Bytes(encoded.get(), encoded_len), Bytes(input))
+        << "SSL_SESSION_to_bytes did not round-trip";
   }
 
-  // Verify the SSL_SESSION decodes.
-  bssl::UniquePtr<SSL_CTX> ssl_ctx(SSL_CTX_new(TLS_method()));
-  if (!ssl_ctx) {
-    return false;
-  }
-  bssl::UniquePtr<SSL_SESSION> session(
-      SSL_SESSION_from_bytes(input.data(), input.size(), ssl_ctx.get()));
-  if (!session) {
-    fprintf(stderr, "SSL_SESSION_from_bytes failed\n");
-    return false;
-  }
+  for (const char *input_b64 : {
+           kBadSessionExtraField,
+           kBadSessionVersion,
+           kBadSessionTrailingData,
+       }) {
+    SCOPED_TRACE(std::string(input_b64));
+    std::vector<uint8_t> input;
+    ASSERT_TRUE(DecodeBase64(&input, input_b64));
 
-  // Verify the SSL_SESSION encoding round-trips.
-  size_t encoded_len;
-  bssl::UniquePtr<uint8_t> encoded;
-  uint8_t *encoded_raw;
-  if (!SSL_SESSION_to_bytes(session.get(), &encoded_raw, &encoded_len)) {
-    fprintf(stderr, "SSL_SESSION_to_bytes failed\n");
-    return false;
+    // Verify that the SSL_SESSION fails to decode.
+    bssl::UniquePtr<SSL_CTX> ssl_ctx(SSL_CTX_new(TLS_method()));
+    ASSERT_TRUE(ssl_ctx);
+    bssl::UniquePtr<SSL_SESSION> session(
+        SSL_SESSION_from_bytes(input.data(), input.size(), ssl_ctx.get()));
+    EXPECT_FALSE(session) << "SSL_SESSION_from_bytes unexpectedly succeeded";
+    ERR_clear_error();
   }
-  encoded.reset(encoded_raw);
-  if (encoded_len != input.size() ||
-      OPENSSL_memcmp(input.data(), encoded.get(), input.size()) != 0) {
-    fprintf(stderr, "SSL_SESSION_to_bytes did not round-trip\n");
-    hexdump(stderr, "Before: ", input.data(), input.size());
-    hexdump(stderr, "After:  ", encoded_raw, encoded_len);
-    return false;
-  }
-
-  // Verify the SSL_SESSION also decodes with the legacy API.
-  cptr = input.data();
-  session.reset(d2i_SSL_SESSION(NULL, &cptr, input.size()));
-  if (!session || cptr != input.data() + input.size()) {
-    fprintf(stderr, "d2i_SSL_SESSION failed\n");
-    return false;
-  }
-
-  // Verify the SSL_SESSION encoding round-trips via the legacy API.
-  int len = i2d_SSL_SESSION(session.get(), NULL);
-  if (len < 0 || (size_t)len != input.size()) {
-    fprintf(stderr, "i2d_SSL_SESSION(NULL) returned invalid length\n");
-    return false;
-  }
-
-  encoded.reset((uint8_t *)OPENSSL_malloc(input.size()));
-  if (!encoded) {
-    fprintf(stderr, "malloc failed\n");
-    return false;
-  }
-
-  ptr = encoded.get();
-  len = i2d_SSL_SESSION(session.get(), &ptr);
-  if (len < 0 || (size_t)len != input.size()) {
-    fprintf(stderr, "i2d_SSL_SESSION returned invalid length\n");
-    return false;
-  }
-  if (ptr != encoded.get() + input.size()) {
-    fprintf(stderr, "i2d_SSL_SESSION did not advance ptr correctly\n");
-    return false;
-  }
-  if (OPENSSL_memcmp(input.data(), encoded.get(), input.size()) != 0) {
-    fprintf(stderr, "i2d_SSL_SESSION did not round-trip\n");
-    return false;
-  }
-
-  return true;
-}
-
-static bool TestBadSSL_SESSIONEncoding(const char *input_b64) {
-  std::vector<uint8_t> input;
-  if (!DecodeBase64(&input, input_b64)) {
-    return false;
-  }
-
-  // Verify that the SSL_SESSION fails to decode.
-  bssl::UniquePtr<SSL_CTX> ssl_ctx(SSL_CTX_new(TLS_method()));
-  if (!ssl_ctx) {
-    return false;
-  }
-  bssl::UniquePtr<SSL_SESSION> session(
-      SSL_SESSION_from_bytes(input.data(), input.size(), ssl_ctx.get()));
-  if (session) {
-    fprintf(stderr, "SSL_SESSION_from_bytes unexpectedly succeeded\n");
-    return false;
-  }
-  ERR_clear_error();
-  return true;
 }
 
 static void ExpectDefaultVersion(uint16_t min_version, uint16_t max_version,
@@ -1087,63 +1061,67 @@
   return client_hello.size() - SSL3_RT_HEADER_LENGTH;
 }
 
-struct PaddingTest {
-  size_t input_len, padded_len;
-};
+TEST(SSLTest, Padding) {
+  struct PaddingVersions {
+    uint16_t max_version, session_version;
+  };
+  static const PaddingVersions kPaddingVersions[] = {
+      // Test the padding extension at TLS 1.2.
+      {TLS1_2_VERSION, TLS1_2_VERSION},
+      // Test the padding extension at TLS 1.3 with a TLS 1.2 session, so there
+      // will be no PSK binder after the padding extension.
+      {TLS1_3_VERSION, TLS1_2_VERSION},
+      // Test the padding extension at TLS 1.3 with a TLS 1.3 session, so there
+      // will be a PSK binder after the padding extension.
+      {TLS1_3_VERSION, TLS1_3_VERSION},
 
-static const PaddingTest kPaddingTests[] = {
-    // ClientHellos of length below 0x100 do not require padding.
-    {0xfe, 0xfe},
-    {0xff, 0xff},
-    // ClientHellos of length 0x100 through 0x1fb are padded up to 0x200.
-    {0x100, 0x200},
-    {0x123, 0x200},
-    {0x1fb, 0x200},
-    // ClientHellos of length 0x1fc through 0x1ff get padded beyond 0x200. The
-    // padding extension takes a minimum of four bytes plus one required content
-    // byte. (To work around yet more server bugs, we avoid empty final
-    // extensions.)
-    {0x1fc, 0x201},
-    {0x1fd, 0x202},
-    {0x1fe, 0x203},
-    {0x1ff, 0x204},
-    // Finally, larger ClientHellos need no padding.
-    {0x200, 0x200},
-    {0x201, 0x201},
-};
+  };
 
-static bool TestPaddingExtension(uint16_t max_version,
-                                 uint16_t session_version) {
-  // Sample a baseline length.
-  size_t base_len = GetClientHelloLen(max_version, session_version, 1);
-  if (base_len == 0) {
-    return false;
-  }
+  struct PaddingTest {
+    size_t input_len, padded_len;
+  };
+  static const PaddingTest kPaddingTests[] = {
+      // ClientHellos of length below 0x100 do not require padding.
+      {0xfe, 0xfe},
+      {0xff, 0xff},
+      // ClientHellos of length 0x100 through 0x1fb are padded up to 0x200.
+      {0x100, 0x200},
+      {0x123, 0x200},
+      {0x1fb, 0x200},
+      // ClientHellos of length 0x1fc through 0x1ff get padded beyond 0x200. The
+      // padding extension takes a minimum of four bytes plus one required
+      // content
+      // byte. (To work around yet more server bugs, we avoid empty final
+      // extensions.)
+      {0x1fc, 0x201},
+      {0x1fd, 0x202},
+      {0x1fe, 0x203},
+      {0x1ff, 0x204},
+      // Finally, larger ClientHellos need no padding.
+      {0x200, 0x200},
+      {0x201, 0x201},
+  };
 
-  for (const PaddingTest &test : kPaddingTests) {
-    if (base_len > test.input_len) {
-      fprintf(stderr,
-              "Baseline ClientHello too long (max_version = %04x, "
-              "session_version = %04x).\n",
-              max_version, session_version);
-      return false;
-    }
+  for (const PaddingVersions &versions : kPaddingVersions) {
+    SCOPED_TRACE(versions.max_version);
+    SCOPED_TRACE(versions.session_version);
 
-    size_t padded_len = GetClientHelloLen(max_version, session_version,
-                                          1 + test.input_len - base_len);
-    if (padded_len != test.padded_len) {
-      fprintf(stderr,
-              "%u-byte ClientHello padded to %u bytes, not %u (max_version = "
-              "%04x, session_version = %04x).\n",
-              static_cast<unsigned>(test.input_len),
-              static_cast<unsigned>(padded_len),
-              static_cast<unsigned>(test.padded_len), max_version,
-              session_version);
-      return false;
+    // Sample a baseline length.
+    size_t base_len =
+        GetClientHelloLen(versions.max_version, versions.session_version, 1);
+    ASSERT_NE(base_len, 0u) << "Baseline length could not be sampled";
+
+    for (const PaddingTest &test : kPaddingTests) {
+      SCOPED_TRACE(test.input_len);
+      ASSERT_LE(base_len, test.input_len) << "Baseline ClientHello too long";
+
+      size_t padded_len =
+          GetClientHelloLen(versions.max_version, versions.session_version,
+                            1 + test.input_len - base_len);
+      EXPECT_EQ(padded_len, test.padded_len)
+          << "ClientHello was not padded to expected length";
     }
   }
-
-  return true;
 }
 
 static bssl::UniquePtr<X509> GetTestCertificate() {
@@ -1550,6 +1528,37 @@
   return true;
 }
 
+static bool FlushNewSessionTickets(SSL *client, SSL *server) {
+  // NewSessionTickets are deferred on the server to |SSL_write|, and clients do
+  // not pick them up until |SSL_read|.
+  for (;;) {
+    int server_ret = SSL_write(server, nullptr, 0);
+    int server_err = SSL_get_error(server, server_ret);
+    // The server may either succeed (|server_ret| is zero) or block on write
+    // (|server_ret| is -1 and |server_err| is |SSL_ERROR_WANT_WRITE|).
+    if (server_ret > 0 ||
+        (server_ret < 0 && server_err != SSL_ERROR_WANT_WRITE)) {
+      fprintf(stderr, "Unexpected server result: %d %d\n", server_ret,
+              server_err);
+      return false;
+    }
+
+    int client_ret = SSL_read(client, nullptr, 0);
+    int client_err = SSL_get_error(client, client_ret);
+    // The client must always block on read.
+    if (client_ret != -1 || client_err != SSL_ERROR_WANT_READ) {
+      fprintf(stderr, "Unexpected client result: %d %d\n", client_ret,
+              client_err);
+      return false;
+    }
+
+    // The server flushed everything it had to write.
+    if (server_ret == 0) {
+      return true;
+    }
+  }
+}
+
 struct ClientConfig {
   SSL_SESSION *session = nullptr;
   std::string servername;
@@ -1661,9 +1670,7 @@
 
   // Drain any post-handshake messages to ensure there are no unread records
   // on either end.
-  uint8_t byte = 0;
-  ASSERT_LE(SSL_read(client_.get(), &byte, 1), 0);
-  ASSERT_LE(SSL_read(server_.get(), &byte, 1), 0);
+  ASSERT_TRUE(FlushNewSessionTickets(client_.get(), server_.get()));
 
   uint64_t client_read_seq = SSL_get_read_sequence(client_.get());
   uint64_t client_write_seq = SSL_get_write_sequence(client_.get());
@@ -1687,6 +1694,7 @@
   }
 
   // Send a record from client to server.
+  uint8_t byte = 0;
   EXPECT_EQ(SSL_write(client_.get(), &byte, 1), 1);
   EXPECT_EQ(SSL_read(server_.get(), &byte, 1), 1);
 
@@ -2084,14 +2092,12 @@
   // Connect client and server to get a session.
   bssl::UniquePtr<SSL> client, server;
   if (!ConnectClientAndServer(&client, &server, client_ctx, server_ctx,
-                              config)) {
+                              config) ||
+      !FlushNewSessionTickets(client.get(), server.get())) {
     fprintf(stderr, "Failed to connect client and server.\n");
     return nullptr;
   }
 
-  // Run the read loop to account for post-handshake tickets in TLS 1.3.
-  SSL_read(client.get(), nullptr, 0);
-
   SSL_CTX_sess_set_new_cb(client_ctx, nullptr);
 
   if (!g_last_session) {
@@ -2125,7 +2131,8 @@
   ClientConfig config;
   config.session = session;
   if (!ConnectClientAndServer(&client, &server, client_ctx, server_ctx,
-                              config)) {
+                              config) ||
+      !FlushNewSessionTickets(client.get(), server.get())) {
     fprintf(stderr, "Failed to connect client and server.\n");
     return nullptr;
   }
@@ -2140,9 +2147,6 @@
     return nullptr;
   }
 
-  // Run the read loop to account for post-handshake tickets in TLS 1.3.
-  SSL_read(client.get(), nullptr, 0);
-
   SSL_CTX_sess_set_new_cb(client_ctx, nullptr);
 
   if (!g_last_session) {
@@ -3055,6 +3059,82 @@
   EXPECT_FALSE(CreateClientSession(client_ctx_.get(), server_ctx_.get()));
 }
 
+// Test that all versions survive tiny write buffers. In particular, TLS 1.3
+// NewSessionTickets are written post-handshake. Servers that block
+// |SSL_do_handshake| on writing them will deadlock if clients are not draining
+// the buffer. Test that we do not do this.
+TEST_P(SSLVersionTest, SmallBuffer) {
+  // DTLS is a datagram protocol and requires packet-sized buffers.
+  if (is_dtls()) {
+    return;
+  }
+
+  // Test both flushing NewSessionTickets with a zero-sized write and
+  // non-zero-sized write.
+  for (bool use_zero_write : {false, true}) {
+    SCOPED_TRACE(use_zero_write);
+
+    g_last_session = nullptr;
+    SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_BOTH);
+    SSL_CTX_sess_set_new_cb(client_ctx_.get(), SaveLastSession);
+
+    bssl::UniquePtr<SSL> client(SSL_new(client_ctx_.get())),
+        server(SSL_new(server_ctx_.get()));
+    ASSERT_TRUE(client);
+    ASSERT_TRUE(server);
+    SSL_set_connect_state(client.get());
+    SSL_set_accept_state(server.get());
+
+    // Use a tiny buffer.
+    BIO *bio1, *bio2;
+    ASSERT_TRUE(BIO_new_bio_pair(&bio1, 1, &bio2, 1));
+
+    // SSL_set_bio takes ownership.
+    SSL_set_bio(client.get(), bio1, bio1);
+    SSL_set_bio(server.get(), bio2, bio2);
+
+    ASSERT_TRUE(CompleteHandshakes(client.get(), server.get()));
+    if (version() >= TLS1_3_VERSION) {
+      // The post-handshake ticket should not have been processed yet.
+      EXPECT_FALSE(g_last_session);
+    }
+
+    if (use_zero_write) {
+      ASSERT_TRUE(FlushNewSessionTickets(client.get(), server.get()));
+      EXPECT_TRUE(g_last_session);
+    }
+
+    // Send some data from server to client. If |use_zero_write| is false, this
+    // will also flush the NewSessionTickets.
+    static const char kMessage[] = "hello world";
+    char buf[sizeof(kMessage)];
+    for (;;) {
+      int server_ret = SSL_write(server.get(), kMessage, sizeof(kMessage));
+      int server_err = SSL_get_error(server.get(), server_ret);
+      int client_ret = SSL_read(client.get(), buf, sizeof(buf));
+      int client_err = SSL_get_error(client.get(), client_ret);
+
+      // The server will write a single record, so every iteration should see
+      // |SSL_ERROR_WANT_WRITE| and |SSL_ERROR_WANT_READ|, until the final
+      // iteration, where both will complete.
+      if (server_ret > 0) {
+        EXPECT_EQ(server_ret, static_cast<int>(sizeof(kMessage)));
+        EXPECT_EQ(client_ret, static_cast<int>(sizeof(kMessage)));
+        EXPECT_EQ(Bytes(buf), Bytes(kMessage));
+        break;
+      }
+
+      ASSERT_EQ(server_ret, -1);
+      ASSERT_EQ(server_err, SSL_ERROR_WANT_WRITE);
+      ASSERT_EQ(client_ret, -1);
+      ASSERT_EQ(client_err, SSL_ERROR_WANT_READ);
+    }
+
+    // The NewSessionTickets should have been flushed and processed.
+    EXPECT_TRUE(g_last_session);
+  }
+}
+
 TEST(SSLTest, AddChainCertHack) {
   // Ensure that we don't accidently break the hack that we have in place to
   // keep curl and serf happy when they use an |X509| even after transfering
@@ -3514,9 +3594,7 @@
   EXPECT_FALSE(SSL_session_reused(client.get()));
   EXPECT_FALSE(SSL_session_reused(server.get()));
 
-  // Run the read loop to account for post-handshake tickets in TLS 1.3.
-  SSL_read(client.get(), nullptr, 0);
-
+  ASSERT_TRUE(FlushNewSessionTickets(client.get(), server.get()));
   bssl::UniquePtr<SSL_SESSION> session = std::move(g_last_session);
   ConnectClientAndServerWithTicketMethod(&client, &server, client_ctx.get(),
                                          server_ctx.get(), retry_count,
@@ -4606,7 +4684,7 @@
     thread.join();
   }
 }
-#endif
+#endif  // OPENSSL_THREADS
 
 constexpr size_t kNumQUICLevels = 4;
 static_assert(ssl_encryption_initial < kNumQUICLevels,
@@ -5263,23 +5341,101 @@
   EXPECT_EQ(SSL_process_quic_post_handshake(client_.get()), 0);
 }
 
-// TODO(davidben): Convert this file to GTest properly.
-TEST(SSLTest, AllTests) {
-  if (!TestSSL_SESSIONEncoding(kOpenSSLSession) ||
-      !TestSSL_SESSIONEncoding(kCustomSession) ||
-      !TestSSL_SESSIONEncoding(kBoringSSLSession) ||
-      !TestBadSSL_SESSIONEncoding(kBadSessionExtraField) ||
-      !TestBadSSL_SESSIONEncoding(kBadSessionVersion) ||
-      !TestBadSSL_SESSIONEncoding(kBadSessionTrailingData) ||
-      // Test the padding extension at TLS 1.2.
-      !TestPaddingExtension(TLS1_2_VERSION, TLS1_2_VERSION) ||
-      // Test the padding extension at TLS 1.3 with a TLS 1.2 session, so there
-      // will be no PSK binder after the padding extension.
-      !TestPaddingExtension(TLS1_3_VERSION, TLS1_2_VERSION) ||
-      // Test the padding extension at TLS 1.3 with a TLS 1.3 session, so there
-      // will be a PSK binder after the padding extension.
-      !TestPaddingExtension(TLS1_3_VERSION, TLS1_3_VERSION)) {
-    ADD_FAILURE() << "Tests failed";
+extern "C" {
+int BORINGSSL_enum_c_type_test(void);
+}
+
+TEST(SSLTest, EnumTypes) {
+  EXPECT_EQ(sizeof(int), sizeof(ssl_private_key_result_t));
+  EXPECT_EQ(1, BORINGSSL_enum_c_type_test());
+}
+
+TEST_P(SSLVersionTest, DoubleSSLError) {
+  // Connect the inner SSL connections.
+  ASSERT_TRUE(Connect());
+
+  // Make a pair of |BIO|s which wrap |client_| and |server_|.
+  UniquePtr<BIO_METHOD> bio_method(BIO_meth_new(0, nullptr));
+  ASSERT_TRUE(bio_method);
+  ASSERT_TRUE(BIO_meth_set_read(
+      bio_method.get(), [](BIO *bio, char *out, int len) -> int {
+        SSL *ssl = static_cast<SSL *>(BIO_get_data(bio));
+        int ret = SSL_read(ssl, out, len);
+        int ssl_ret = SSL_get_error(ssl, ret);
+        if (ssl_ret == SSL_ERROR_WANT_READ) {
+          BIO_set_retry_read(bio);
+        }
+        return ret;
+      }));
+  ASSERT_TRUE(BIO_meth_set_write(
+      bio_method.get(), [](BIO *bio, const char *in, int len) -> int {
+        SSL *ssl = static_cast<SSL *>(BIO_get_data(bio));
+        int ret = SSL_write(ssl, in, len);
+        int ssl_ret = SSL_get_error(ssl, ret);
+        if (ssl_ret == SSL_ERROR_WANT_WRITE) {
+          BIO_set_retry_write(bio);
+        }
+        return ret;
+      }));
+  ASSERT_TRUE(BIO_meth_set_ctrl(
+      bio_method.get(), [](BIO *bio, int cmd, long larg, void *parg) -> long {
+        // |SSL| objects require |BIO_flush| support.
+        if (cmd == BIO_CTRL_FLUSH) {
+          return 1;
+        }
+        return 0;
+      }));
+
+  UniquePtr<BIO> client_bio(BIO_new(bio_method.get()));
+  ASSERT_TRUE(client_bio);
+  BIO_set_data(client_bio.get(), client_.get());
+  BIO_set_init(client_bio.get(), 1);
+
+  UniquePtr<BIO> server_bio(BIO_new(bio_method.get()));
+  ASSERT_TRUE(server_bio);
+  BIO_set_data(server_bio.get(), server_.get());
+  BIO_set_init(server_bio.get(), 1);
+
+  // Wrap the inner connections in another layer of SSL.
+  UniquePtr<SSL> client_outer(SSL_new(client_ctx_.get()));
+  ASSERT_TRUE(client_outer);
+  SSL_set_connect_state(client_outer.get());
+  SSL_set_bio(client_outer.get(), client_bio.get(), client_bio.get());
+  client_bio.release();  // |SSL_set_bio| takes ownership.
+
+  UniquePtr<SSL> server_outer(SSL_new(server_ctx_.get()));
+  ASSERT_TRUE(server_outer);
+  SSL_set_accept_state(server_outer.get());
+  SSL_set_bio(server_outer.get(), server_bio.get(), server_bio.get());
+  server_bio.release();  // |SSL_set_bio| takes ownership.
+
+  // Configure |client_outer| to reject the server certificate.
+  SSL_set_custom_verify(
+      client_outer.get(), SSL_VERIFY_PEER,
+      [](SSL *ssl, uint8_t *out_alert) -> ssl_verify_result_t {
+        return ssl_verify_invalid;
+      });
+
+  for (;;) {
+    int client_ret = SSL_do_handshake(client_outer.get());
+    int client_err = SSL_get_error(client_outer.get(), client_ret);
+    if (client_err != SSL_ERROR_WANT_READ &&
+        client_err != SSL_ERROR_WANT_WRITE) {
+      // The client handshake should terminate on a certificate verification
+      // error.
+      EXPECT_EQ(SSL_ERROR_SSL, client_err);
+      uint32_t err = ERR_peek_error();
+      EXPECT_EQ(ERR_LIB_SSL, ERR_GET_LIB(err));
+      EXPECT_EQ(SSL_R_CERTIFICATE_VERIFY_FAILED, ERR_GET_REASON(err));
+      break;
+    }
+
+    // Run the server handshake and continue.
+    int server_ret = SSL_do_handshake(server_outer.get());
+    int server_err = SSL_get_error(server_outer.get(), server_ret);
+    ASSERT_TRUE(server_err == SSL_ERROR_NONE ||
+                server_err == SSL_ERROR_WANT_READ ||
+                server_err == SSL_ERROR_WANT_WRITE);
   }
 }
 
diff --git a/src/ssl/ssl_x509.cc b/src/ssl/ssl_x509.cc
index 841482f..cda7611 100644
--- a/src/ssl/ssl_x509.cc
+++ b/src/ssl/ssl_x509.cc
@@ -200,19 +200,19 @@
 // forms of elements of |chain|. It returns one on success or zero on error, in
 // which case no change to |cert->chain| is made. It preverses the existing
 // leaf from |cert->chain|, if any.
-static int ssl_cert_set_chain(CERT *cert, STACK_OF(X509) *chain) {
+static bool ssl_cert_set_chain(CERT *cert, STACK_OF(X509) *chain) {
   UniquePtr<STACK_OF(CRYPTO_BUFFER)> new_chain;
 
   if (cert->chain != nullptr) {
     new_chain.reset(sk_CRYPTO_BUFFER_new_null());
     if (!new_chain) {
-      return 0;
+      return false;
     }
 
     // |leaf| might be NULL if it's a “leafless” chain.
     CRYPTO_BUFFER *leaf = sk_CRYPTO_BUFFER_value(cert->chain.get(), 0);
     if (!PushToStack(new_chain.get(), UpRef(leaf))) {
-      return 0;
+      return false;
     }
   }
 
@@ -220,32 +220,32 @@
     if (!new_chain) {
       new_chain = new_leafless_chain();
       if (!new_chain) {
-        return 0;
+        return false;
       }
     }
 
     UniquePtr<CRYPTO_BUFFER> buffer = x509_to_buffer(x509);
     if (!buffer ||
         !PushToStack(new_chain.get(), std::move(buffer))) {
-      return 0;
+      return false;
     }
   }
 
   cert->chain = std::move(new_chain);
-  return 1;
+  return true;
 }
 
 static void ssl_crypto_x509_cert_flush_cached_leaf(CERT *cert) {
   X509_free(cert->x509_leaf);
-  cert->x509_leaf = NULL;
+  cert->x509_leaf = nullptr;
 }
 
 static void ssl_crypto_x509_cert_flush_cached_chain(CERT *cert) {
   sk_X509_pop_free(cert->x509_chain, X509_free);
-  cert->x509_chain = NULL;
+  cert->x509_chain = nullptr;
 }
 
-static int ssl_crypto_x509_check_client_CA_list(
+static bool ssl_crypto_x509_check_client_CA_list(
     STACK_OF(CRYPTO_BUFFER) *names) {
   for (const CRYPTO_BUFFER *buffer : names) {
     const uint8_t *inp = CRYPTO_BUFFER_data(buffer);
@@ -253,11 +253,11 @@
         d2i_X509_NAME(nullptr, &inp, CRYPTO_BUFFER_len(buffer)));
     if (name == nullptr ||
         inp != CRYPTO_BUFFER_data(buffer) + CRYPTO_BUFFER_len(buffer)) {
-      return 0;
+      return false;
     }
   }
 
-  return 1;
+  return true;
 }
 
 static void ssl_crypto_x509_cert_clear(CERT *cert) {
@@ -265,7 +265,7 @@
   ssl_crypto_x509_cert_flush_cached_chain(cert);
 
   X509_free(cert->x509_stash);
-  cert->x509_stash = NULL;
+  cert->x509_stash = nullptr;
 }
 
 static void ssl_crypto_x509_cert_free(CERT *cert) {
@@ -274,19 +274,19 @@
 }
 
 static void ssl_crypto_x509_cert_dup(CERT *new_cert, const CERT *cert) {
-  if (cert->verify_store != NULL) {
+  if (cert->verify_store != nullptr) {
     X509_STORE_up_ref(cert->verify_store);
     new_cert->verify_store = cert->verify_store;
   }
 }
 
-static int ssl_crypto_x509_session_cache_objects(SSL_SESSION *sess) {
+static bool ssl_crypto_x509_session_cache_objects(SSL_SESSION *sess) {
   bssl::UniquePtr<STACK_OF(X509)> chain, chain_without_leaf;
   if (sk_CRYPTO_BUFFER_num(sess->certs.get()) > 0) {
     chain.reset(sk_X509_new_null());
     if (!chain) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-      return 0;
+      return false;
     }
     if (sess->is_server) {
       // chain_without_leaf is only needed for server sessions. See
@@ -294,7 +294,7 @@
       chain_without_leaf.reset(sk_X509_new_null());
       if (!chain_without_leaf) {
         OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-        return 0;
+        return false;
       }
     }
   }
@@ -304,18 +304,18 @@
     UniquePtr<X509> x509(X509_parse_from_buffer(cert));
     if (!x509) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-      return 0;
+      return false;
     }
     if (leaf == nullptr) {
       leaf = UpRef(x509);
     } else if (chain_without_leaf &&
                !PushToStack(chain_without_leaf.get(), UpRef(x509))) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-      return 0;
+      return false;
     }
     if (!PushToStack(chain.get(), std::move(x509))) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-      return 0;
+      return false;
     }
   }
 
@@ -327,80 +327,76 @@
 
   X509_free(sess->x509_peer);
   sess->x509_peer = leaf.release();
-  return 1;
+  return true;
 }
 
-static int ssl_crypto_x509_session_dup(SSL_SESSION *new_session,
-                                       const SSL_SESSION *session) {
+static bool ssl_crypto_x509_session_dup(SSL_SESSION *new_session,
+                                        const SSL_SESSION *session) {
   new_session->x509_peer = UpRef(session->x509_peer).release();
   if (session->x509_chain != nullptr) {
     new_session->x509_chain = X509_chain_up_ref(session->x509_chain);
     if (new_session->x509_chain == nullptr) {
-      return 0;
+      return false;
     }
   }
   if (session->x509_chain_without_leaf != nullptr) {
     new_session->x509_chain_without_leaf =
         X509_chain_up_ref(session->x509_chain_without_leaf);
     if (new_session->x509_chain_without_leaf == nullptr) {
-      return 0;
+      return false;
     }
   }
 
-  return 1;
+  return true;
 }
 
 static void ssl_crypto_x509_session_clear(SSL_SESSION *session) {
   X509_free(session->x509_peer);
-  session->x509_peer = NULL;
+  session->x509_peer = nullptr;
   sk_X509_pop_free(session->x509_chain, X509_free);
-  session->x509_chain = NULL;
+  session->x509_chain = nullptr;
   sk_X509_pop_free(session->x509_chain_without_leaf, X509_free);
-  session->x509_chain_without_leaf = NULL;
+  session->x509_chain_without_leaf = nullptr;
 }
 
-static int ssl_crypto_x509_session_verify_cert_chain(SSL_SESSION *session,
-                                                     SSL_HANDSHAKE *hs,
-                                                     uint8_t *out_alert) {
+static bool ssl_crypto_x509_session_verify_cert_chain(SSL_SESSION *session,
+                                                      SSL_HANDSHAKE *hs,
+                                                      uint8_t *out_alert) {
   *out_alert = SSL_AD_INTERNAL_ERROR;
   STACK_OF(X509) *const cert_chain = session->x509_chain;
-  if (cert_chain == NULL || sk_X509_num(cert_chain) == 0) {
-    return 0;
+  if (cert_chain == nullptr || sk_X509_num(cert_chain) == 0) {
+    return false;
   }
 
   SSL_CTX *ssl_ctx = hs->ssl->ctx.get();
   X509_STORE *verify_store = ssl_ctx->cert_store;
-  if (hs->config->cert->verify_store != NULL) {
+  if (hs->config->cert->verify_store != nullptr) {
     verify_store = hs->config->cert->verify_store;
   }
 
   X509 *leaf = sk_X509_value(cert_chain, 0);
   ScopedX509_STORE_CTX ctx;
-  if (!X509_STORE_CTX_init(ctx.get(), verify_store, leaf, cert_chain)) {
+  if (!X509_STORE_CTX_init(ctx.get(), verify_store, leaf, cert_chain) ||
+      !X509_STORE_CTX_set_ex_data(
+          ctx.get(), SSL_get_ex_data_X509_STORE_CTX_idx(), hs->ssl) ||
+      // We need to inherit the verify parameters. These can be determined by
+      // the context: if its a server it will verify SSL client certificates or
+      // vice versa.
+      !X509_STORE_CTX_set_default(
+          ctx.get(), hs->ssl->server ? "ssl_client" : "ssl_server") ||
+      // Anything non-default in "param" should overwrite anything in the ctx.
+      !X509_VERIFY_PARAM_set1(X509_STORE_CTX_get0_param(ctx.get()),
+                              hs->config->param)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_X509_LIB);
-    return 0;
+    return false;
   }
-  if (!X509_STORE_CTX_set_ex_data(
-          ctx.get(), SSL_get_ex_data_X509_STORE_CTX_idx(), hs->ssl)) {
-    return 0;
-  }
-
-  // We need to inherit the verify parameters. These can be determined by the
-  // context: if its a server it will verify SSL client certificates or vice
-  // versa.
-  X509_STORE_CTX_set_default(ctx.get(),
-                             hs->ssl->server ? "ssl_client" : "ssl_server");
-
-  // Anything non-default in "param" should overwrite anything in the ctx.
-  X509_VERIFY_PARAM_set1(X509_STORE_CTX_get0_param(ctx.get()),
-                         hs->config->param);
 
   if (hs->config->verify_callback) {
     X509_STORE_CTX_set_verify_cb(ctx.get(), hs->config->verify_callback);
   }
 
   int verify_ret;
-  if (ssl_ctx->app_verify_callback != NULL) {
+  if (ssl_ctx->app_verify_callback != nullptr) {
     verify_ret =
         ssl_ctx->app_verify_callback(ctx.get(), ssl_ctx->app_verify_arg);
   } else {
@@ -412,59 +408,59 @@
   // If |SSL_VERIFY_NONE|, the error is non-fatal, but we keep the result.
   if (verify_ret <= 0 && hs->config->verify_mode != SSL_VERIFY_NONE) {
     *out_alert = SSL_alert_from_verify_result(ctx->error);
-    return 0;
+    return false;
   }
 
   ERR_clear_error();
-  return 1;
+  return true;
 }
 
 static void ssl_crypto_x509_hs_flush_cached_ca_names(SSL_HANDSHAKE *hs) {
   sk_X509_NAME_pop_free(hs->cached_x509_ca_names, X509_NAME_free);
-  hs->cached_x509_ca_names = NULL;
+  hs->cached_x509_ca_names = nullptr;
 }
 
-static int ssl_crypto_x509_ssl_new(SSL_HANDSHAKE *hs) {
+static bool ssl_crypto_x509_ssl_new(SSL_HANDSHAKE *hs) {
   hs->config->param = X509_VERIFY_PARAM_new();
-  if (hs->config->param == NULL) {
-    return 0;
+  if (hs->config->param == nullptr) {
+    return false;
   }
   X509_VERIFY_PARAM_inherit(hs->config->param, hs->ssl->ctx->param);
-  return 1;
+  return true;
 }
 
 static void ssl_crypto_x509_ssl_flush_cached_client_CA(SSL_CONFIG *cfg) {
   sk_X509_NAME_pop_free(cfg->cached_x509_client_CA, X509_NAME_free);
-  cfg->cached_x509_client_CA = NULL;
+  cfg->cached_x509_client_CA = nullptr;
 }
 
 static void ssl_crypto_x509_ssl_config_free(SSL_CONFIG *cfg) {
   sk_X509_NAME_pop_free(cfg->cached_x509_client_CA, X509_NAME_free);
-  cfg->cached_x509_client_CA = NULL;
+  cfg->cached_x509_client_CA = nullptr;
   X509_VERIFY_PARAM_free(cfg->param);
 }
 
-static int ssl_crypto_x509_ssl_auto_chain_if_needed(SSL_HANDSHAKE *hs) {
+static bool ssl_crypto_x509_ssl_auto_chain_if_needed(SSL_HANDSHAKE *hs) {
   // Only build a chain if there are no intermediates configured and the feature
   // isn't disabled.
   if ((hs->ssl->mode & SSL_MODE_NO_AUTO_CHAIN) ||
       !ssl_has_certificate(hs) || hs->config->cert->chain == NULL ||
       sk_CRYPTO_BUFFER_num(hs->config->cert->chain.get()) > 1) {
-    return 1;
+    return true;
   }
 
   UniquePtr<X509> leaf(X509_parse_from_buffer(
       sk_CRYPTO_BUFFER_value(hs->config->cert->chain.get(), 0)));
   if (!leaf) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_X509_LIB);
-    return 0;
+    return false;
   }
 
   ScopedX509_STORE_CTX ctx;
   if (!X509_STORE_CTX_init(ctx.get(), hs->ssl->ctx->cert_store, leaf.get(),
                            NULL)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_X509_LIB);
-    return 0;
+    return false;
   }
 
   // Attempt to build a chain, ignoring the result.
@@ -475,23 +471,23 @@
   X509_free(sk_X509_shift(ctx->chain));
 
   if (!ssl_cert_set_chain(hs->config->cert.get(), ctx->chain)) {
-    return 0;
+    return false;
   }
 
   ssl_crypto_x509_cert_flush_cached_chain(hs->config->cert.get());
 
-  return 1;
+  return true;
 }
 
 static void ssl_crypto_x509_ssl_ctx_flush_cached_client_CA(SSL_CTX *ctx) {
   sk_X509_NAME_pop_free(ctx->cached_x509_client_CA, X509_NAME_free);
-  ctx->cached_x509_client_CA = NULL;
+  ctx->cached_x509_client_CA = nullptr;
 }
 
-static int ssl_crypto_x509_ssl_ctx_new(SSL_CTX *ctx) {
+static bool ssl_crypto_x509_ssl_ctx_new(SSL_CTX *ctx) {
   ctx->cert_store = X509_STORE_new();
   ctx->param = X509_VERIFY_PARAM_new();
-  return (ctx->cert_store != NULL && ctx->param != NULL);
+  return (ctx->cert_store != nullptr && ctx->param != nullptr);
 }
 
 static void ssl_crypto_x509_ssl_ctx_free(SSL_CTX *ctx) {
diff --git a/src/ssl/t1_enc.cc b/src/ssl/t1_enc.cc
index c6b2844..4c2fffb 100644
--- a/src/ssl/t1_enc.cc
+++ b/src/ssl/t1_enc.cc
@@ -359,27 +359,3 @@
       MakeConstSpan(session->master_key, session->master_key_length),
       MakeConstSpan(label, label_len), seed, {});
 }
-
-int SSL_export_early_keying_material(
-    SSL *ssl, uint8_t *out, size_t out_len, const char *label, size_t label_len,
-    const uint8_t *context, size_t context_len) {
-  if (!SSL_in_early_data(ssl) &&
-      (!ssl->s3->have_version ||
-       ssl_protocol_version(ssl) < TLS1_3_VERSION)) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_SSL_VERSION);
-    return 0;
-  }
-
-  // The early exporter only exists if we accepted early data or offered it as
-  // a client.
-  if (!SSL_in_early_data(ssl) && !SSL_early_data_accepted(ssl)) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_EARLY_DATA_NOT_IN_USE);
-    return 0;
-  }
-
-  return tls13_export_keying_material(
-      ssl, MakeSpan(out, out_len),
-      MakeConstSpan(ssl->s3->early_exporter_secret,
-                    ssl->s3->early_exporter_secret_len),
-      MakeConstSpan(label, label_len), MakeConstSpan(context, context_len));
-}
diff --git a/src/ssl/t1_lib.cc b/src/ssl/t1_lib.cc
index 87f1888..c1c41a8 100644
--- a/src/ssl/t1_lib.cc
+++ b/src/ssl/t1_lib.cc
@@ -199,6 +199,10 @@
   return true;
 }
 
+static bool is_post_quantum_group(uint16_t id) {
+  return id == SSL_CURVE_CECPQ2 || id == SSL_CURVE_CECPQ2b;
+}
+
 bool ssl_client_hello_init(const SSL *ssl, SSL_CLIENT_HELLO *out,
                            const SSLMessage &msg) {
   OPENSSL_memset(out, 0, sizeof(*out));
@@ -325,10 +329,10 @@
   for (uint16_t pref_group : pref) {
     for (uint16_t supp_group : supp) {
       if (pref_group == supp_group &&
-          // CECPQ2 doesn't fit in the u8-length-prefixed ECPoint field in TLS
-          // 1.2 and below.
+          // CECPQ2(b) doesn't fit in the u8-length-prefixed ECPoint field in
+          // TLS 1.2 and below.
           (ssl_protocol_version(ssl) >= TLS1_3_VERSION ||
-           pref_group != SSL_CURVE_CECPQ2)) {
+           !is_post_quantum_group(pref_group))) {
         *out_group_id = pref_group;
         return true;
       }
@@ -390,9 +394,9 @@
 }
 
 bool tls1_check_group_id(const SSL_HANDSHAKE *hs, uint16_t group_id) {
-  if (group_id == SSL_CURVE_CECPQ2 &&
+  if (is_post_quantum_group(group_id) &&
       ssl_protocol_version(hs->ssl) < TLS1_3_VERSION) {
-    // CECPQ2 requires TLS 1.3.
+    // CECPQ2(b) requires TLS 1.3.
     return false;
   }
 
@@ -629,45 +633,7 @@
 
 static bool ext_sni_parse_clienthello(SSL_HANDSHAKE *hs, uint8_t *out_alert,
                                       CBS *contents) {
-  SSL *const ssl = hs->ssl;
-  if (contents == NULL) {
-    return true;
-  }
-
-  CBS server_name_list, host_name;
-  uint8_t name_type;
-  if (!CBS_get_u16_length_prefixed(contents, &server_name_list) ||
-      !CBS_get_u8(&server_name_list, &name_type) ||
-      // Although the server_name extension was intended to be extensible to
-      // new name types and multiple names, OpenSSL 1.0.x had a bug which meant
-      // different name types will cause an error. Further, RFC 4366 originally
-      // defined syntax inextensibly. RFC 6066 corrected this mistake, but
-      // adding new name types is no longer feasible.
-      //
-      // Act as if the extensibility does not exist to simplify parsing.
-      !CBS_get_u16_length_prefixed(&server_name_list, &host_name) ||
-      CBS_len(&server_name_list) != 0 ||
-      CBS_len(contents) != 0) {
-    return false;
-  }
-
-  if (name_type != TLSEXT_NAMETYPE_host_name ||
-      CBS_len(&host_name) == 0 ||
-      CBS_len(&host_name) > TLSEXT_MAXLEN_host_name ||
-      CBS_contains_zero_byte(&host_name)) {
-    *out_alert = SSL_AD_UNRECOGNIZED_NAME;
-    return false;
-  }
-
-  // Copy the hostname as a string.
-  char *raw = nullptr;
-  if (!CBS_strdup(&host_name, &raw)) {
-    *out_alert = SSL_AD_INTERNAL_ERROR;
-    return false;
-  }
-  ssl->s3->hostname.reset(raw);
-
-  hs->should_ack_sni = true;
+  // SNI has already been parsed earlier in the handshake. See |extract_sni|.
   return true;
 }
 
@@ -1790,7 +1756,7 @@
 }
 
 static bool ext_ec_point_add_clienthello(SSL_HANDSHAKE *hs, CBB *out) {
-  // The point format extension is unneccessary in TLS 1.3.
+  // The point format extension is unnecessary in TLS 1.3.
   if (hs->min_version >= TLS1_3_VERSION) {
     return true;
   }
@@ -2057,20 +2023,46 @@
 
 static bool ext_early_data_add_clienthello(SSL_HANDSHAKE *hs, CBB *out) {
   SSL *const ssl = hs->ssl;
-  if (!ssl->enable_early_data ||
-      // Session must be 0-RTT capable.
-      ssl->session == nullptr ||
-      ssl_session_protocol_version(ssl->session.get()) < TLS1_3_VERSION ||
-      ssl->session->ticket_max_early_data == 0 ||
-      // The second ClientHello never offers early data.
-      hs->received_hello_retry_request ||
-      // In case ALPN preferences changed since this session was established,
-      // avoid reporting a confusing value in |SSL_get0_alpn_selected|.
-      (!ssl->session->early_alpn.empty() &&
-       !ssl_is_alpn_protocol_allowed(hs, ssl->session->early_alpn))) {
+  // The second ClientHello never offers early data, and we must have already
+  // filled in |early_data_reason| by this point.
+  if (hs->received_hello_retry_request) {
+    assert(ssl->s3->early_data_reason != ssl_early_data_unknown);
     return true;
   }
 
+  if (!ssl->enable_early_data) {
+    ssl->s3->early_data_reason = ssl_early_data_disabled;
+    return true;
+  }
+
+  if (hs->max_version < TLS1_3_VERSION) {
+    // We discard inapplicable sessions, so this is redundant with the session
+    // checks below, but we check give a more useful reason.
+    ssl->s3->early_data_reason = ssl_early_data_protocol_version;
+    return true;
+  }
+
+  if (ssl->session == nullptr) {
+    ssl->s3->early_data_reason = ssl_early_data_no_session_offered;
+    return true;
+  }
+
+  if (ssl_session_protocol_version(ssl->session.get()) < TLS1_3_VERSION ||
+      ssl->session->ticket_max_early_data == 0) {
+    ssl->s3->early_data_reason = ssl_early_data_unsupported_for_session;
+    return true;
+  }
+
+  // In case ALPN preferences changed since this session was established, avoid
+  // reporting a confusing value in |SSL_get0_alpn_selected| and sending early
+  // data we know will be rejected.
+  if (!ssl->session->early_alpn.empty() &&
+      !ssl_is_alpn_protocol_allowed(hs, ssl->session->early_alpn)) {
+    ssl->s3->early_data_reason = ssl_early_data_alpn_mismatch;
+    return true;
+  }
+
+  // |early_data_reason| will be filled in later when the server responds.
   hs->early_data_offered = true;
 
   if (!CBB_add_u16(out, TLSEXT_TYPE_early_data) ||
@@ -2083,12 +2075,27 @@
 }
 
 static bool ext_early_data_parse_serverhello(SSL_HANDSHAKE *hs,
-                                             uint8_t *out_alert, CBS *contents) {
+                                             uint8_t *out_alert,
+                                             CBS *contents) {
   SSL *const ssl = hs->ssl;
   if (contents == NULL) {
+    if (hs->early_data_offered && !hs->received_hello_retry_request) {
+      ssl->s3->early_data_reason = ssl->s3->session_reused
+                                       ? ssl_early_data_peer_declined
+                                       : ssl_early_data_session_not_resumed;
+    } else {
+      // We already filled in |early_data_reason| when declining to offer 0-RTT
+      // or handling the implicit HelloRetryRequest reject.
+      assert(ssl->s3->early_data_reason != ssl_early_data_unknown);
+    }
     return true;
   }
 
+  // If we received an HRR, the second ClientHello never offers early data, so
+  // the extensions logic will automatically reject early data extensions as
+  // unsolicited. This covered by the ServerAcceptsEarlyDataOnHRR test.
+  assert(!hs->received_hello_retry_request);
+
   if (CBS_len(contents) != 0) {
     *out_alert = SSL_AD_DECODE_ERROR;
     return false;
@@ -2100,6 +2107,7 @@
     return false;
   }
 
+  ssl->s3->early_data_reason = ssl_early_data_accepted;
   ssl->s3->early_data_accepted = true;
   return true;
 }
@@ -2186,8 +2194,8 @@
 
     group_id = groups[0];
 
-    if (group_id == SSL_CURVE_CECPQ2 && groups.size() >= 2) {
-      // CECPQ2 is not sent as the only initial key share. We'll include the
+    if (is_post_quantum_group(group_id) && groups.size() >= 2) {
+      // CECPQ2(b) is not sent as the only initial key share. We'll include the
       // 2nd preference group too to avoid round-trips.
       second_group_id = groups[1];
       assert(second_group_id != group_id);
@@ -2424,7 +2432,7 @@
   }
 
   for (uint16_t group : tls1_get_grouplist(hs)) {
-    if (group == SSL_CURVE_CECPQ2 &&
+    if (is_post_quantum_group(group) &&
         hs->max_version < TLS1_3_VERSION) {
       continue;
     }
@@ -2840,6 +2848,67 @@
   return true;
 }
 
+
+// Post-quantum experiment signal
+//
+// This extension may be used in order to identify a control group for
+// experimenting with post-quantum key exchange algorithms.
+
+static bool ext_pq_experiment_signal_add_clienthello(SSL_HANDSHAKE *hs,
+                                                     CBB *out) {
+  if (hs->ssl->ctx->pq_experiment_signal &&
+      (!CBB_add_u16(out, TLSEXT_TYPE_pq_experiment_signal) ||
+       !CBB_add_u16(out, 0))) {
+    return false;
+  }
+
+  return true;
+}
+
+static bool ext_pq_experiment_signal_parse_serverhello(SSL_HANDSHAKE *hs,
+                                                       uint8_t *out_alert,
+                                                       CBS *contents) {
+  if (contents == nullptr) {
+    return true;
+  }
+
+  if (!hs->ssl->ctx->pq_experiment_signal || CBS_len(contents) != 0) {
+    return false;
+  }
+
+  hs->ssl->s3->pq_experiment_signal_seen = true;
+  return true;
+}
+
+static bool ext_pq_experiment_signal_parse_clienthello(SSL_HANDSHAKE *hs,
+                                                       uint8_t *out_alert,
+                                                       CBS *contents) {
+  if (contents == nullptr) {
+    return true;
+  }
+
+  if (CBS_len(contents) != 0) {
+    return false;
+  }
+
+  if (hs->ssl->ctx->pq_experiment_signal) {
+    hs->ssl->s3->pq_experiment_signal_seen = true;
+  }
+
+  return true;
+}
+
+static bool ext_pq_experiment_signal_add_serverhello(SSL_HANDSHAKE *hs,
+                                                     CBB *out) {
+  if (hs->ssl->s3->pq_experiment_signal_seen &&
+      (!CBB_add_u16(out, TLSEXT_TYPE_pq_experiment_signal) ||
+       !CBB_add_u16(out, 0))) {
+    return false;
+  }
+
+  return true;
+}
+
 // kExtensions contains all the supported extensions.
 static const struct tls_extension kExtensions[] = {
   {
@@ -3028,6 +3097,14 @@
     ext_delegated_credential_parse_clienthello,
     dont_add_serverhello,
   },
+  {
+    TLSEXT_TYPE_pq_experiment_signal,
+    NULL,
+    ext_pq_experiment_signal_add_clienthello,
+    ext_pq_experiment_signal_parse_serverhello,
+    ext_pq_experiment_signal_parse_clienthello,
+    ext_pq_experiment_signal_add_serverhello,
+  },
 };
 
 #define kNumExtensions (sizeof(kExtensions) / sizeof(struct tls_extension))
@@ -3061,6 +3138,9 @@
     return false;
   }
 
+  // Note we may send multiple ClientHellos for DTLS HelloVerifyRequest and TLS
+  // 1.3 HelloRetryRequest. For the latter, the extensions may change, so it is
+  // important to reset this value.
   hs->extensions.sent = 0;
 
   for (size_t i = 0; i < kNumExtensions; i++) {
diff --git a/src/ssl/test/bssl_shim.cc b/src/ssl/test/bssl_shim.cc
index 62db076..f58c151 100644
--- a/src/ssl/test/bssl_shim.cc
+++ b/src/ssl/test/bssl_shim.cc
@@ -279,23 +279,23 @@
 // after a renegotiation, that authentication-related properties match |config|.
 static bool CheckAuthProperties(SSL *ssl, bool is_resume,
                                 const TestConfig *config) {
-  if (!config->expected_ocsp_response.empty()) {
+  if (!config->expect_ocsp_response.empty()) {
     const uint8_t *data;
     size_t len;
     SSL_get0_ocsp_response(ssl, &data, &len);
-    if (config->expected_ocsp_response.size() != len ||
-        OPENSSL_memcmp(config->expected_ocsp_response.data(), data, len) != 0) {
+    if (config->expect_ocsp_response.size() != len ||
+        OPENSSL_memcmp(config->expect_ocsp_response.data(), data, len) != 0) {
       fprintf(stderr, "OCSP response mismatch\n");
       return false;
     }
   }
 
-  if (!config->expected_signed_cert_timestamps.empty()) {
+  if (!config->expect_signed_cert_timestamps.empty()) {
     const uint8_t *data;
     size_t len;
     SSL_get0_signed_cert_timestamp_list(ssl, &data, &len);
-    if (config->expected_signed_cert_timestamps.size() != len ||
-        OPENSSL_memcmp(config->expected_signed_cert_timestamps.data(), data,
+    if (config->expect_signed_cert_timestamps.size() != len ||
+        OPENSSL_memcmp(config->expect_signed_cert_timestamps.data(), data,
                        len) != 0) {
       fprintf(stderr, "SCT list mismatch\n");
       return false;
@@ -389,6 +389,39 @@
   return true;
 }
 
+static const char *EarlyDataReasonToString(ssl_early_data_reason_t reason) {
+  switch (reason) {
+    case ssl_early_data_unknown:
+      return "unknown";
+    case ssl_early_data_disabled:
+      return "disabled";
+    case ssl_early_data_accepted:
+      return "accepted";
+    case ssl_early_data_protocol_version:
+      return "protocol_version";
+    case ssl_early_data_peer_declined:
+      return "peer_declined";
+    case ssl_early_data_no_session_offered:
+      return "no_session_offered";
+    case ssl_early_data_session_not_resumed:
+      return "session_not_resumed";
+    case ssl_early_data_unsupported_for_session:
+      return "unsupported_for_session";
+    case ssl_early_data_hello_retry_request:
+      return "hello_retry_request";
+    case ssl_early_data_alpn_mismatch:
+      return "alpn_mismatch";
+    case ssl_early_data_channel_id:
+      return "channel_id";
+    case ssl_early_data_token_binding:
+      return "token_binding";
+    case ssl_early_data_ticket_age_skew:
+      return "ticket_age_skew";
+  }
+
+  abort();
+}
+
 // CheckHandshakeProperties checks, immediately after |ssl| completes its
 // initial handshake (or False Starts), whether all the properties are
 // consistent with the test configuration and invariants.
@@ -459,23 +492,23 @@
     return false;
   }
 
-  if (!config->expected_server_name.empty()) {
+  if (!config->expect_server_name.empty()) {
     const char *server_name =
         SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
     if (server_name == nullptr ||
-        server_name != config->expected_server_name) {
+        server_name != config->expect_server_name) {
       fprintf(stderr, "servername mismatch (got %s; want %s)\n",
-              server_name, config->expected_server_name.c_str());
+              server_name, config->expect_server_name.c_str());
       return false;
     }
   }
 
-  if (!config->expected_next_proto.empty()) {
+  if (!config->expect_next_proto.empty()) {
     const uint8_t *next_proto;
     unsigned next_proto_len;
     SSL_get0_next_proto_negotiated(ssl, &next_proto, &next_proto_len);
-    if (next_proto_len != config->expected_next_proto.size() ||
-        OPENSSL_memcmp(next_proto, config->expected_next_proto.data(),
+    if (next_proto_len != config->expect_next_proto.size() ||
+        OPENSSL_memcmp(next_proto, config->expect_next_proto.data(),
                        next_proto_len) != 0) {
       fprintf(stderr, "negotiated next proto mismatch\n");
       return false;
@@ -486,48 +519,48 @@
     const uint8_t *alpn_proto;
     unsigned alpn_proto_len;
     SSL_get0_alpn_selected(ssl, &alpn_proto, &alpn_proto_len);
-    if (alpn_proto_len != config->expected_alpn.size() ||
-        OPENSSL_memcmp(alpn_proto, config->expected_alpn.data(),
+    if (alpn_proto_len != config->expect_alpn.size() ||
+        OPENSSL_memcmp(alpn_proto, config->expect_alpn.data(),
                        alpn_proto_len) != 0) {
       fprintf(stderr, "negotiated alpn proto mismatch\n");
       return false;
     }
   }
 
-  if (!config->expected_quic_transport_params.empty()) {
+  if (!config->expect_quic_transport_params.empty()) {
     const uint8_t *peer_params;
     size_t peer_params_len;
     SSL_get_peer_quic_transport_params(ssl, &peer_params, &peer_params_len);
-    if (peer_params_len != config->expected_quic_transport_params.size() ||
+    if (peer_params_len != config->expect_quic_transport_params.size() ||
         OPENSSL_memcmp(peer_params,
-                       config->expected_quic_transport_params.data(),
+                       config->expect_quic_transport_params.data(),
                        peer_params_len) != 0) {
       fprintf(stderr, "QUIC transport params mismatch\n");
       return false;
     }
   }
 
-  if (!config->expected_channel_id.empty()) {
+  if (!config->expect_channel_id.empty()) {
     uint8_t channel_id[64];
     if (!SSL_get_tls_channel_id(ssl, channel_id, sizeof(channel_id))) {
       fprintf(stderr, "no channel id negotiated\n");
       return false;
     }
-    if (config->expected_channel_id.size() != 64 ||
-        OPENSSL_memcmp(config->expected_channel_id.data(), channel_id, 64) !=
+    if (config->expect_channel_id.size() != 64 ||
+        OPENSSL_memcmp(config->expect_channel_id.data(), channel_id, 64) !=
             0) {
       fprintf(stderr, "channel id mismatch\n");
       return false;
     }
   }
 
-  if (config->expected_token_binding_param != -1) {
+  if (config->expect_token_binding_param != -1) {
     if (!SSL_is_token_binding_negotiated(ssl)) {
       fprintf(stderr, "no Token Binding negotiated\n");
       return false;
     }
     if (SSL_get_negotiated_token_binding_param(ssl) !=
-        static_cast<uint8_t>(config->expected_token_binding_param)) {
+        static_cast<uint8_t>(config->expect_token_binding_param)) {
       fprintf(stderr, "Token Binding param mismatch\n");
       return false;
     }
@@ -587,7 +620,8 @@
     return false;
   }
 
-  if (is_resume && !SSL_in_early_data(ssl)) {
+  // The early data status is only applicable after the handshake is confirmed.
+  if (!SSL_in_early_data(ssl)) {
     if ((config->expect_accept_early_data && !SSL_early_data_accepted(ssl)) ||
         (config->expect_reject_early_data && SSL_early_data_accepted(ssl))) {
       fprintf(stderr,
@@ -595,6 +629,15 @@
               SSL_early_data_accepted(ssl) ? "" : " not");
       return false;
     }
+
+    const char *early_data_reason =
+        EarlyDataReasonToString(SSL_get_early_data_reason(ssl));
+    if (!config->expect_early_data_reason.empty() &&
+        config->expect_early_data_reason != early_data_reason) {
+      fprintf(stderr, "Early data reason was \"%s\", expected \"%s\"\n",
+              early_data_reason, config->expect_early_data_reason.c_str());
+      return false;
+    }
   }
 
   if (!config->psk.empty()) {
@@ -622,6 +665,21 @@
     return false;
   }
 
+  if (config->expect_delegated_credential_used !=
+      !!SSL_delegated_credential_used(ssl)) {
+    fprintf(stderr,
+            "Got %s delegated credential usage, but wanted opposite. \n",
+            SSL_delegated_credential_used(ssl) ? "" : "no");
+    return false;
+  }
+
+  if (config->expect_pq_experiment_signal !=
+      !!SSL_pq_experiment_signal_seen(ssl)) {
+    fprintf(stderr, "Got %sPQ experiment signal, but wanted opposite. \n",
+            SSL_pq_experiment_signal_seen(ssl) ? "" : "no ");
+    return false;
+  }
+
   return true;
 }
 
@@ -701,6 +759,16 @@
       return false;
     }
 
+    // Client pre- and post-0-RTT reject states are considered logically
+    // different connections with different test expections. Check that the test
+    // did not mistakenly configure reason expectations on the wrong one.
+    if (!config->expect_early_data_reason.empty()) {
+      fprintf(stderr,
+              "Test error: client reject -expect-early-data-reason flags "
+              "should be configured with -on-retry, not -on-resume.\n");
+      return false;
+    }
+
     // Reset the connection and try again at 1-RTT.
     SSL_reset_early_data_reject(ssl.get());
     GetTestState(ssl.get())->cert_verified = false;
@@ -804,22 +872,6 @@
     GetTestState(ssl)->got_new_session = false;
   }
 
-  if (config->export_early_keying_material > 0) {
-    std::vector<uint8_t> result(
-        static_cast<size_t>(config->export_early_keying_material));
-    if (!SSL_export_early_keying_material(
-            ssl, result.data(), result.size(), config->export_label.data(),
-            config->export_label.size(),
-            reinterpret_cast<const uint8_t *>(config->export_context.data()),
-            config->export_context.size())) {
-      fprintf(stderr, "failed to export keying material\n");
-      return false;
-    }
-    if (WriteAll(ssl, result.data(), result.size()) < 0) {
-      return false;
-    }
-  }
-
   if (config->export_keying_material > 0) {
     std::vector<uint8_t> result(
         static_cast<size_t>(config->export_keying_material));
diff --git a/src/ssl/test/runner/common.go b/src/ssl/test/runner/common.go
index bbcacf5..b56b9b3 100644
--- a/src/ssl/test/runner/common.go
+++ b/src/ssl/test/runner/common.go
@@ -126,6 +126,7 @@
 	extensionQUICTransportParams        uint16 = 0xffa5 // draft-ietf-quic-tls-13
 	extensionChannelID                  uint16 = 30032  // not IANA assigned
 	extensionDelegatedCredentials       uint16 = 0xff02 // not IANA assigned
+	extensionPQExperimentSignal         uint16 = 54538
 )
 
 // TLS signaling cipher suite values
@@ -144,12 +145,13 @@
 type CurveID uint16
 
 const (
-	CurveP224   CurveID = 21
-	CurveP256   CurveID = 23
-	CurveP384   CurveID = 24
-	CurveP521   CurveID = 25
-	CurveX25519 CurveID = 29
-	CurveCECPQ2 CurveID = 16696
+	CurveP224    CurveID = 21
+	CurveP256    CurveID = 23
+	CurveP384    CurveID = 24
+	CurveP521    CurveID = 25
+	CurveX25519  CurveID = 29
+	CurveCECPQ2  CurveID = 16696
+	CurveCECPQ2b CurveID = 65074
 )
 
 // TLS Elliptic Curve Point Formats
@@ -499,6 +501,11 @@
 
 	CertCompressionAlgs map[uint16]CertCompressionAlg
 
+	// PQExperimentSignal instructs a client to send a non-IANA defined extension
+	// that signals participation in an experiment of post-quantum key exchange
+	// methods.
+	PQExperimentSignal bool
+
 	// Bugs specifies optional misbehaviour to be used for testing other
 	// implementations.
 	Bugs ProtocolBugs
@@ -1319,21 +1326,6 @@
 	// it was accepted.
 	SendEarlyDataExtension bool
 
-	// ExpectEarlyKeyingMaterial, if non-zero, causes a TLS 1.3 server to
-	// read an application data record after the ClientHello before it sends
-	// a ServerHello. The record's contents have the specified length and
-	// match the corresponding early exporter value. This is used to test
-	// the client using the early exporter in the 0-RTT state.
-	ExpectEarlyKeyingMaterial int
-
-	// ExpectEarlyKeyingLabel is the label to use with
-	// ExpectEarlyKeyingMaterial.
-	ExpectEarlyKeyingLabel string
-
-	// ExpectEarlyKeyingContext is the context string to use with
-	// ExpectEarlyKeyingMaterial
-	ExpectEarlyKeyingContext string
-
 	// ExpectEarlyData causes a TLS 1.3 server to read application
 	// data after the ClientHello (assuming the server is able to
 	// derive the key under which the data is encrypted) before it
@@ -1649,6 +1641,10 @@
 	// DisableDelegatedCredentials, if true, disables client support for delegated
 	// credentials.
 	DisableDelegatedCredentials bool
+
+	// ExpectPQExperimentSignal specifies whether or not the post-quantum
+	// experiment signal should be received by a client or server.
+	ExpectPQExperimentSignal bool
 }
 
 func (c *Config) serverInit() {
@@ -1728,7 +1724,7 @@
 	return ret
 }
 
-var defaultCurvePreferences = []CurveID{CurveCECPQ2, CurveX25519, CurveP256, CurveP384, CurveP521}
+var defaultCurvePreferences = []CurveID{CurveCECPQ2b, CurveCECPQ2, CurveX25519, CurveP256, CurveP384, CurveP521}
 
 func (c *Config) curvePreferences() []CurveID {
 	if c == nil || len(c.CurvePreferences) == 0 {
diff --git a/src/ssl/test/runner/ecdsa_p224_key.pem b/src/ssl/test/runner/ecdsa_p224_key.pem
index cfe411b..d62594b 100644
--- a/src/ssl/test/runner/ecdsa_p224_key.pem
+++ b/src/ssl/test/runner/ecdsa_p224_key.pem
@@ -1,5 +1,5 @@
------BEGIN EC PRIVATE KEY-----
-MGgCAQEEHGi+rNLi+gHJqmRRtdlLBOw1WYv7H/VnlYGAZ0+gBwYFK4EEACGhPAM6
-AATp26Xp0vT4LKigWIorhX4Rg1g9sxmgtPFFyNGCvDd1vpDMEC+INLEoANNW7JGZ
-pULvgx/pugEcOQ==
------END EC PRIVATE KEY-----
+-----BEGIN PRIVATE KEY-----
+MHgCAQAwEAYHKoZIzj0CAQYFK4EEACEEYTBfAgEBBBxovqzS4voByapkUbXZSwTs
+NVmL+x/1Z5WBgGdPoTwDOgAE6dul6dL0+CyooFiKK4V+EYNYPbMZoLTxRcjRgrw3
+db6QzBAviDSxKADTVuyRmaVC74Mf6boBHDk=
+-----END PRIVATE KEY-----
diff --git a/src/ssl/test/runner/fuzzer_mode.json b/src/ssl/test/runner/fuzzer_mode.json
index 1a154c2..0a50722 100644
--- a/src/ssl/test/runner/fuzzer_mode.json
+++ b/src/ssl/test/runner/fuzzer_mode.json
@@ -18,9 +18,6 @@
 
     "BadECDSA-*": "Fuzzer mode always accepts a signature.",
     "*-InvalidSignature-*": "Fuzzer mode always accepts a signature.",
-    "*Auth-Verify-RSA-PKCS1-*-TLS13*": "Fuzzer mode always accepts a signature.",
-    "*Auth-Verify-ECDSA-SHA1-TLS13*": "Fuzzer mode always accepts a signature.",
-    "*Auth-Verify-ECDSA-P224-*-TLS13*": "Fuzzer mode always accepts a signature.",
     "Verify-*Auth-SignatureType*": "Fuzzer mode always accepts a signature.",
     "ECDSACurveMismatch-Verify-TLS13*": "Fuzzer mode always accepts a signature.",
     "InvalidChannelIDSignature-*": "Fuzzer mode always accepts a signature.",
@@ -30,6 +27,7 @@
     "Resume-Server-DeclineCrossVersion*": "Fuzzer mode does not encrypt tickets.",
     "TicketCallback-SingleCall-*": "Fuzzer mode does not encrypt tickets.",
     "CorruptTicket-*": "Fuzzer mode does not encrypt tickets.",
+    "*RejectTicket-Server-*": "Fuzzer mode does not encrypt tickets.",
     "ShimTicketRewritable*": "Fuzzer mode does not encrypt tickets.",
 
     "Resume-Server-*Binder*": "Fuzzer mode does not check binders.",
@@ -46,6 +44,7 @@
     "*-EarlyData-RejectUnfinishedWrite-Client-*": "Trial decryption does not work with the NULL cipher.",
     "EarlyData-Reject*-Client-*": "Trial decryption does not work with the NULL cipher.",
     "CustomExtensions-Server-EarlyDataOffered": "Trial decryption does not work with the NULL cipher.",
+    "*-TicketAgeSkew-*-Reject": "Trial decryption does not work with the NULL cipher.",
 
     "Renegotiate-Client-BadExt*": "Fuzzer mode does not check renegotiation_info.",
 
diff --git a/src/ssl/test/runner/handshake_client.go b/src/ssl/test/runner/handshake_client.go
index 45dc75d..2574ec3 100644
--- a/src/ssl/test/runner/handshake_client.go
+++ b/src/ssl/test/runner/handshake_client.go
@@ -129,6 +129,7 @@
 		omitExtensions:          c.config.Bugs.OmitExtensions,
 		emptyExtensions:         c.config.Bugs.EmptyExtensions,
 		delegatedCredentials:    !c.config.Bugs.DisableDelegatedCredentials,
+		pqExperimentSignal:      c.config.PQExperimentSignal,
 	}
 
 	if maxVersion >= VersionTLS13 {
@@ -1666,6 +1667,10 @@
 		c.quicTransportParams = serverExtensions.quicTransportParams
 	}
 
+	if c.config.Bugs.ExpectPQExperimentSignal != serverExtensions.pqExperimentSignal {
+		return fmt.Errorf("tls: PQ experiment signal presence (%t) was not what was expected", serverExtensions.pqExperimentSignal)
+	}
+
 	return nil
 }
 
diff --git a/src/ssl/test/runner/handshake_messages.go b/src/ssl/test/runner/handshake_messages.go
index f12ca1a..ac52eed 100644
--- a/src/ssl/test/runner/handshake_messages.go
+++ b/src/ssl/test/runner/handshake_messages.go
@@ -298,6 +298,7 @@
 	pad                     int
 	compressedCertAlgs      []uint16
 	delegatedCredentials    bool
+	pqExperimentSignal      bool
 }
 
 func (m *clientHelloMsg) equal(i interface{}) bool {
@@ -352,7 +353,8 @@
 		m.emptyExtensions == m1.emptyExtensions &&
 		m.pad == m1.pad &&
 		eqUint16s(m.compressedCertAlgs, m1.compressedCertAlgs) &&
-		m.delegatedCredentials == m1.delegatedCredentials
+		m.delegatedCredentials == m1.delegatedCredentials &&
+		m.pqExperimentSignal == m1.pqExperimentSignal
 }
 
 func (m *clientHelloMsg) marshalKeyShares(bb *byteBuilder) {
@@ -598,6 +600,11 @@
 		extensions.addU16(extensionDelegatedCredentials)
 		extensions.addU16(0) // Length is always 0
 	}
+	if m.pqExperimentSignal {
+		extensions.addU16(extensionPQExperimentSignal)
+		extensions.addU16(0) // Length is always 0
+	}
+
 	// The PSK extension must be last. See https://tools.ietf.org/html/rfc8446#section-4.2.11
 	if len(m.pskIdentities) > 0 && !m.pskBinderFirst {
 		extensions.addU16(extensionPreSharedKey)
@@ -724,6 +731,7 @@
 	m.extendedMasterSecret = false
 	m.customExtension = ""
 	m.delegatedCredentials = false
+	m.pqExperimentSignal = false
 
 	if len(reader) == 0 {
 		// ClientHello is optionally followed by extension data
@@ -959,6 +967,11 @@
 				return false
 			}
 			m.delegatedCredentials = true
+		case extensionPQExperimentSignal:
+			if len(body) != 0 {
+				return false
+			}
+			m.pqExperimentSignal = true
 		}
 
 		if isGREASEValue(extension) {
@@ -1226,6 +1239,7 @@
 	supportedCurves         []CurveID
 	quicTransportParams     []byte
 	serverNameAck           bool
+	pqExperimentSignal      bool
 }
 
 func (m *serverExtensions) marshal(extensions *byteBuilder) {
@@ -1360,6 +1374,10 @@
 		extensions.addU16(extensionServerName)
 		extensions.addU16(0) // zero length
 	}
+	if m.pqExperimentSignal {
+		extensions.addU16(extensionPQExperimentSignal)
+		extensions.addU16(0) // zero length
+	}
 }
 
 func (m *serverExtensions) unmarshal(data byteReader, version uint16) bool {
@@ -1468,6 +1486,11 @@
 				return false
 			}
 			m.hasEarlyData = true
+		case extensionPQExperimentSignal:
+			if len(body) != 0 {
+				return false
+			}
+			m.pqExperimentSignal = true
 		default:
 			// Unknown extensions are illegal from the server.
 			return false
diff --git a/src/ssl/test/runner/handshake_server.go b/src/ssl/test/runner/handshake_server.go
index d2ef9b4..3a6c810 100644
--- a/src/ssl/test/runner/handshake_server.go
+++ b/src/ssl/test/runner/handshake_server.go
@@ -210,8 +210,8 @@
 
 	if config.Bugs.FailIfCECPQ2Offered {
 		for _, offeredCurve := range hs.clientHello.supportedCurves {
-			if offeredCurve == CurveCECPQ2 {
-				return errors.New("tls: CECPQ2 was offered")
+			if isPqGroup(offeredCurve) {
+				return errors.New("tls: CECPQ2 or CECPQ2b was offered")
 			}
 		}
 	}
@@ -228,6 +228,10 @@
 		}
 	}
 
+	if c.config.Bugs.ExpectPQExperimentSignal != hs.clientHello.pqExperimentSignal {
+		return fmt.Errorf("tls: PQ experiment signal presence (%t) was not what was expected", hs.clientHello.pqExperimentSignal)
+	}
+
 	c.clientVersion = hs.clientHello.vers
 
 	// Use the versions extension if supplied, otherwise use the legacy ClientHello version.
@@ -722,16 +726,7 @@
 			}
 
 			c.earlyCipherSuite = hs.suite
-			expectEarlyData := config.Bugs.ExpectEarlyData
-			if n := config.Bugs.ExpectEarlyKeyingMaterial; n > 0 {
-				exporter, err := c.ExportEarlyKeyingMaterial(n, []byte(config.Bugs.ExpectEarlyKeyingLabel), []byte(config.Bugs.ExpectEarlyKeyingContext))
-				if err != nil {
-					return err
-				}
-				expectEarlyData = append([][]byte{exporter}, expectEarlyData...)
-			}
-
-			for _, expectedMsg := range expectEarlyData {
+			for _, expectedMsg := range config.Bugs.ExpectEarlyData {
 				if err := c.readRecord(recordTypeApplicationData); err != nil {
 					return err
 				}
@@ -1232,8 +1227,8 @@
 	preferredCurves := config.curvePreferences()
 Curves:
 	for _, curve := range hs.clientHello.supportedCurves {
-		if curve == CurveCECPQ2 && c.vers < VersionTLS13 {
-			// CECPQ2 is TLS 1.3-only.
+		if isPqGroup(curve) && c.vers < VersionTLS13 {
+			// CECPQ2 and CECPQ2b is TLS 1.3-only.
 			continue
 		}
 
@@ -1456,6 +1451,7 @@
 	}
 
 	serverExtensions.serverNameAck = c.config.Bugs.SendServerNameAck
+	serverExtensions.pqExperimentSignal = hs.clientHello.pqExperimentSignal
 
 	return nil
 }
diff --git a/src/ssl/test/runner/key_agreement.go b/src/ssl/test/runner/key_agreement.go
index 13e78bc..f4789b6 100644
--- a/src/ssl/test/runner/key_agreement.go
+++ b/src/ssl/test/runner/key_agreement.go
@@ -19,6 +19,7 @@
 	"boringssl.googlesource.com/boringssl/ssl/test/runner/curve25519"
 	"boringssl.googlesource.com/boringssl/ssl/test/runner/ed25519"
 	"boringssl.googlesource.com/boringssl/ssl/test/runner/hrss"
+	"boringssl.googlesource.com/boringssl/ssl/test/runner/sike"
 )
 
 type keyType int
@@ -433,6 +434,98 @@
 	return preMasterSecret, nil
 }
 
+// cecpq2BCurve implements CECPQ2b, which is SIKE combined with X25519.
+type cecpq2BCurve struct {
+	// Both public key and shared secret size
+	x25519PrivateKey [32]byte
+	sikePrivateKey   *sike.PrivateKey
+}
+
+func (e *cecpq2BCurve) offer(rand io.Reader) (publicKey []byte, err error) {
+	if _, err = io.ReadFull(rand, e.x25519PrivateKey[:]); err != nil {
+		return nil, err
+	}
+
+	var x25519Public [32]byte
+	curve25519.ScalarBaseMult(&x25519Public, &e.x25519PrivateKey)
+
+	e.sikePrivateKey = sike.NewPrivateKey(sike.KeyVariant_SIKE)
+	if err = e.sikePrivateKey.Generate(rand); err != nil {
+		return nil, err
+	}
+
+	sikePublic := e.sikePrivateKey.GeneratePublicKey().Export()
+	var ret []byte
+	ret = append(ret, x25519Public[:]...)
+	ret = append(ret, sikePublic...)
+	return ret, nil
+}
+
+func (e *cecpq2BCurve) accept(rand io.Reader, peerKey []byte) (publicKey []byte, preMasterSecret []byte, err error) {
+	if len(peerKey) != 32+sike.Params.PublicKeySize {
+		return nil, nil, errors.New("tls: bad length CECPQ2b offer")
+	}
+
+	if _, err = io.ReadFull(rand, e.x25519PrivateKey[:]); err != nil {
+		return nil, nil, err
+	}
+
+	var x25519Shared, x25519PeerKey, x25519Public [32]byte
+	copy(x25519PeerKey[:], peerKey)
+	curve25519.ScalarBaseMult(&x25519Public, &e.x25519PrivateKey)
+	curve25519.ScalarMult(&x25519Shared, &e.x25519PrivateKey, &x25519PeerKey)
+
+	// Per RFC 7748, reject the all-zero value in constant time.
+	var zeros [32]byte
+	if subtle.ConstantTimeCompare(zeros[:], x25519Shared[:]) == 1 {
+		return nil, nil, errors.New("tls: X25519 value with wrong order")
+	}
+
+	var sikePubKey = sike.NewPublicKey(sike.KeyVariant_SIKE)
+	if err = sikePubKey.Import(peerKey[32:]); err != nil {
+		// should never happen as size was already checked
+		return nil, nil, errors.New("tls: implementation error")
+	}
+	sikeCiphertext, sikeShared, err := sike.Encapsulate(rand, sikePubKey)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	publicKey = append(publicKey, x25519Public[:]...)
+	publicKey = append(publicKey, sikeCiphertext...)
+	preMasterSecret = append(preMasterSecret, x25519Shared[:]...)
+	preMasterSecret = append(preMasterSecret, sikeShared...)
+
+	return publicKey, preMasterSecret, nil
+}
+
+func (e *cecpq2BCurve) finish(peerKey []byte) (preMasterSecret []byte, err error) {
+	if len(peerKey) != 32+(sike.Params.PublicKeySize+sike.Params.MsgLen) {
+		return nil, errors.New("tls: bad length CECPQ2b reply")
+	}
+
+	var x25519Shared, x25519PeerKey [32]byte
+	copy(x25519PeerKey[:], peerKey)
+	curve25519.ScalarMult(&x25519Shared, &e.x25519PrivateKey, &x25519PeerKey)
+
+	// Per RFC 7748, reject the all-zero value in constant time.
+	var zeros [32]byte
+	if subtle.ConstantTimeCompare(zeros[:], x25519Shared[:]) == 1 {
+		return nil, errors.New("tls: X25519 value with wrong order")
+	}
+
+	var sikePubKey = e.sikePrivateKey.GeneratePublicKey()
+	sikeShared, err := sike.Decapsulate(e.sikePrivateKey, sikePubKey, peerKey[32:])
+	if err != nil {
+		return nil, errors.New("tls: invalid SIKE ciphertext")
+	}
+
+	preMasterSecret = append(preMasterSecret, x25519Shared[:]...)
+	preMasterSecret = append(preMasterSecret, sikeShared...)
+
+	return preMasterSecret, nil
+}
+
 func curveForCurveID(id CurveID, config *Config) (ecdhCurve, bool) {
 	switch id {
 	case CurveP224:
@@ -447,6 +540,8 @@
 		return &x25519ECDHCurve{setHighBit: config.Bugs.SetX25519HighBit}, true
 	case CurveCECPQ2:
 		return &cecpq2Curve{}, true
+	case CurveCECPQ2b:
+		return &cecpq2BCurve{}, true
 	default:
 		return nil, false
 	}
@@ -594,8 +689,8 @@
 
 NextCandidate:
 	for _, candidate := range preferredCurves {
-		if candidate == CurveCECPQ2 && version < VersionTLS13 {
-			// CECPQ2 is TLS 1.3-only.
+		if isPqGroup(candidate) && version < VersionTLS13 {
+			// CECPQ2 and CECPQ2b is TLS 1.3-only.
 			continue
 		}
 
diff --git a/src/ssl/test/runner/runner.go b/src/ssl/test/runner/runner.go
index 8461bd8..877a239 100644
--- a/src/ssl/test/runner/runner.go
+++ b/src/ssl/test/runner/runner.go
@@ -589,9 +589,6 @@
 	exportLabel          string
 	exportContext        string
 	useExportContext     bool
-	// exportEarlyKeyingMaterial, if non-zero, behaves like
-	// exportKeyingMaterial, but for the early exporter.
-	exportEarlyKeyingMaterial int
 	// flags, if not empty, contains a list of command-line flags that will
 	// be passed to the shim program.
 	flags []string
@@ -881,20 +878,6 @@
 		}
 	}
 
-	if isResume && test.exportEarlyKeyingMaterial > 0 {
-		actual := make([]byte, test.exportEarlyKeyingMaterial)
-		if _, err := io.ReadFull(tlsConn, actual); err != nil {
-			return err
-		}
-		expected, err := tlsConn.ExportEarlyKeyingMaterial(test.exportEarlyKeyingMaterial, []byte(test.exportLabel), []byte(test.exportContext))
-		if err != nil {
-			return err
-		}
-		if !bytes.Equal(actual, expected) {
-			return fmt.Errorf("early keying material mismatch; got %x, wanted %x", actual, expected)
-		}
-	}
-
 	if test.exportKeyingMaterial > 0 {
 		actual := make([]byte, test.exportKeyingMaterial)
 		if _, err := io.ReadFull(tlsConn, actual); err != nil {
@@ -1272,10 +1255,7 @@
 			flags = append(flags, "-use-export-context")
 		}
 	}
-	if test.exportEarlyKeyingMaterial > 0 {
-		flags = append(flags, "-on-resume-export-early-keying-material", strconv.Itoa(test.exportEarlyKeyingMaterial))
-	}
-	if test.exportKeyingMaterial > 0 || test.exportEarlyKeyingMaterial > 0 {
+	if test.exportKeyingMaterial > 0 {
 		flags = append(flags, "-export-label", test.exportLabel)
 		flags = append(flags, "-export-context", test.exportContext)
 	}
@@ -1569,34 +1549,34 @@
 }
 
 var testCipherSuites = []testCipherSuite{
-	{"3DES-SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
-	{"AES128-GCM", TLS_RSA_WITH_AES_128_GCM_SHA256},
-	{"AES128-SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
-	{"AES256-GCM", TLS_RSA_WITH_AES_256_GCM_SHA384},
-	{"AES256-SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
-	{"ECDHE-ECDSA-AES128-GCM", TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
-	{"ECDHE-ECDSA-AES128-SHA", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA},
-	{"ECDHE-ECDSA-AES256-GCM", TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384},
-	{"ECDHE-ECDSA-AES256-SHA", TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA},
-	{"ECDHE-ECDSA-CHACHA20-POLY1305", TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256},
-	{"ECDHE-RSA-AES128-GCM", TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
-	{"ECDHE-RSA-AES128-SHA", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
-	{"ECDHE-RSA-AES256-GCM", TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384},
-	{"ECDHE-RSA-AES256-SHA", TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA},
-	{"ECDHE-RSA-CHACHA20-POLY1305", TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256},
-	{"PSK-AES128-CBC-SHA", TLS_PSK_WITH_AES_128_CBC_SHA},
-	{"PSK-AES256-CBC-SHA", TLS_PSK_WITH_AES_256_CBC_SHA},
-	{"ECDHE-PSK-AES128-CBC-SHA", TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA},
-	{"ECDHE-PSK-AES256-CBC-SHA", TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA},
-	{"ECDHE-PSK-CHACHA20-POLY1305", TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256},
-	{"AEAD-CHACHA20-POLY1305", TLS_CHACHA20_POLY1305_SHA256},
-	{"AEAD-AES128-GCM-SHA256", TLS_AES_128_GCM_SHA256},
-	{"AEAD-AES256-GCM-SHA384", TLS_AES_256_GCM_SHA384},
-	{"NULL-SHA", TLS_RSA_WITH_NULL_SHA},
+	{"RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+	{"RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+	{"RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+	{"RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+	{"RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+	{"ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+	{"ECDHE_ECDSA_WITH_AES_128_CBC_SHA", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA},
+	{"ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384},
+	{"ECDHE_ECDSA_WITH_AES_256_CBC_SHA", TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA},
+	{"ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256},
+	{"ECDHE_RSA_WITH_AES_128_GCM_SHA256", TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+	{"ECDHE_RSA_WITH_AES_128_CBC_SHA", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
+	{"ECDHE_RSA_WITH_AES_256_GCM_SHA384", TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384},
+	{"ECDHE_RSA_WITH_AES_256_CBC_SHA", TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA},
+	{"ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256},
+	{"PSK_WITH_AES_128_CBC_SHA", TLS_PSK_WITH_AES_128_CBC_SHA},
+	{"PSK_WITH_AES_256_CBC_SHA", TLS_PSK_WITH_AES_256_CBC_SHA},
+	{"ECDHE_PSK_WITH_AES_128_CBC_SHA", TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA},
+	{"ECDHE_PSK_WITH_AES_256_CBC_SHA", TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA},
+	{"ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256", TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256},
+	{"CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+	{"AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+	{"AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+	{"RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
 }
 
 func hasComponent(suiteName, component string) bool {
-	return strings.Contains("-"+suiteName+"-", "-"+component+"-")
+	return strings.Contains("_"+suiteName+"_", "_"+component+"_")
 }
 
 func isTLS12Only(suiteName string) bool {
@@ -1607,7 +1587,7 @@
 }
 
 func isTLS13Suite(suiteName string) bool {
-	return strings.HasPrefix(suiteName, "AEAD-")
+	return !hasComponent(suiteName, "WITH")
 }
 
 func bigFromHex(hex string) *big.Int {
@@ -4466,6 +4446,8 @@
 			},
 			resumeSession:        true,
 			resumeRenewedSession: true,
+			// 0-RTT being disabled overrides all other 0-RTT reasons.
+			flags: []string{"-expect-early-data-reason", "disabled"},
 		})
 
 		tests = append(tests, testCase{
@@ -4477,9 +4459,13 @@
 			},
 			resumeSession:        true,
 			resumeRenewedSession: true,
-			// TLS 1.3 uses tickets, so the session should not be
-			// cached statefully.
-			flags: []string{"-expect-no-session-id"},
+			flags: []string{
+				// TLS 1.3 uses tickets, so the session should not be
+				// cached statefully.
+				"-expect-no-session-id",
+				// 0-RTT being disabled overrides all other 0-RTT reasons.
+				"-expect-early-data-reason", "disabled",
+			},
 		})
 
 		tests = append(tests, testCase{
@@ -4532,7 +4518,7 @@
 			flags: []string{
 				"-enable-early-data",
 				"-expect-ticket-supports-early-data",
-				"-expect-accept-early-data",
+				"-on-resume-expect-accept-early-data",
 				"-on-resume-shim-writes-first",
 			},
 		})
@@ -4561,7 +4547,7 @@
 				flags: []string{
 					"-enable-early-data",
 					"-expect-ticket-supports-early-data",
-					"-expect-accept-early-data",
+					"-on-resume-expect-accept-early-data",
 					"-on-resume-read-with-unfinished-write",
 					"-on-resume-shim-writes-first",
 				},
@@ -4612,7 +4598,7 @@
 			resumeSession: true,
 			flags: []string{
 				"-enable-early-data",
-				"-expect-accept-early-data",
+				"-on-resume-expect-accept-early-data",
 			},
 			shouldFail:    true,
 			expectedError: ":TOO_MUCH_READ_EARLY_DATA:",
@@ -6663,7 +6649,7 @@
 			flags: []string{
 				"-token-binding-params",
 				base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
-				"-expected-token-binding-param",
+				"-expect-token-binding-param",
 				"2",
 			},
 		})
@@ -6712,7 +6698,7 @@
 			flags: []string{
 				"-token-binding-params",
 				base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
-				"-expected-token-binding-param",
+				"-expect-token-binding-param",
 				"2",
 			},
 		})
@@ -6748,7 +6734,7 @@
 			flags: []string{
 				"-token-binding-params",
 				base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
-				"-expected-token-binding-param",
+				"-expect-token-binding-param",
 				"2",
 			},
 		})
@@ -6766,7 +6752,7 @@
 			flags: []string{
 				"-token-binding-params",
 				base64.StdEncoding.EncodeToString([]byte{0, 1, 2}),
-				"-expected-token-binding-param",
+				"-expect-token-binding-param",
 				"2",
 			},
 		})
@@ -6797,7 +6783,7 @@
 			flags: []string{
 				"-token-binding-params",
 				base64.StdEncoding.EncodeToString([]byte{0, 1, 2}),
-				"-expected-token-binding-param",
+				"-expect-token-binding-param",
 				"2",
 			},
 			shouldFail:    true,
@@ -6817,7 +6803,7 @@
 			flags: []string{
 				"-token-binding-params",
 				base64.StdEncoding.EncodeToString([]byte{0, 1, 2}),
-				"-expected-token-binding-param",
+				"-expect-token-binding-param",
 				"2",
 			},
 			shouldFail:    true,
@@ -6837,7 +6823,7 @@
 			flags: []string{
 				"-token-binding-params",
 				base64.StdEncoding.EncodeToString([]byte{0, 1, 2}),
-				"-expected-token-binding-param",
+				"-expect-token-binding-param",
 				"2",
 			},
 			shouldFail:    true,
@@ -6873,7 +6859,7 @@
 			flags: []string{
 				"-token-binding-params",
 				base64.StdEncoding.EncodeToString([]byte{0, 1, 2}),
-				"-expected-token-binding-param",
+				"-expect-token-binding-param",
 				"2",
 			},
 		})
@@ -7018,6 +7004,8 @@
 					"-expect-ticket-supports-early-data",
 					"-token-binding-params",
 					base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
+					"-expect-reject-early-data",
+					"-on-retry-expect-early-data-reason", "token_binding",
 				},
 			})
 		}
@@ -7036,7 +7024,7 @@
 				flags: []string{
 					"-quic-transport-params",
 					base64.StdEncoding.EncodeToString([]byte{3, 4}),
-					"-expected-quic-transport-params",
+					"-expect-quic-transport-params",
 					base64.StdEncoding.EncodeToString([]byte{1, 2}),
 				},
 				expectedQUICTransportParams: []byte{3, 4},
@@ -7053,7 +7041,7 @@
 				flags: []string{
 					"-quic-transport-params",
 					base64.StdEncoding.EncodeToString([]byte{3, 4}),
-					"-expected-quic-transport-params",
+					"-expect-quic-transport-params",
 					base64.StdEncoding.EncodeToString([]byte{1, 2}),
 				},
 				expectedQUICTransportParams: []byte{3, 4},
@@ -7097,7 +7085,7 @@
 					QUICTransportParams: []byte{1, 2},
 				},
 				flags: []string{
-					"-expected-quic-transport-params",
+					"-expect-quic-transport-params",
 					base64.StdEncoding.EncodeToString([]byte{1, 2}),
 				},
 				shouldFail:    true,
@@ -8712,21 +8700,21 @@
 	id   signatureAlgorithm
 	cert testCert
 }{
-	{"RSA-PKCS1-SHA1", signatureRSAPKCS1WithSHA1, testCertRSA},
-	{"RSA-PKCS1-SHA256", signatureRSAPKCS1WithSHA256, testCertRSA},
-	{"RSA-PKCS1-SHA384", signatureRSAPKCS1WithSHA384, testCertRSA},
-	{"RSA-PKCS1-SHA512", signatureRSAPKCS1WithSHA512, testCertRSA},
-	{"ECDSA-SHA1", signatureECDSAWithSHA1, testCertECDSAP256},
+	{"RSA_PKCS1_SHA1", signatureRSAPKCS1WithSHA1, testCertRSA},
+	{"RSA_PKCS1_SHA256", signatureRSAPKCS1WithSHA256, testCertRSA},
+	{"RSA_PKCS1_SHA384", signatureRSAPKCS1WithSHA384, testCertRSA},
+	{"RSA_PKCS1_SHA512", signatureRSAPKCS1WithSHA512, testCertRSA},
+	{"ECDSA_SHA1", signatureECDSAWithSHA1, testCertECDSAP256},
 	// The “P256” in the following line is not a mistake. In TLS 1.2 the
 	// hash function doesn't have to match the curve and so the same
 	// signature algorithm works with P-224.
-	{"ECDSA-P224-SHA256", signatureECDSAWithP256AndSHA256, testCertECDSAP224},
-	{"ECDSA-P256-SHA256", signatureECDSAWithP256AndSHA256, testCertECDSAP256},
-	{"ECDSA-P384-SHA384", signatureECDSAWithP384AndSHA384, testCertECDSAP384},
-	{"ECDSA-P521-SHA512", signatureECDSAWithP521AndSHA512, testCertECDSAP521},
-	{"RSA-PSS-SHA256", signatureRSAPSSWithSHA256, testCertRSA},
-	{"RSA-PSS-SHA384", signatureRSAPSSWithSHA384, testCertRSA},
-	{"RSA-PSS-SHA512", signatureRSAPSSWithSHA512, testCertRSA},
+	{"ECDSA_P224_SHA256", signatureECDSAWithP256AndSHA256, testCertECDSAP224},
+	{"ECDSA_P256_SHA256", signatureECDSAWithP256AndSHA256, testCertECDSAP256},
+	{"ECDSA_P384_SHA384", signatureECDSAWithP384AndSHA384, testCertECDSAP384},
+	{"ECDSA_P521_SHA512", signatureECDSAWithP521AndSHA512, testCertECDSAP521},
+	{"RSA_PSS_SHA256", signatureRSAPSSWithSHA256, testCertRSA},
+	{"RSA_PSS_SHA384", signatureRSAPSSWithSHA384, testCertRSA},
+	{"RSA_PSS_SHA512", signatureRSAPSSWithSHA512, testCertRSA},
 	{"Ed25519", signatureEd25519, testCertEd25519},
 	// Tests for key types prior to TLS 1.2.
 	{"RSA", 0, testCertRSA},
@@ -10129,7 +10117,7 @@
 				flags: []string{
 					"-enable-early-data",
 					"-expect-ticket-supports-early-data",
-					"-expect-accept-early-data",
+					"-on-resume-expect-accept-early-data",
 					"-on-resume-export-keying-material", "1024",
 					"-on-resume-export-label", "label",
 					"-on-resume-export-context", "context",
@@ -10138,106 +10126,6 @@
 				expectedError: ":HANDSHAKE_NOT_COMPLETE:",
 			})
 
-			// Test the early exporter works while the client is
-			// sending 0-RTT data. This data arrives during the
-			// server handshake, so we test it with ProtocolBugs.
-			testCases = append(testCases, testCase{
-				name: "ExportEarlyKeyingMaterial-Client-InEarlyData-" + vers.name,
-				config: Config{
-					MaxVersion:       vers.version,
-					MaxEarlyDataSize: 16384,
-				},
-				resumeConfig: &Config{
-					MaxVersion:       vers.version,
-					MaxEarlyDataSize: 16384,
-					Bugs: ProtocolBugs{
-						ExpectEarlyKeyingMaterial: 1024,
-						ExpectEarlyKeyingLabel:    "label",
-						ExpectEarlyKeyingContext:  "context",
-					},
-				},
-				resumeSession: true,
-				flags: []string{
-					"-enable-early-data",
-					"-expect-ticket-supports-early-data",
-					"-expect-accept-early-data",
-					"-on-resume-export-early-keying-material", "1024",
-					"-on-resume-export-label", "label",
-					"-on-resume-export-context", "context",
-				},
-			})
-
-			// Test the early exporter still works on the client
-			// after the handshake is confirmed. This arrives after
-			// the server handshake, so the normal hooks work.
-			testCases = append(testCases, testCase{
-				name: "ExportEarlyKeyingMaterial-Client-EarlyDataAccept-" + vers.name,
-				config: Config{
-					MaxVersion:       vers.version,
-					MaxEarlyDataSize: 16384,
-				},
-				resumeConfig: &Config{
-					MaxVersion:       vers.version,
-					MaxEarlyDataSize: 16384,
-				},
-				resumeSession:             true,
-				exportEarlyKeyingMaterial: 1024,
-				exportLabel:               "label",
-				exportContext:             "context",
-				flags: []string{
-					"-enable-early-data",
-					"-expect-ticket-supports-early-data",
-					"-expect-accept-early-data",
-					// Handshake twice on the client to force
-					// handshake confirmation.
-					"-handshake-twice",
-				},
-			})
-
-			// Test the early exporter does not work on the client
-			// if 0-RTT was not offered.
-			testCases = append(testCases, testCase{
-				name: "NoExportEarlyKeyingMaterial-Client-Initial-" + vers.name,
-				config: Config{
-					MaxVersion: vers.version,
-				},
-				flags:         []string{"-export-early-keying-material", "1024"},
-				shouldFail:    true,
-				expectedError: ":EARLY_DATA_NOT_IN_USE:",
-			})
-			testCases = append(testCases, testCase{
-				name: "NoExportEarlyKeyingMaterial-Client-Resume-" + vers.name,
-				config: Config{
-					MaxVersion: vers.version,
-				},
-				resumeSession: true,
-				flags:         []string{"-on-resume-export-early-keying-material", "1024"},
-				shouldFail:    true,
-				expectedError: ":EARLY_DATA_NOT_IN_USE:",
-			})
-
-			// Test the early exporter does not work on the client
-			// after a 0-RTT reject.
-			testCases = append(testCases, testCase{
-				name: "NoExportEarlyKeyingMaterial-Client-EarlyDataReject-" + vers.name,
-				config: Config{
-					MaxVersion:       vers.version,
-					MaxEarlyDataSize: 16384,
-					Bugs: ProtocolBugs{
-						AlwaysRejectEarlyData: true,
-					},
-				},
-				resumeSession: true,
-				flags: []string{
-					"-enable-early-data",
-					"-expect-ticket-supports-early-data",
-					"-expect-reject-early-data",
-					"-on-retry-export-early-keying-material", "1024",
-				},
-				shouldFail:    true,
-				expectedError: ":EARLY_DATA_NOT_IN_USE:",
-			})
-
 			// Test the normal exporter on the server in half-RTT.
 			testCases = append(testCases, testCase{
 				testType: serverTest,
@@ -10256,75 +10144,6 @@
 				useExportContext:     true,
 				flags:                []string{"-enable-early-data"},
 			})
-
-			// Test the early exporter works on the server in half-RTT.
-			testCases = append(testCases, testCase{
-				testType: serverTest,
-				name:     "ExportEarlyKeyingMaterial-Server-HalfRTT-" + vers.name,
-				config: Config{
-					MaxVersion: vers.version,
-					Bugs: ProtocolBugs{
-						SendEarlyData:           [][]byte{},
-						ExpectEarlyDataAccepted: true,
-					},
-				},
-				resumeSession:             true,
-				exportEarlyKeyingMaterial: 1024,
-				exportLabel:               "label",
-				exportContext:             "context",
-				flags:                     []string{"-enable-early-data"},
-			})
-
-			// Test the early exporter does not work on the server
-			// if 0-RTT was not offered.
-			testCases = append(testCases, testCase{
-				testType: serverTest,
-				name:     "NoExportEarlyKeyingMaterial-Server-Initial-" + vers.name,
-				config: Config{
-					MaxVersion: vers.version,
-				},
-				flags:         []string{"-export-early-keying-material", "1024"},
-				shouldFail:    true,
-				expectedError: ":EARLY_DATA_NOT_IN_USE:",
-			})
-			testCases = append(testCases, testCase{
-				testType: serverTest,
-				name:     "NoExportEarlyKeyingMaterial-Server-Resume-" + vers.name,
-				config: Config{
-					MaxVersion: vers.version,
-				},
-				resumeSession: true,
-				flags:         []string{"-on-resume-export-early-keying-material", "1024"},
-				shouldFail:    true,
-				expectedError: ":EARLY_DATA_NOT_IN_USE:",
-			})
-		} else {
-			// Test the early exporter fails before TLS 1.3.
-			testCases = append(testCases, testCase{
-				name: "NoExportEarlyKeyingMaterial-Client-" + vers.name,
-				config: Config{
-					MaxVersion: vers.version,
-				},
-				resumeSession:             true,
-				exportEarlyKeyingMaterial: 1024,
-				exportLabel:               "label",
-				exportContext:             "context",
-				shouldFail:                true,
-				expectedError:             ":WRONG_SSL_VERSION:",
-			})
-			testCases = append(testCases, testCase{
-				testType: serverTest,
-				name:     "NoExportEarlyKeyingMaterial-Server-" + vers.name,
-				config: Config{
-					MaxVersion: vers.version,
-				},
-				resumeSession:             true,
-				exportEarlyKeyingMaterial: 1024,
-				exportLabel:               "label",
-				exportContext:             "context",
-				shouldFail:                true,
-				expectedError:             ":WRONG_SSL_VERSION:",
-			})
 		}
 	}
 
@@ -10580,14 +10399,19 @@
 	{"P-521", CurveP521},
 	{"X25519", CurveX25519},
 	{"CECPQ2", CurveCECPQ2},
+	{"CECPQ2b", CurveCECPQ2b},
 }
 
 const bogusCurve = 0x1234
 
+func isPqGroup(r CurveID) bool {
+	return r == CurveCECPQ2 || r == CurveCECPQ2b
+}
+
 func addCurveTests() {
 	for _, curve := range testCurves {
 		for _, ver := range tlsVersions {
-			if curve.id == CurveCECPQ2 && ver.version < VersionTLS13 {
+			if isPqGroup(curve.id) && ver.version < VersionTLS13 {
 				continue
 			}
 
@@ -10629,7 +10453,7 @@
 				expectedCurveID: curve.id,
 			})
 
-			if curve.id != CurveX25519 && curve.id != CurveCECPQ2 {
+			if curve.id != CurveX25519 && !isPqGroup(curve.id) {
 				testCases = append(testCases, testCase{
 					name: "CurveTest-Client-Compressed-" + suffix,
 					config: Config{
@@ -11054,6 +10878,21 @@
 		},
 	})
 
+	// CECPQ2b should not be offered by a TLS < 1.3 client.
+	testCases = append(testCases, testCase{
+		name: "CECPQ2bNotInTLS12",
+		config: Config{
+			Bugs: ProtocolBugs{
+				FailIfCECPQ2Offered: true,
+			},
+		},
+		flags: []string{
+			"-max-version", strconv.Itoa(VersionTLS12),
+			"-curves", strconv.Itoa(int(CurveCECPQ2b)),
+			"-curves", strconv.Itoa(int(CurveX25519)),
+		},
+	})
+
 	// CECPQ2 should not crash a TLS < 1.3 client if the server mistakenly
 	// selects it.
 	testCases = append(testCases, testCase{
@@ -11072,6 +10911,24 @@
 		expectedError: ":WRONG_CURVE:",
 	})
 
+	// CECPQ2b should not crash a TLS < 1.3 client if the server mistakenly
+	// selects it.
+	testCases = append(testCases, testCase{
+		name: "CECPQ2bNotAcceptedByTLS12Client",
+		config: Config{
+			Bugs: ProtocolBugs{
+				SendCurve: CurveCECPQ2b,
+			},
+		},
+		flags: []string{
+			"-max-version", strconv.Itoa(VersionTLS12),
+			"-curves", strconv.Itoa(int(CurveCECPQ2b)),
+			"-curves", strconv.Itoa(int(CurveX25519)),
+		},
+		shouldFail:    true,
+		expectedError: ":WRONG_CURVE:",
+	})
+
 	// CECPQ2 should not be offered by default as a client.
 	testCases = append(testCases, testCase{
 		name: "CECPQ2NotEnabledByDefaultInClients",
@@ -11083,6 +10940,17 @@
 		},
 	})
 
+	// CECPQ2b should not be offered by default as a client.
+	testCases = append(testCases, testCase{
+		name: "CECPQ2bNotEnabledByDefaultInClients",
+		config: Config{
+			MinVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				FailIfCECPQ2Offered: true,
+			},
+		},
+	})
+
 	// If CECPQ2 is offered, both X25519 and CECPQ2 should have a key-share.
 	testCases = append(testCases, testCase{
 		name: "NotJustCECPQ2KeyShare",
@@ -11115,6 +10983,38 @@
 		},
 	})
 
+	// If CECPQ2b is offered, both X25519 and CECPQ2b should have a key-share.
+	testCases = append(testCases, testCase{
+		name: "NotJustCECPQ2bKeyShare",
+		config: Config{
+			MinVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				ExpectedKeyShares: []CurveID{CurveCECPQ2b, CurveX25519},
+			},
+		},
+		flags: []string{
+			"-curves", strconv.Itoa(int(CurveCECPQ2b)),
+			"-curves", strconv.Itoa(int(CurveX25519)),
+			"-expect-curve-id", strconv.Itoa(int(CurveCECPQ2b)),
+		},
+	})
+
+	// ... but only if CECPQ2b is listed first.
+	testCases = append(testCases, testCase{
+		name: "CECPQ2bKeyShareNotIncludedSecond",
+		config: Config{
+			MinVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				ExpectedKeyShares: []CurveID{CurveX25519},
+			},
+		},
+		flags: []string{
+			"-curves", strconv.Itoa(int(CurveX25519)),
+			"-curves", strconv.Itoa(int(CurveCECPQ2b)),
+			"-expect-curve-id", strconv.Itoa(int(CurveX25519)),
+		},
+	})
+
 	// If CECPQ2 is the only configured curve, the key share is sent.
 	testCases = append(testCases, testCase{
 		name: "JustConfiguringCECPQ2Works",
@@ -11130,6 +11030,21 @@
 		},
 	})
 
+	// If CECPQ2b is the only configured curve, the key share is sent.
+	testCases = append(testCases, testCase{
+		name: "JustConfiguringCECPQ2bWorks",
+		config: Config{
+			MinVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				ExpectedKeyShares: []CurveID{CurveCECPQ2b},
+			},
+		},
+		flags: []string{
+			"-curves", strconv.Itoa(int(CurveCECPQ2b)),
+			"-expect-curve-id", strconv.Itoa(int(CurveCECPQ2b)),
+		},
+	})
+
 	// As a server, CECPQ2 is not yet supported by default.
 	testCases = append(testCases, testCase{
 		testType: serverTest,
@@ -11144,6 +11059,21 @@
 			"-expect-curve-id", strconv.Itoa(int(CurveX25519)),
 		},
 	})
+
+	// As a server, CECPQ2b is not yet supported by default.
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "CECPQ2bNotEnabledByDefaultForAServer",
+		config: Config{
+			MinVersion:       VersionTLS13,
+			CurvePreferences: []CurveID{CurveCECPQ2b, CurveX25519},
+			DefaultCurves:    []CurveID{CurveCECPQ2b},
+		},
+		flags: []string{
+			"-server-preference",
+			"-expect-curve-id", strconv.Itoa(int(CurveX25519)),
+		},
+	})
 }
 
 func addTLS13RecordTests() {
@@ -11322,9 +11252,99 @@
 		},
 	})
 
+	// Test that ticket age skew up to 60 seconds in either direction is accepted.
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "TLS13-TicketAgeSkew-Forward-60-Accept",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				SendTicketAge:           70 * time.Second,
+				SendEarlyData:           [][]byte{{1, 2, 3, 4}},
+				ExpectEarlyDataAccepted: true,
+				ExpectHalfRTTData:       [][]byte{{254, 253, 252, 251}},
+			},
+		},
+		resumeSession: true,
+		flags: []string{
+			"-resumption-delay", "10",
+			"-expect-ticket-age-skew", "60",
+			// 0-RTT is accepted.
+			"-enable-early-data",
+			"-on-resume-expect-accept-early-data",
+			"-on-resume-expect-early-data-reason", "accept",
+		},
+	})
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "TLS13-TicketAgeSkew-Backward-60-Accept",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				SendTicketAge:           10 * time.Second,
+				SendEarlyData:           [][]byte{{1, 2, 3, 4}},
+				ExpectEarlyDataAccepted: true,
+				ExpectHalfRTTData:       [][]byte{{254, 253, 252, 251}},
+			},
+		},
+		resumeSession: true,
+		flags: []string{
+			"-resumption-delay", "70",
+			"-expect-ticket-age-skew", "-60",
+			// 0-RTT is accepted.
+			"-enable-early-data",
+			"-on-resume-expect-accept-early-data",
+			"-on-resume-expect-early-data-reason", "accept",
+		},
+	})
+
+	// Test that ticket age skew beyond 60 seconds in either direction is rejected.
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "TLS13-TicketAgeSkew-Forward-61-Reject",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				SendTicketAge:           71 * time.Second,
+				SendEarlyData:           [][]byte{{1, 2, 3, 4}},
+				ExpectEarlyDataAccepted: false,
+			},
+		},
+		resumeSession: true,
+		flags: []string{
+			"-resumption-delay", "10",
+			"-expect-ticket-age-skew", "61",
+			// 0-RTT is rejected.
+			"-enable-early-data",
+			"-expect-reject-early-data",
+			"-on-resume-expect-early-data-reason", "ticket_age_skew",
+		},
+	})
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "TLS13-TicketAgeSkew-Backward-61-Reject",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				SendTicketAge:           10 * time.Second,
+				SendEarlyData:           [][]byte{{1, 2, 3, 4}},
+				ExpectEarlyDataAccepted: false,
+			},
+		},
+		resumeSession: true,
+		flags: []string{
+			"-resumption-delay", "71",
+			"-expect-ticket-age-skew", "-61",
+			// 0-RTT is rejected.
+			"-enable-early-data",
+			"-expect-reject-early-data",
+			"-on-resume-expect-early-data-reason", "ticket_age_skew",
+		},
+	})
+
 	testCases = append(testCases, testCase{
 		testType: clientTest,
-		name:     "TLS13-SendTicketEarlyDataInfo",
+		name:     "TLS13-SendTicketEarlyDataSupport",
 		config: Config{
 			MaxVersion:       VersionTLS13,
 			MaxEarlyDataSize: 16384,
@@ -11335,19 +11355,22 @@
 		},
 	})
 
-	// Test that 0-RTT tickets are ignored in clients unless opted in.
+	// Test that 0-RTT tickets are still recorded as such when early data is disabled overall.
 	testCases = append(testCases, testCase{
 		testType: clientTest,
-		name:     "TLS13-SendTicketEarlyDataInfo-Disabled",
+		name:     "TLS13-SendTicketEarlyDataSupport-Disabled",
 		config: Config{
 			MaxVersion:       VersionTLS13,
 			MaxEarlyDataSize: 16384,
 		},
+		flags: []string{
+			"-expect-ticket-supports-early-data",
+		},
 	})
 
 	testCases = append(testCases, testCase{
 		testType: clientTest,
-		name:     "TLS13-DuplicateTicketEarlyDataInfo",
+		name:     "TLS13-DuplicateTicketEarlyDataSupport",
 		config: Config{
 			MaxVersion:       VersionTLS13,
 			MaxEarlyDataSize: 16384,
@@ -11362,7 +11385,7 @@
 
 	testCases = append(testCases, testCase{
 		testType: serverTest,
-		name:     "TLS13-ExpectTicketEarlyDataInfo",
+		name:     "TLS13-ExpectTicketEarlyDataSupport",
 		config: Config{
 			MaxVersion: VersionTLS13,
 			Bugs: ProtocolBugs{
@@ -12329,7 +12352,9 @@
 		flags: []string{
 			"-enable-early-data",
 			"-expect-ticket-supports-early-data",
-			"-expect-accept-early-data",
+			"-on-initial-expect-early-data-reason", "no_session_offered",
+			"-on-resume-expect-accept-early-data",
+			"-on-resume-expect-early-data-reason", "accept",
 			"-on-resume-shim-writes-first",
 		},
 	})
@@ -12354,6 +12379,7 @@
 			"-expect-ticket-supports-early-data",
 			"-expect-reject-early-data",
 			"-on-resume-shim-writes-first",
+			"-on-retry-expect-early-data-reason", "peer_declined",
 		},
 	})
 
@@ -12373,10 +12399,14 @@
 		resumeSession: true,
 		flags: []string{
 			"-enable-early-data",
-			"-expect-accept-early-data",
+			"-on-initial-expect-early-data-reason", "no_session_offered",
+			"-on-resume-expect-accept-early-data",
+			"-on-resume-expect-early-data-reason", "accept",
 		},
 	})
 
+	// The above tests the most recent ticket. Additionally test that 0-RTT
+	// works on the first ticket issued by the server.
 	testCases = append(testCases, testCase{
 		testType: serverTest,
 		name:     "EarlyData-FirstTicket-Server-TLS13",
@@ -12394,7 +12424,8 @@
 		resumeSession: true,
 		flags: []string{
 			"-enable-early-data",
-			"-expect-accept-early-data",
+			"-on-resume-expect-accept-early-data",
+			"-on-resume-expect-early-data-reason", "accept",
 		},
 	})
 
@@ -12463,6 +12494,9 @@
 			},
 			DefaultCurves: []CurveID{},
 		},
+		// Though the session is not resumed and we send HelloRetryRequest,
+		// early data being disabled takes priority as the reject reason.
+		flags: []string{"-expect-early-data-reason", "disabled"},
 	})
 
 	testCases = append(testCases, testCase{
@@ -12979,6 +13013,8 @@
 			},
 		},
 	})
+
+	// Test the client handles 0-RTT being rejected by a full handshake.
 	testCases = append(testCases, testCase{
 		testType: clientTest,
 		name:     "EarlyData-RejectTicket-Client-TLS13",
@@ -13000,6 +13036,9 @@
 			"-expect-ticket-supports-early-data",
 			"-expect-reject-early-data",
 			"-on-resume-shim-writes-first",
+			"-on-retry-expect-early-data-reason", "session_not_resumed",
+			// Test the peer certificate is reported correctly in each of the
+			// three logical connections.
 			"-on-initial-expect-peer-cert-file", path.Join(*resourceDir, rsaCertificateFile),
 			"-on-resume-expect-peer-cert-file", path.Join(*resourceDir, rsaCertificateFile),
 			"-on-retry-expect-peer-cert-file", path.Join(*resourceDir, ecdsaP256CertificateFile),
@@ -13008,6 +13047,34 @@
 		},
 	})
 
+	// Test the server rejects 0-RTT if it does not recognize the ticket.
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "EarlyData-RejectTicket-Server-TLS13",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			MinVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				SendEarlyData:           [][]byte{{1, 2, 3, 4}},
+				ExpectEarlyDataAccepted: false,
+				// Corrupt the ticket.
+				FilterTicket: func(in []byte) ([]byte, error) {
+					in[len(in)-1] ^= 1
+					return in, nil
+				},
+			},
+		},
+		messageCount:         2,
+		resumeSession:        true,
+		expectResumeRejected: true,
+		flags: []string{
+			"-enable-early-data",
+			"-on-resume-expect-reject-early-data",
+			"-on-resume-expect-early-data-reason", "session_not_resumed",
+		},
+	})
+
+	// Test the client handles 0-RTT being rejected via a HelloRetryRequest.
 	testCases = append(testCases, testCase{
 		testType: clientTest,
 		name:     "EarlyData-HRR-Client-TLS13",
@@ -13027,6 +13094,99 @@
 			"-enable-early-data",
 			"-expect-ticket-supports-early-data",
 			"-expect-reject-early-data",
+			"-on-retry-expect-early-data-reason", "hello_retry_request",
+		},
+	})
+
+	// Test the server rejects 0-RTT if it needs to send a HelloRetryRequest.
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "EarlyData-HRR-Server-TLS13",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			MinVersion: VersionTLS13,
+			// Require a HelloRetryRequest for every curve.
+			DefaultCurves: []CurveID{},
+			Bugs: ProtocolBugs{
+				SendEarlyData:           [][]byte{{1, 2, 3, 4}},
+				ExpectEarlyDataAccepted: false,
+			},
+		},
+		messageCount:  2,
+		resumeSession: true,
+		flags: []string{
+			"-enable-early-data",
+			"-on-resume-expect-reject-early-data",
+			"-on-resume-expect-early-data-reason", "hello_retry_request",
+		},
+	})
+
+	// Test the client handles a 0-RTT reject from both ticket rejection and
+	// HelloRetryRequest.
+	testCases = append(testCases, testCase{
+		testType: clientTest,
+		name:     "EarlyData-HRR-RejectTicket-Client-TLS13",
+		config: Config{
+			MaxVersion:       VersionTLS13,
+			MaxEarlyDataSize: 16384,
+			Certificates:     []Certificate{rsaCertificate},
+		},
+		resumeConfig: &Config{
+			MaxVersion:             VersionTLS13,
+			MaxEarlyDataSize:       16384,
+			Certificates:           []Certificate{ecdsaP256Certificate},
+			SessionTicketsDisabled: true,
+			Bugs: ProtocolBugs{
+				SendHelloRetryRequestCookie: []byte{1, 2, 3, 4},
+			},
+		},
+		resumeSession:        true,
+		expectResumeRejected: true,
+		flags: []string{
+			"-enable-early-data",
+			"-expect-ticket-supports-early-data",
+			"-expect-reject-early-data",
+			// The client sees HelloRetryRequest before the resumption result,
+			// though neither value is inherently preferable.
+			"-on-retry-expect-early-data-reason", "hello_retry_request",
+			// Test the peer certificate is reported correctly in each of the
+			// three logical connections.
+			"-on-initial-expect-peer-cert-file", path.Join(*resourceDir, rsaCertificateFile),
+			"-on-resume-expect-peer-cert-file", path.Join(*resourceDir, rsaCertificateFile),
+			"-on-retry-expect-peer-cert-file", path.Join(*resourceDir, ecdsaP256CertificateFile),
+			// Session tickets are disabled, so the runner will not send a ticket.
+			"-on-retry-expect-no-session",
+		},
+	})
+
+	// Test the server rejects 0-RTT if it needs to send a HelloRetryRequest.
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "EarlyData-HRR-RejectTicket-Server-TLS13",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			MinVersion: VersionTLS13,
+			// Require a HelloRetryRequest for every curve.
+			DefaultCurves: []CurveID{},
+			Bugs: ProtocolBugs{
+				SendEarlyData:           [][]byte{{1, 2, 3, 4}},
+				ExpectEarlyDataAccepted: false,
+				// Corrupt the ticket.
+				FilterTicket: func(in []byte) ([]byte, error) {
+					in[len(in)-1] ^= 1
+					return in, nil
+				},
+			},
+		},
+		messageCount:         2,
+		resumeSession:        true,
+		expectResumeRejected: true,
+		flags: []string{
+			"-enable-early-data",
+			"-on-resume-expect-reject-early-data",
+			// The server sees the missed resumption before HelloRetryRequest,
+			// though neither value is inherently preferable.
+			"-on-resume-expect-early-data-reason", "session_not_resumed",
 		},
 	})
 
@@ -13192,6 +13352,10 @@
 			"-enable-early-data",
 			"-expect-ticket-supports-early-data",
 			"-expect-reject-early-data",
+			// The client does not learn ALPN was the cause.
+			"-on-retry-expect-early-data-reason", "peer_declined",
+			// In the 0-RTT state, we surface the predicted ALPN. After
+			// processing the reject, we surface the real one.
 			"-on-initial-expect-alpn", "foo",
 			"-on-resume-expect-alpn", "foo",
 			"-on-retry-expect-alpn", "bar",
@@ -13218,6 +13382,10 @@
 			"-enable-early-data",
 			"-expect-ticket-supports-early-data",
 			"-expect-reject-early-data",
+			// The client does not learn ALPN was the cause.
+			"-on-retry-expect-early-data-reason", "peer_declined",
+			// In the 0-RTT state, we surface the predicted ALPN. After
+			// processing the reject, we surface the real one.
 			"-on-initial-expect-alpn", "",
 			"-on-resume-expect-alpn", "",
 			"-on-retry-expect-alpn", "foo",
@@ -13245,6 +13413,10 @@
 			"-enable-early-data",
 			"-expect-ticket-supports-early-data",
 			"-expect-reject-early-data",
+			// The client does not learn ALPN was the cause.
+			"-on-retry-expect-early-data-reason", "peer_declined",
+			// In the 0-RTT state, we surface the predicted ALPN. After
+			// processing the reject, we surface the real one.
 			"-on-initial-expect-alpn", "foo",
 			"-on-resume-expect-alpn", "foo",
 			"-on-retry-expect-alpn", "",
@@ -13299,10 +13471,31 @@
 			"-enable-early-data",
 			"-expect-ticket-supports-early-data",
 			"-expect-no-offer-early-data",
+			// Offer different ALPN values in the initial and resumption.
 			"-on-initial-advertise-alpn", "\x03foo",
-			"-on-resume-advertise-alpn", "\x03bar",
 			"-on-initial-expect-alpn", "foo",
+			"-on-resume-advertise-alpn", "\x03bar",
 			"-on-resume-expect-alpn", "bar",
+			// The ALPN mismatch comes from the client, so it reports it as the
+			// reason.
+			"-on-resume-expect-early-data-reason", "alpn_mismatch",
+		},
+	})
+
+	// Test that the client does not offer 0-RTT to servers which never
+	// advertise it.
+	testCases = append(testCases, testCase{
+		testType: clientTest,
+		name:     "EarlyData-NonZeroRTTSession-Client-TLS13",
+		config: Config{
+			MaxVersion: VersionTLS13,
+		},
+		resumeSession: true,
+		flags: []string{
+			"-enable-early-data",
+			"-on-resume-expect-no-offer-early-data",
+			// The client declines to offer 0-RTT because of the session.
+			"-on-resume-expect-early-data-reason", "unsupported_for_session",
 		},
 	})
 
@@ -13325,6 +13518,8 @@
 		flags: []string{
 			"-on-resume-enable-early-data",
 			"-expect-reject-early-data",
+			// The server rejects 0-RTT because of the session.
+			"-on-resume-expect-early-data-reason", "unsupported_for_session",
 		},
 	})
 
@@ -13350,6 +13545,7 @@
 			"-enable-early-data",
 			"-on-initial-select-alpn", "",
 			"-on-resume-select-alpn", "foo",
+			"-on-resume-expect-early-data-reason", "alpn_mismatch",
 		},
 	})
 
@@ -13375,6 +13571,7 @@
 			"-enable-early-data",
 			"-on-initial-select-alpn", "foo",
 			"-on-resume-select-alpn", "",
+			"-on-resume-expect-early-data-reason", "alpn_mismatch",
 		},
 	})
 
@@ -13399,6 +13596,7 @@
 			"-enable-early-data",
 			"-on-initial-select-alpn", "foo",
 			"-on-resume-select-alpn", "bar",
+			"-on-resume-expect-early-data-reason", "alpn_mismatch",
 		},
 	})
 
@@ -13443,6 +13641,8 @@
 			"-expect-ticket-supports-early-data",
 			"-send-channel-id", path.Join(*resourceDir, channelIDKeyFile),
 			"-expect-reject-early-data",
+			// The client never learns the reason was Channel ID.
+			"-on-retry-expect-early-data-reason", "peer_declined",
 		},
 	})
 
@@ -13460,7 +13660,8 @@
 			"-enable-early-data",
 			"-expect-ticket-supports-early-data",
 			"-send-channel-id", path.Join(*resourceDir, channelIDKeyFile),
-			"-expect-accept-early-data",
+			"-on-resume-expect-accept-early-data",
+			"-on-resume-expect-early-data-reason", "accept",
 		},
 	})
 
@@ -13484,6 +13685,7 @@
 			"-expect-reject-early-data",
 			"-expect-channel-id",
 			base64.StdEncoding.EncodeToString(channelIDBytes),
+			"-on-resume-expect-early-data-reason", "channel_id",
 		},
 	})
 
@@ -13504,8 +13706,9 @@
 		expectChannelID: false,
 		flags: []string{
 			"-enable-early-data",
-			"-expect-accept-early-data",
+			"-on-resume-expect-accept-early-data",
 			"-enable-channel-id",
+			"-on-resume-expect-early-data-reason", "accept",
 		},
 	})
 
@@ -13565,7 +13768,7 @@
 		flags: []string{
 			"-enable-early-data",
 			"-expect-ticket-supports-early-data",
-			"-expect-accept-early-data",
+			"-on-resume-expect-accept-early-data",
 			"-expect-version", strconv.Itoa(VersionTLS13),
 		},
 	})
@@ -13590,7 +13793,7 @@
 		flags: []string{
 			"-enable-early-data",
 			"-expect-ticket-supports-early-data",
-			"-expect-accept-early-data",
+			"-on-resume-expect-accept-early-data",
 		},
 		shouldFail:         true,
 		expectedError:      ":DIGEST_CHECK_FAILED:",
@@ -13614,7 +13817,7 @@
 		resumeSession: true,
 		flags: []string{
 			"-enable-early-data",
-			"-expect-accept-early-data",
+			"-on-resume-expect-accept-early-data",
 		},
 		shouldFail:         true,
 		expectedError:      ":DIGEST_CHECK_FAILED:",
@@ -13639,7 +13842,7 @@
 		flags: []string{
 			"-enable-early-data",
 			"-expect-ticket-supports-early-data",
-			"-expect-accept-early-data",
+			"-on-resume-expect-accept-early-data",
 		},
 		shouldFail:    true,
 		expectedError: ":DECODE_ERROR:",
@@ -13687,6 +13890,45 @@
 		expectedLocalError: "remote error: unexpected message",
 	})
 
+	// If the client or server has 0-RTT enabled but disabled TLS 1.3, it should
+	// report a reason of protocol_version.
+	testCases = append(testCases, testCase{
+		testType:        clientTest,
+		name:            "EarlyDataEnabled-Client-MaxTLS12",
+		expectedVersion: VersionTLS12,
+		flags: []string{
+			"-enable-early-data",
+			"-max-version", strconv.Itoa(VersionTLS12),
+			"-expect-early-data-reason", "protocol_version",
+		},
+	})
+	testCases = append(testCases, testCase{
+		testType:        serverTest,
+		name:            "EarlyDataEnabled-Server-MaxTLS12",
+		expectedVersion: VersionTLS12,
+		flags: []string{
+			"-enable-early-data",
+			"-max-version", strconv.Itoa(VersionTLS12),
+			"-expect-early-data-reason", "protocol_version",
+		},
+	})
+
+	// The server additionally reports protocol_version if it enabled TLS 1.3,
+	// but the peer negotiated TLS 1.2. (The corresponding situation does not
+	// exist on the client because negotiating TLS 1.2 with a 0-RTT ClientHello
+	// is a fatal error.)
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "EarlyDataEnabled-Server-NegotiateTLS12",
+		config: Config{
+			MaxVersion: VersionTLS12,
+		},
+		expectedVersion: VersionTLS12,
+		flags: []string{
+			"-enable-early-data",
+			"-expect-early-data-reason", "protocol_version",
+		},
+	})
 }
 
 func addTLS13CipherPreferenceTests() {
@@ -13756,6 +13998,22 @@
 			"-curves", strconv.Itoa(int(CurveCECPQ2)),
 		},
 	})
+
+	// CECPQ2b prefers 256-bit ciphers but will use AES-128 if there's nothing else.
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "TLS13-CipherPreference-CECPQ2b-AES128Only",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			CipherSuites: []uint16{
+				TLS_AES_128_GCM_SHA256,
+			},
+		},
+		flags: []string{
+			"-curves", strconv.Itoa(int(CurveCECPQ2b)),
+		},
+	})
+
 	// When a 256-bit cipher is offered, even if not in first place, it should be
 	// picked.
 	testCases = append(testCases, testCase{
@@ -13790,6 +14048,40 @@
 		expectedCipher: TLS_AES_128_GCM_SHA256,
 	})
 
+	// When a 256-bit cipher is offered, even if not in first place, it should be
+	// picked.
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "TLS13-CipherPreference-CECPQ2b-AES256Preferred",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			CipherSuites: []uint16{
+				TLS_AES_128_GCM_SHA256,
+				TLS_AES_256_GCM_SHA384,
+			},
+		},
+		flags: []string{
+			"-curves", strconv.Itoa(int(CurveCECPQ2b)),
+		},
+		expectedCipher: TLS_AES_256_GCM_SHA384,
+	})
+	// ... but when CECPQ2b isn't being used, the client's preference controls.
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "TLS13-CipherPreference-CECPQ2b-AES128PreferredOtherwise",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			CipherSuites: []uint16{
+				TLS_AES_128_GCM_SHA256,
+				TLS_AES_256_GCM_SHA384,
+			},
+		},
+		flags: []string{
+			"-curves", strconv.Itoa(int(CurveX25519)),
+		},
+		expectedCipher: TLS_AES_128_GCM_SHA256,
+	})
+
 	// Test that CECPQ2 continues to honor AES vs ChaCha20 logic.
 	testCases = append(testCases, testCase{
 		testType: serverTest,
@@ -13825,6 +14117,42 @@
 			"-expect-cipher-no-aes", strconv.Itoa(int(TLS_CHACHA20_POLY1305_SHA256)),
 		},
 	})
+
+	// Test that CECPQ2b continues to honor AES vs ChaCha20 logic.
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "TLS13-CipherPreference-CECPQ2b-AES128-ChaCha20-AES256",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			CipherSuites: []uint16{
+				TLS_AES_128_GCM_SHA256,
+				TLS_CHACHA20_POLY1305_SHA256,
+				TLS_AES_256_GCM_SHA384,
+			},
+		},
+		flags: []string{
+			"-curves", strconv.Itoa(int(CurveCECPQ2b)),
+			"-expect-cipher-aes", strconv.Itoa(int(TLS_CHACHA20_POLY1305_SHA256)),
+			"-expect-cipher-no-aes", strconv.Itoa(int(TLS_CHACHA20_POLY1305_SHA256)),
+		},
+	})
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "TLS13-CipherPreference-CECPQ2b-AES128-AES256-ChaCha20",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			CipherSuites: []uint16{
+				TLS_AES_128_GCM_SHA256,
+				TLS_AES_256_GCM_SHA384,
+				TLS_CHACHA20_POLY1305_SHA256,
+			},
+		},
+		flags: []string{
+			"-curves", strconv.Itoa(int(CurveCECPQ2b)),
+			"-expect-cipher-aes", strconv.Itoa(int(TLS_AES_256_GCM_SHA384)),
+			"-expect-cipher-no-aes", strconv.Itoa(int(TLS_CHACHA20_POLY1305_SHA256)),
+		},
+	})
 }
 
 func addPeekTests() {
@@ -14390,7 +14718,7 @@
 		flags: []string{
 			"-async",
 			"-enable-early-data",
-			"-expect-accept-early-data",
+			"-on-resume-expect-accept-early-data",
 			"-no-op-extra-handshake",
 		},
 	})
@@ -14963,6 +15291,7 @@
 		},
 		flags: []string{
 			"-delegated-credential", ecdsaFlagValue,
+			"-expect-delegated-credential-used",
 		},
 	})
 
@@ -15013,6 +15342,67 @@
 	})
 }
 
+func addPQExperimentSignalTests() {
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "PQExperimentSignal-Server-NoEchoIfNotConfigured",
+		config: Config{
+			MinVersion: VersionTLS13,
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				ExpectPQExperimentSignal: false,
+			},
+			PQExperimentSignal: true,
+		},
+	})
+
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "PQExperimentSignal-Server-Echo",
+		config: Config{
+			MinVersion: VersionTLS13,
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				ExpectPQExperimentSignal: true,
+			},
+			PQExperimentSignal: true,
+		},
+		flags: []string{
+			"-enable-pq-experiment-signal",
+			"-expect-pq-experiment-signal",
+		},
+	})
+
+	testCases = append(testCases, testCase{
+		testType: clientTest,
+		name:     "PQExperimentSignal-Client-NotDefault",
+		config: Config{
+			MinVersion: VersionTLS13,
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				ExpectPQExperimentSignal: false,
+			},
+			PQExperimentSignal: true,
+		},
+	})
+
+	testCases = append(testCases, testCase{
+		testType: clientTest,
+		name:     "PQExperimentSignal-Client",
+		config: Config{
+			MinVersion: VersionTLS13,
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				ExpectPQExperimentSignal: true,
+			},
+		},
+		flags: []string{
+			"-enable-pq-experiment-signal",
+			"-expect-pq-experiment-signal",
+		},
+	})
+}
+
 func worker(statusChan chan statusMsg, c chan *testCase, shimPath string, wg *sync.WaitGroup) {
 	defer wg.Done()
 
@@ -15150,6 +15540,7 @@
 	addCertCompressionTests()
 	addJDK11WorkaroundTests()
 	addDelegatedCredentialTests()
+	addPQExperimentSignalTests()
 
 	testCases = append(testCases, convertToSplitHandshakeTests(testCases)...)
 
diff --git a/src/ssl/test/runner/sike/arith.go b/src/ssl/test/runner/sike/arith.go
new file mode 100644
index 0000000..10a2ca6
--- /dev/null
+++ b/src/ssl/test/runner/sike/arith.go
@@ -0,0 +1,374 @@
+// Copyright (c) 2019, Cloudflare 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.
+
+package sike
+
+import (
+	"math/bits"
+)
+
+// Compute z = x + y (mod 2*p).
+func fpAddRdc(z, x, y *Fp) {
+	var carry uint64
+
+	// z=x+y % p
+	for i := 0; i < FP_WORDS; i++ {
+		z[i], carry = bits.Add64(x[i], y[i], carry)
+	}
+
+	// z = z - pX2
+	carry = 0
+	for i := 0; i < FP_WORDS; i++ {
+		z[i], carry = bits.Sub64(z[i], pX2[i], carry)
+	}
+
+	// if z<0 add pX2 back
+	mask := uint64(0 - carry)
+	carry = 0
+	for i := 0; i < FP_WORDS; i++ {
+		z[i], carry = bits.Add64(z[i], pX2[i]&mask, carry)
+	}
+}
+
+// Compute z = x - y (mod 2*p).
+func fpSubRdc(z, x, y *Fp) {
+	var borrow uint64
+
+	// z = z - pX2
+	for i := 0; i < FP_WORDS; i++ {
+		z[i], borrow = bits.Sub64(x[i], y[i], borrow)
+	}
+
+	// if z<0 add pX2 back
+	mask := uint64(0 - borrow)
+	borrow = 0
+	for i := 0; i < FP_WORDS; i++ {
+		z[i], borrow = bits.Add64(z[i], pX2[i]&mask, borrow)
+	}
+}
+
+// Reduce a field element in [0, 2*p) to one in [0,p).
+func fpRdcP(x *Fp) {
+	var borrow, mask uint64
+	for i := 0; i < FP_WORDS; i++ {
+		x[i], borrow = bits.Sub64(x[i], p[i], borrow)
+	}
+
+	// Sets all bits if borrow = 1
+	mask = 0 - borrow
+	borrow = 0
+	for i := 0; i < FP_WORDS; i++ {
+		x[i], borrow = bits.Add64(x[i], p[i]&mask, borrow)
+	}
+}
+
+// Implementation doesn't actually depend on a prime field.
+func fpSwapCond(x, y *Fp, mask uint8) {
+	if mask != 0 {
+		var tmp Fp
+		copy(tmp[:], y[:])
+		copy(y[:], x[:])
+		copy(x[:], tmp[:])
+	}
+}
+
+// Compute z = x * y.
+func fpMul(z *FpX2, x, y *Fp) {
+	var carry, t, u, v uint64
+	var hi, lo uint64
+
+	for i := uint64(0); i < FP_WORDS; i++ {
+		for j := uint64(0); j <= i; j++ {
+			hi, lo = bits.Mul64(x[j], y[i-j])
+			v, carry = bits.Add64(lo, v, 0)
+			u, carry = bits.Add64(hi, u, carry)
+			t += carry
+		}
+		z[i] = v
+		v = u
+		u = t
+		t = 0
+	}
+
+	for i := FP_WORDS; i < (2*FP_WORDS)-1; i++ {
+		for j := i - FP_WORDS + 1; j < FP_WORDS; j++ {
+			hi, lo = bits.Mul64(x[j], y[i-j])
+			v, carry = bits.Add64(lo, v, 0)
+			u, carry = bits.Add64(hi, u, carry)
+			t += carry
+		}
+		z[i] = v
+		v = u
+		u = t
+		t = 0
+	}
+	z[2*FP_WORDS-1] = v
+}
+
+// Perform Montgomery reduction: set z = x R^{-1} (mod 2*p)
+// with R=2^512. Destroys the input value.
+func fpMontRdc(z *Fp, x *FpX2) {
+	var carry, t, u, v uint64
+	var hi, lo uint64
+	var count int
+
+	count = 3 // number of 0 digits in the least significat part of p + 1
+
+	for i := 0; i < FP_WORDS; i++ {
+		for j := 0; j < i; j++ {
+			if j < (i - count + 1) {
+				hi, lo = bits.Mul64(z[j], p1[i-j])
+				v, carry = bits.Add64(lo, v, 0)
+				u, carry = bits.Add64(hi, u, carry)
+				t += carry
+			}
+		}
+		v, carry = bits.Add64(v, x[i], 0)
+		u, carry = bits.Add64(u, 0, carry)
+		t += carry
+
+		z[i] = v
+		v = u
+		u = t
+		t = 0
+	}
+
+	for i := FP_WORDS; i < 2*FP_WORDS-1; i++ {
+		if count > 0 {
+			count--
+		}
+		for j := i - FP_WORDS + 1; j < FP_WORDS; j++ {
+			if j < (FP_WORDS - count) {
+				hi, lo = bits.Mul64(z[j], p1[i-j])
+				v, carry = bits.Add64(lo, v, 0)
+				u, carry = bits.Add64(hi, u, carry)
+				t += carry
+			}
+		}
+		v, carry = bits.Add64(v, x[i], 0)
+		u, carry = bits.Add64(u, 0, carry)
+
+		t += carry
+		z[i-FP_WORDS] = v
+		v = u
+		u = t
+		t = 0
+	}
+	v, carry = bits.Add64(v, x[2*FP_WORDS-1], 0)
+	z[FP_WORDS-1] = v
+}
+
+// Compute z = x + y, without reducing mod p.
+func fp2Add(z, x, y *FpX2) {
+	var carry uint64
+	for i := 0; i < 2*FP_WORDS; i++ {
+		z[i], carry = bits.Add64(x[i], y[i], carry)
+	}
+}
+
+// Compute z = x - y, without reducing mod p.
+func fp2Sub(z, x, y *FpX2) {
+	var borrow, mask uint64
+	for i := 0; i < 2*FP_WORDS; i++ {
+		z[i], borrow = bits.Sub64(x[i], y[i], borrow)
+	}
+
+	// Sets all bits if borrow = 1
+	mask = 0 - borrow
+	borrow = 0
+	for i := FP_WORDS; i < 2*FP_WORDS; i++ {
+		z[i], borrow = bits.Add64(z[i], p[i-FP_WORDS]&mask, borrow)
+	}
+}
+
+// Montgomery multiplication. Input values must be already
+// in Montgomery domain.
+func fpMulRdc(dest, lhs, rhs *Fp) {
+	a := lhs // = a*R
+	b := rhs // = b*R
+
+	var ab FpX2
+	fpMul(&ab, a, b)     // = a*b*R*R
+	fpMontRdc(dest, &ab) // = a*b*R mod p
+}
+
+// Set dest = x^((p-3)/4).  If x is square, this is 1/sqrt(x).
+// Uses variation of sliding-window algorithm from with window size
+// of 5 and least to most significant bit sliding (left-to-right)
+// See HAC 14.85 for general description.
+//
+// Allowed to overlap x with dest.
+// All values in Montgomery domains
+// Set dest = x^(2^k), for k >= 1, by repeated squarings.
+func p34(dest, x *Fp) {
+	var lookup [16]Fp
+
+	// This performs sum(powStrategy) + 1 squarings and len(lookup) + len(mulStrategy)
+	// multiplications.
+	powStrategy := []uint8{
+		0x03, 0x0A, 0x07, 0x05, 0x06, 0x05, 0x03, 0x08, 0x04, 0x07,
+		0x05, 0x06, 0x04, 0x05, 0x09, 0x06, 0x03, 0x0B, 0x05, 0x05,
+		0x02, 0x08, 0x04, 0x07, 0x07, 0x08, 0x05, 0x06, 0x04, 0x08,
+		0x05, 0x02, 0x0A, 0x06, 0x05, 0x04, 0x08, 0x05, 0x05, 0x05,
+		0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
+		0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
+		0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
+		0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x01}
+	mulStrategy := []uint8{
+		0x02, 0x0F, 0x09, 0x08, 0x0E, 0x0C, 0x02, 0x08, 0x05, 0x0F,
+		0x08, 0x0F, 0x06, 0x06, 0x03, 0x02, 0x00, 0x0A, 0x09, 0x0D,
+		0x01, 0x0C, 0x03, 0x07, 0x01, 0x0A, 0x08, 0x0B, 0x02, 0x0F,
+		0x0E, 0x01, 0x0B, 0x0C, 0x0E, 0x03, 0x0B, 0x0F, 0x0F, 0x0F,
+		0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
+		0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
+		0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
+		0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x00}
+	initialMul := uint8(8)
+
+	// Precompute lookup table of odd multiples of x for window
+	// size k=5.
+	var xx Fp
+	fpMulRdc(&xx, x, x)
+	lookup[0] = *x
+	for i := 1; i < 16; i++ {
+		fpMulRdc(&lookup[i], &lookup[i-1], &xx)
+	}
+
+	// Now lookup = {x, x^3, x^5, ... }
+	// so that lookup[i] = x^{2*i + 1}
+	// so that lookup[k/2] = x^k, for odd k
+	*dest = lookup[initialMul]
+	for i := uint8(0); i < uint8(len(powStrategy)); i++ {
+		fpMulRdc(dest, dest, dest)
+		for j := uint8(1); j < powStrategy[i]; j++ {
+			fpMulRdc(dest, dest, dest)
+		}
+		fpMulRdc(dest, dest, &lookup[mulStrategy[i]])
+	}
+}
+
+func add(dest, lhs, rhs *Fp2) {
+	fpAddRdc(&dest.A, &lhs.A, &rhs.A)
+	fpAddRdc(&dest.B, &lhs.B, &rhs.B)
+}
+
+func sub(dest, lhs, rhs *Fp2) {
+	fpSubRdc(&dest.A, &lhs.A, &rhs.A)
+	fpSubRdc(&dest.B, &lhs.B, &rhs.B)
+}
+
+func mul(dest, lhs, rhs *Fp2) {
+	// Let (a,b,c,d) = (lhs.a,lhs.b,rhs.a,rhs.b).
+	a := &lhs.A
+	b := &lhs.B
+	c := &rhs.A
+	d := &rhs.B
+
+	// We want to compute
+	//
+	// (a + bi)*(c + di) = (a*c - b*d) + (a*d + b*c)i
+	//
+	// Use Karatsuba's trick: note that
+	//
+	// (b - a)*(c - d) = (b*c + a*d) - a*c - b*d
+	//
+	// so (a*d + b*c) = (b-a)*(c-d) + a*c + b*d.
+
+	var ac, bd FpX2
+	fpMul(&ac, a, c) // = a*c*R*R
+	fpMul(&bd, b, d) // = b*d*R*R
+
+	var b_minus_a, c_minus_d Fp
+	fpSubRdc(&b_minus_a, b, a) // = (b-a)*R
+	fpSubRdc(&c_minus_d, c, d) // = (c-d)*R
+
+	var ad_plus_bc FpX2
+	fpMul(&ad_plus_bc, &b_minus_a, &c_minus_d) // = (b-a)*(c-d)*R*R
+	fp2Add(&ad_plus_bc, &ad_plus_bc, &ac)      // = ((b-a)*(c-d) + a*c)*R*R
+	fp2Add(&ad_plus_bc, &ad_plus_bc, &bd)      // = ((b-a)*(c-d) + a*c + b*d)*R*R
+
+	fpMontRdc(&dest.B, &ad_plus_bc) // = (a*d + b*c)*R mod p
+
+	var ac_minus_bd FpX2
+	fp2Sub(&ac_minus_bd, &ac, &bd)   // = (a*c - b*d)*R*R
+	fpMontRdc(&dest.A, &ac_minus_bd) // = (a*c - b*d)*R mod p
+}
+
+func inv(dest, x *Fp2) {
+	var a2PlusB2 Fp
+	var asq, bsq FpX2
+	var ac FpX2
+	var minusB Fp
+	var minusBC FpX2
+
+	a := &x.A
+	b := &x.B
+
+	// We want to compute
+	//
+	//    1          1     (a - bi)	    (a - bi)
+	// -------- = -------- -------- = -----------
+	// (a + bi)   (a + bi) (a - bi)   (a^2 + b^2)
+	//
+	// Letting c = 1/(a^2 + b^2), this is
+	//
+	// 1/(a+bi) = a*c - b*ci.
+
+	fpMul(&asq, a, a)          // = a*a*R*R
+	fpMul(&bsq, b, b)          // = b*b*R*R
+	fp2Add(&asq, &asq, &bsq)   // = (a^2 + b^2)*R*R
+	fpMontRdc(&a2PlusB2, &asq) // = (a^2 + b^2)*R mod p
+	// Now a2PlusB2 = a^2 + b^2
+
+	inv := a2PlusB2
+	fpMulRdc(&inv, &a2PlusB2, &a2PlusB2)
+	p34(&inv, &inv)
+	fpMulRdc(&inv, &inv, &inv)
+	fpMulRdc(&inv, &inv, &a2PlusB2)
+
+	fpMul(&ac, a, &inv)
+	fpMontRdc(&dest.A, &ac)
+
+	fpSubRdc(&minusB, &minusB, b)
+	fpMul(&minusBC, &minusB, &inv)
+	fpMontRdc(&dest.B, &minusBC)
+}
+
+func sqr(dest, x *Fp2) {
+	var a2, aPlusB, aMinusB Fp
+	var a2MinB2, ab2 FpX2
+
+	a := &x.A
+	b := &x.B
+
+	// (a + bi)*(a + bi) = (a^2 - b^2) + 2abi.
+	fpAddRdc(&a2, a, a)                // = a*R + a*R = 2*a*R
+	fpAddRdc(&aPlusB, a, b)            // = a*R + b*R = (a+b)*R
+	fpSubRdc(&aMinusB, a, b)           // = a*R - b*R = (a-b)*R
+	fpMul(&a2MinB2, &aPlusB, &aMinusB) // = (a+b)*(a-b)*R*R = (a^2 - b^2)*R*R
+	fpMul(&ab2, &a2, b)                // = 2*a*b*R*R
+	fpMontRdc(&dest.A, &a2MinB2)       // = (a^2 - b^2)*R mod p
+	fpMontRdc(&dest.B, &ab2)           // = 2*a*b*R mod p
+}
+
+// In case choice == 1, performs following swap in constant time:
+// 	xPx <-> xQx
+//	xPz <-> xQz
+// Otherwise returns xPx, xPz, xQx, xQz unchanged
+func condSwap(xPx, xPz, xQx, xQz *Fp2, choice uint8) {
+	fpSwapCond(&xPx.A, &xQx.A, choice)
+	fpSwapCond(&xPx.B, &xQx.B, choice)
+	fpSwapCond(&xPz.A, &xQz.A, choice)
+	fpSwapCond(&xPz.B, &xQz.B, choice)
+}
diff --git a/src/ssl/test/runner/sike/consts.go b/src/ssl/test/runner/sike/consts.go
new file mode 100644
index 0000000..9d68a4f
--- /dev/null
+++ b/src/ssl/test/runner/sike/consts.go
@@ -0,0 +1,317 @@
+// Copyright (c) 2019, Cloudflare 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.
+
+package sike
+
+// I keep it bool in order to be able to apply logical NOT
+type KeyVariant uint
+
+// Representation of an element of the base field F_p.
+//
+// No particular meaning is assigned to the representation -- it could represent
+// an element in Montgomery form, or not.  Tracking the meaning of the field
+// element is left to higher types.
+type Fp [FP_WORDS]uint64
+
+// Represents an intermediate product of two elements of the base field F_p.
+type FpX2 [2 * FP_WORDS]uint64
+
+// Represents an element of the extended field Fp^2 = Fp(x+i)
+type Fp2 struct {
+	A Fp
+	B Fp
+}
+
+type DomainParams struct {
+	// P, Q and R=P-Q base points
+	Affine_P, Affine_Q, Affine_R Fp2
+	// Size of a compuatation strategy for x-torsion group
+	IsogenyStrategy []uint32
+	// Max size of secret key for x-torsion group
+	SecretBitLen uint
+	// Max size of secret key for x-torsion group
+	SecretByteLen uint
+}
+
+type SidhParams struct {
+	Id uint8
+	// Bytelen of P
+	Bytelen int
+	// The public key size, in bytes.
+	PublicKeySize int
+	// The shared secret size, in bytes.
+	SharedSecretSize int
+	// Defines A,C constant for starting curve Cy^2 = x^3 + Ax^2 + x
+	InitCurve ProjectiveCurveParameters
+	// 2- and 3-torsion group parameter definitions
+	A, B DomainParams
+	// Precomputed 1/2 in the Fp2 in Montgomery domain
+	HalfFp2 Fp2
+	// Precomputed identity element in the Fp2 in Montgomery domain
+	OneFp2 Fp2
+	// Length of SIKE secret message. Must be one of {24,32,40},
+	// depending on size of prime field used (see [SIKE], 1.4 and 5.1)
+	MsgLen int
+	// Length of SIKE ephemeral KEM key (see [SIKE], 1.4 and 5.1)
+	KemSize int
+	// Size of a ciphertext returned by encapsulation in bytes
+	CiphertextSize int
+}
+
+// Stores curve projective parameters equivalent to A/C. Meaning of the
+// values depends on the context. When working with isogenies over
+// subgroup that are powers of:
+// * three then  (A:C) ~ (A+2C:A-2C)
+// * four then   (A:C) ~ (A+2C:  4C)
+// See Appendix A of SIKE for more details
+type CurveCoefficientsEquiv struct {
+	A Fp2
+	C Fp2
+}
+
+// A point on the projective line P^1(F_{p^2}).
+//
+// This represents a point on the Kummer line of a Montgomery curve.  The
+// curve is specified by a ProjectiveCurveParameters struct.
+type ProjectivePoint struct {
+	X Fp2
+	Z Fp2
+}
+
+// Base type for public and private key. Used mainly to carry domain
+// parameters.
+type key struct {
+	// Domain parameters of the algorithm to be used with a key
+	params *SidhParams
+	// Flag indicates whether corresponds to 2-, 3-torsion group or SIKE
+	keyVariant KeyVariant
+}
+
+// Defines operations on private key
+type PrivateKey struct {
+	key
+	// Secret key
+	Scalar []byte
+	// Used only by KEM
+	S []byte
+}
+
+// Defines operations on public key
+type PublicKey struct {
+	key
+	affine_xP   Fp2
+	affine_xQ   Fp2
+	affine_xQmP Fp2
+}
+
+// A point on the projective line P^1(F_{p^2}).
+//
+// This is used to work projectively with the curve coefficients.
+type ProjectiveCurveParameters struct {
+	A Fp2
+	C Fp2
+}
+
+const (
+	// First 2 bits identify SIDH variant third bit indicates
+	// whether key is a SIKE variant (set) or SIDH (not set)
+
+	// 001 - SIDH: corresponds to 2-torsion group
+	KeyVariant_SIDH_A KeyVariant = 1 << 0
+	// 010 - SIDH: corresponds to 3-torsion group
+	KeyVariant_SIDH_B = 1 << 1
+	// 110 - SIKE
+	KeyVariant_SIKE = 1<<2 | KeyVariant_SIDH_B
+	// Number of uint64 limbs used to store field element
+	FP_WORDS = 7
+)
+
+// Used internally by this package
+// -------------------------------
+
+var (
+	p = Fp{
+		0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFDC1767AE2FFFFFF,
+		0x7BC65C783158AEA3, 0x6CFC5FD681C52056, 0x2341F27177344,
+	}
+
+	// 2*p434
+	pX2 = Fp{
+		0xFFFFFFFFFFFFFFFE, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFB82ECF5C5FFFFFF,
+		0xF78CB8F062B15D47, 0xD9F8BFAD038A40AC, 0x4683E4E2EE688,
+	}
+
+	// p434 + 1
+	p1 = Fp{
+		0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0xFDC1767AE3000000,
+		0x7BC65C783158AEA3, 0x6CFC5FD681C52056, 0x0002341F27177344,
+	}
+
+	// R^2=(2^448)^2 mod p
+	R2 = Fp{
+		0x28E55B65DCD69B30, 0xACEC7367768798C2, 0xAB27973F8311688D, 0x175CC6AF8D6C7C0B,
+		0xABCD92BF2DDE347E, 0x69E16A61C7686D9A, 0x000025A89BCDD12A,
+	}
+
+	// 1/2 * R mod p
+	half = Fp2{
+		A: Fp{
+			0x0000000000003A16, 0x0000000000000000, 0x0000000000000000, 0x5C87FA027E000000,
+			0x6C00D27DAACFD66A, 0x74992A2A2FBBA086, 0x0000767753DE976D},
+	}
+
+	// 1*R mod p
+	one = Fp2{
+		A: Fp{
+			0x000000000000742C, 0x0000000000000000, 0x0000000000000000, 0xB90FF404FC000000,
+			0xD801A4FB559FACD4, 0xE93254545F77410C, 0x0000ECEEA7BD2EDA},
+	}
+
+	// 6*R mod p
+	six = Fp2{
+		A: Fp{
+			0x000000000002B90A, 0x0000000000000000, 0x0000000000000000, 0x5ADCCB2822000000,
+			0x187D24F39F0CAFB4, 0x9D353A4D394145A0, 0x00012559A0403298},
+	}
+
+	Params SidhParams
+)
+
+func init() {
+	Params = SidhParams{
+		// SIDH public key byte size.
+		PublicKeySize: 330,
+		// SIDH shared secret byte size.
+		SharedSecretSize: 110,
+		InitCurve: ProjectiveCurveParameters{
+			A: six,
+			C: one,
+		},
+		A: DomainParams{
+			// The x-coordinate of PA
+			Affine_P: Fp2{
+				A: Fp{
+					0x05ADF455C5C345BF, 0x91935C5CC767AC2B, 0xAFE4E879951F0257, 0x70E792DC89FA27B1,
+					0xF797F526BB48C8CD, 0x2181DB6131AF621F, 0x00000A1C08B1ECC4,
+				},
+				B: Fp{
+					0x74840EB87CDA7788, 0x2971AA0ECF9F9D0B, 0xCB5732BDF41715D5, 0x8CD8E51F7AACFFAA,
+					0xA7F424730D7E419F, 0xD671EB919A179E8C, 0x0000FFA26C5A924A,
+				},
+			},
+			// The x-coordinate of QA
+			Affine_Q: Fp2{
+				A: Fp{
+					0xFEC6E64588B7273B, 0xD2A626D74CBBF1C6, 0xF8F58F07A78098C7, 0xE23941F470841B03,
+					0x1B63EDA2045538DD, 0x735CFEB0FFD49215, 0x0001C4CB77542876,
+				},
+				B: Fp{
+					0xADB0F733C17FFDD6, 0x6AFFBD037DA0A050, 0x680EC43DB144E02F, 0x1E2E5D5FF524E374,
+					0xE2DDA115260E2995, 0xA6E4B552E2EDE508, 0x00018ECCDDF4B53E,
+				},
+			},
+			// The x-coordinate of RA = PA-QA
+			Affine_R: Fp2{
+				A: Fp{
+					0x01BA4DB518CD6C7D, 0x2CB0251FE3CC0611, 0x259B0C6949A9121B, 0x60E17AC16D2F82AD,
+					0x3AA41F1CE175D92D, 0x413FBE6A9B9BC4F3, 0x00022A81D8D55643,
+				},
+				B: Fp{
+					0xB8ADBC70FC82E54A, 0xEF9CDDB0D5FADDED, 0x5820C734C80096A0, 0x7799994BAA96E0E4,
+					0x044961599E379AF8, 0xDB2B94FBF09F27E2, 0x0000B87FC716C0C6,
+				},
+			},
+			// Max size of secret key for 2-torsion group, corresponds to 2^e2 - 1
+			SecretBitLen: 216,
+			// SecretBitLen in bytes.
+			SecretByteLen: 27,
+			// 2-torsion group computation strategy
+			IsogenyStrategy: []uint32{
+				0x30, 0x1C, 0x10, 0x08, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01,
+				0x01, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x08, 0x04,
+				0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x04, 0x02, 0x01, 0x01,
+				0x02, 0x01, 0x01, 0x0D, 0x07, 0x04, 0x02, 0x01, 0x01, 0x02,
+				0x01, 0x01, 0x03, 0x02, 0x01, 0x01, 0x01, 0x01, 0x05, 0x04,
+				0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01,
+				0x15, 0x0C, 0x07, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01,
+				0x03, 0x02, 0x01, 0x01, 0x01, 0x01, 0x05, 0x03, 0x02, 0x01,
+				0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x09, 0x05, 0x03,
+				0x02, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x04,
+				0x02, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01},
+		},
+		B: DomainParams{
+			// The x-coordinate of PB
+			Affine_P: Fp2{
+				A: Fp{
+					0x6E5497556EDD48A3, 0x2A61B501546F1C05, 0xEB919446D049887D, 0x5864A4A69D450C4F,
+					0xB883F276A6490D2B, 0x22CC287022D5F5B9, 0x0001BED4772E551F,
+				},
+				B: Fp{
+					0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000,
+					0x0000000000000000, 0x0000000000000000, 0x0000000000000000,
+				},
+			},
+			// The x-coordinate of QB
+			Affine_Q: Fp2{
+				A: Fp{
+					0xFAE2A3F93D8B6B8E, 0x494871F51700FE1C, 0xEF1A94228413C27C, 0x498FF4A4AF60BD62,
+					0xB00AD2A708267E8A, 0xF4328294E017837F, 0x000034080181D8AE,
+				},
+				B: Fp{
+					0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000,
+					0x0000000000000000, 0x0000000000000000, 0x0000000000000000,
+				},
+			},
+			// The x-coordinate of RB = PB - QB
+			Affine_R: Fp2{
+				A: Fp{
+					0x283B34FAFEFDC8E4, 0x9208F44977C3E647, 0x7DEAE962816F4E9A, 0x68A2BA8AA262EC9D,
+					0x8176F112EA43F45B, 0x02106D022634F504, 0x00007E8A50F02E37,
+				},
+				B: Fp{
+					0xB378B7C1DA22CCB1, 0x6D089C99AD1D9230, 0xEBE15711813E2369, 0x2B35A68239D48A53,
+					0x445F6FD138407C93, 0xBEF93B29A3F6B54B, 0x000173FA910377D3,
+				},
+			},
+			// Size of secret key for 3-torsion group, corresponds to log_2(3^e3) - 1.
+			SecretBitLen: 217,
+			// SecretBitLen in bytes.
+			SecretByteLen: 28,
+			// 3-torsion group computation strategy
+			IsogenyStrategy: []uint32{
+				0x42, 0x21, 0x11, 0x09, 0x05, 0x03, 0x02, 0x01, 0x01, 0x01,
+				0x01, 0x02, 0x01, 0x01, 0x01, 0x04, 0x02, 0x01, 0x01, 0x01,
+				0x02, 0x01, 0x01, 0x08, 0x04, 0x02, 0x01, 0x01, 0x01, 0x02,
+				0x01, 0x01, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x10,
+				0x08, 0x04, 0x02, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x04,
+				0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x08, 0x04, 0x02, 0x01,
+				0x01, 0x02, 0x01, 0x01, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01,
+				0x01, 0x20, 0x10, 0x08, 0x04, 0x03, 0x01, 0x01, 0x01, 0x01,
+				0x02, 0x01, 0x01, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01,
+				0x08, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x04, 0x02,
+				0x01, 0x01, 0x02, 0x01, 0x01, 0x10, 0x08, 0x04, 0x02, 0x01,
+				0x01, 0x02, 0x01, 0x01, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01,
+				0x01, 0x08, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x04,
+				0x02, 0x01, 0x01, 0x02, 0x01, 0x01},
+		},
+		OneFp2:  one,
+		HalfFp2: half,
+		MsgLen:  16,
+		// SIKEp434 provides 128 bit of classical security ([SIKE], 5.1)
+		KemSize: 16,
+		// ceil(434+7/8)
+		Bytelen:        55,
+		CiphertextSize: 16 + 330,
+	}
+}
diff --git a/src/ssl/test/runner/sike/curve.go b/src/ssl/test/runner/sike/curve.go
new file mode 100644
index 0000000..8172546
--- /dev/null
+++ b/src/ssl/test/runner/sike/curve.go
@@ -0,0 +1,422 @@
+// Copyright (c) 2019, Cloudflare 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.
+
+package sike
+
+// Interface for working with isogenies.
+type isogeny interface {
+	// Given a torsion point on a curve computes isogenous curve.
+	// Returns curve coefficients (A:C), so that E_(A/C) = E_(A/C)/<P>,
+	// where P is a provided projective point. Sets also isogeny constants
+	// that are needed for isogeny evaluation.
+	GenerateCurve(*ProjectivePoint) CurveCoefficientsEquiv
+	// Evaluates isogeny at caller provided point. Requires isogeny curve constants
+	// to be earlier computed by GenerateCurve.
+	EvaluatePoint(*ProjectivePoint) ProjectivePoint
+}
+
+// Stores isogeny 3 curve constants
+type isogeny3 struct {
+	K1 Fp2
+	K2 Fp2
+}
+
+// Stores isogeny 4 curve constants
+type isogeny4 struct {
+	isogeny3
+	K3 Fp2
+}
+
+// Constructs isogeny3 objects
+func NewIsogeny3() isogeny {
+	return &isogeny3{}
+}
+
+// Constructs isogeny4 objects
+func NewIsogeny4() isogeny {
+	return &isogeny4{}
+}
+
+// Helper function for RightToLeftLadder(). Returns A+2C / 4.
+func calcAplus2Over4(cparams *ProjectiveCurveParameters) (ret Fp2) {
+	var tmp Fp2
+
+	// 2C
+	add(&tmp, &cparams.C, &cparams.C)
+	// A+2C
+	add(&ret, &cparams.A, &tmp)
+	// 1/4C
+	add(&tmp, &tmp, &tmp)
+	inv(&tmp, &tmp)
+	// A+2C/4C
+	mul(&ret, &ret, &tmp)
+	return
+}
+
+// Converts values in x.A and x.B to Montgomery domain
+// x.A = x.A * R mod p
+// x.B = x.B * R mod p
+// Performs v = v*R^2*R^(-1) mod p, for both x.A and x.B
+func toMontDomain(x *Fp2) {
+	var aRR FpX2
+
+	// convert to montgomery domain
+	fpMul(&aRR, &x.A, &R2) // = a*R*R
+	fpMontRdc(&x.A, &aRR)  // = a*R mod p
+	fpMul(&aRR, &x.B, &R2)
+	fpMontRdc(&x.B, &aRR)
+}
+
+// Converts values in x.A and x.B from Montgomery domain
+// a = x.A mod p
+// b = x.B mod p
+//
+// After returning from the call x is not modified.
+func fromMontDomain(x *Fp2, out *Fp2) {
+	var aR FpX2
+
+	// convert from montgomery domain
+	copy(aR[:], x.A[:])
+	fpMontRdc(&out.A, &aR) // = a mod p in [0, 2p)
+	fpRdcP(&out.A)         // = a mod p in [0, p)
+	for i := range aR {
+		aR[i] = 0
+	}
+	copy(aR[:], x.B[:])
+	fpMontRdc(&out.B, &aR)
+	fpRdcP(&out.B)
+}
+
+// Computes j-invariant for a curve y2=x3+A/Cx+x with A,C in F_(p^2). Result
+// is returned in 'j'. Implementation corresponds to Algorithm 9 from SIKE.
+func Jinvariant(cparams *ProjectiveCurveParameters, j *Fp2) {
+	var t0, t1 Fp2
+
+	sqr(j, &cparams.A)   // j  = A^2
+	sqr(&t1, &cparams.C) // t1 = C^2
+	add(&t0, &t1, &t1)   // t0 = t1 + t1
+	sub(&t0, j, &t0)     // t0 = j - t0
+	sub(&t0, &t0, &t1)   // t0 = t0 - t1
+	sub(j, &t0, &t1)     // t0 = t0 - t1
+	sqr(&t1, &t1)        // t1 = t1^2
+	mul(j, j, &t1)       // j = j * t1
+	add(&t0, &t0, &t0)   // t0 = t0 + t0
+	add(&t0, &t0, &t0)   // t0 = t0 + t0
+	sqr(&t1, &t0)        // t1 = t0^2
+	mul(&t0, &t0, &t1)   // t0 = t0 * t1
+	add(&t0, &t0, &t0)   // t0 = t0 + t0
+	add(&t0, &t0, &t0)   // t0 = t0 + t0
+	inv(j, j)            // j  = 1/j
+	mul(j, &t0, j)       // j  = t0 * j
+}
+
+// Given affine points x(P), x(Q) and x(Q-P) in a extension field F_{p^2}, function
+// recorvers projective coordinate A of a curve. This is Algorithm 10 from SIKE.
+func RecoverCoordinateA(curve *ProjectiveCurveParameters, xp, xq, xr *Fp2) {
+	var t0, t1 Fp2
+
+	add(&t1, xp, xq)                        // t1 = Xp + Xq
+	mul(&t0, xp, xq)                        // t0 = Xp * Xq
+	mul(&curve.A, xr, &t1)                  // A  = X(q-p) * t1
+	add(&curve.A, &curve.A, &t0)            // A  = A + t0
+	mul(&t0, &t0, xr)                       // t0 = t0 * X(q-p)
+	sub(&curve.A, &curve.A, &Params.OneFp2) // A  = A - 1
+	add(&t0, &t0, &t0)                      // t0 = t0 + t0
+	add(&t1, &t1, xr)                       // t1 = t1 + X(q-p)
+	add(&t0, &t0, &t0)                      // t0 = t0 + t0
+	sqr(&curve.A, &curve.A)                 // A  = A^2
+	inv(&t0, &t0)                           // t0 = 1/t0
+	mul(&curve.A, &curve.A, &t0)            // A  = A * t0
+	sub(&curve.A, &curve.A, &t1)            // A  = A - t1
+}
+
+// Computes equivalence (A:C) ~ (A+2C : A-2C)
+func CalcCurveParamsEquiv3(cparams *ProjectiveCurveParameters) CurveCoefficientsEquiv {
+	var coef CurveCoefficientsEquiv
+	var c2 Fp2
+
+	add(&c2, &cparams.C, &cparams.C)
+	// A24p = A+2*C
+	add(&coef.A, &cparams.A, &c2)
+	// A24m = A-2*C
+	sub(&coef.C, &cparams.A, &c2)
+	return coef
+}
+
+// Computes equivalence (A:C) ~ (A+2C : 4C)
+func CalcCurveParamsEquiv4(cparams *ProjectiveCurveParameters) CurveCoefficientsEquiv {
+	var coefEq CurveCoefficientsEquiv
+
+	add(&coefEq.C, &cparams.C, &cparams.C)
+	// A24p = A+2C
+	add(&coefEq.A, &cparams.A, &coefEq.C)
+	// C24 = 4*C
+	add(&coefEq.C, &coefEq.C, &coefEq.C)
+	return coefEq
+}
+
+// Recovers (A:C) curve parameters from projectively equivalent (A+2C:A-2C).
+func RecoverCurveCoefficients3(cparams *ProjectiveCurveParameters, coefEq *CurveCoefficientsEquiv) {
+	add(&cparams.A, &coefEq.A, &coefEq.C)
+	// cparams.A = 2*(A+2C+A-2C) = 4A
+	add(&cparams.A, &cparams.A, &cparams.A)
+	// cparams.C = (A+2C-A+2C) = 4C
+	sub(&cparams.C, &coefEq.A, &coefEq.C)
+	return
+}
+
+// Recovers (A:C) curve parameters from projectively equivalent (A+2C:4C).
+func RecoverCurveCoefficients4(cparams *ProjectiveCurveParameters, coefEq *CurveCoefficientsEquiv) {
+	// cparams.C = (4C)*1/2=2C
+	mul(&cparams.C, &coefEq.C, &Params.HalfFp2)
+	// cparams.A = A+2C - 2C = A
+	sub(&cparams.A, &coefEq.A, &cparams.C)
+	// cparams.C = 2C * 1/2 = C
+	mul(&cparams.C, &cparams.C, &Params.HalfFp2)
+	return
+}
+
+// Combined coordinate doubling and differential addition. Takes projective points
+// P,Q,Q-P and (A+2C)/4C curve E coefficient. Returns 2*P and P+Q calculated on E.
+// Function is used only by RightToLeftLadder. Corresponds to Algorithm 5 of SIKE
+func xDbladd(P, Q, QmP *ProjectivePoint, a24 *Fp2) (dblP, PaQ ProjectivePoint) {
+	var t0, t1, t2 Fp2
+	xQmP, zQmP := &QmP.X, &QmP.Z
+	xPaQ, zPaQ := &PaQ.X, &PaQ.Z
+	x2P, z2P := &dblP.X, &dblP.Z
+	xP, zP := &P.X, &P.Z
+	xQ, zQ := &Q.X, &Q.Z
+
+	add(&t0, xP, zP)      // t0   = Xp+Zp
+	sub(&t1, xP, zP)      // t1   = Xp-Zp
+	sqr(x2P, &t0)         // 2P.X = t0^2
+	sub(&t2, xQ, zQ)      // t2   = Xq-Zq
+	add(xPaQ, xQ, zQ)     // Xp+q = Xq+Zq
+	mul(&t0, &t0, &t2)    // t0   = t0 * t2
+	mul(z2P, &t1, &t1)    // 2P.Z = t1 * t1
+	mul(&t1, &t1, xPaQ)   // t1   = t1 * Xp+q
+	sub(&t2, x2P, z2P)    // t2   = 2P.X - 2P.Z
+	mul(x2P, x2P, z2P)    // 2P.X = 2P.X * 2P.Z
+	mul(xPaQ, a24, &t2)   // Xp+q = A24 * t2
+	sub(zPaQ, &t0, &t1)   // Zp+q = t0 - t1
+	add(z2P, xPaQ, z2P)   // 2P.Z = Xp+q + 2P.Z
+	add(xPaQ, &t0, &t1)   // Xp+q = t0 + t1
+	mul(z2P, z2P, &t2)    // 2P.Z = 2P.Z * t2
+	sqr(zPaQ, zPaQ)       // Zp+q = Zp+q ^ 2
+	sqr(xPaQ, xPaQ)       // Xp+q = Xp+q ^ 2
+	mul(zPaQ, xQmP, zPaQ) // Zp+q = Xq-p * Zp+q
+	mul(xPaQ, zQmP, xPaQ) // Xp+q = Zq-p * Xp+q
+	return
+}
+
+// Given the curve parameters, xP = x(P), computes xP = x([2^k]P)
+// Safe to overlap xP, x2P.
+func Pow2k(xP *ProjectivePoint, params *CurveCoefficientsEquiv, k uint32) {
+	var t0, t1 Fp2
+
+	x, z := &xP.X, &xP.Z
+	for i := uint32(0); i < k; i++ {
+		sub(&t0, x, z)           // t0  = Xp - Zp
+		add(&t1, x, z)           // t1  = Xp + Zp
+		sqr(&t0, &t0)            // t0  = t0 ^ 2
+		sqr(&t1, &t1)            // t1  = t1 ^ 2
+		mul(z, &params.C, &t0)   // Z2p = C24 * t0
+		mul(x, z, &t1)           // X2p = Z2p * t1
+		sub(&t1, &t1, &t0)       // t1  = t1 - t0
+		mul(&t0, &params.A, &t1) // t0  = A24+ * t1
+		add(z, z, &t0)           // Z2p = Z2p + t0
+		mul(z, z, &t1)           // Zp  = Z2p * t1
+	}
+}
+
+// Given the curve parameters, xP = x(P), and k >= 0, compute xP = x([3^k]P).
+//
+// Safe to overlap xP, xR.
+func Pow3k(xP *ProjectivePoint, params *CurveCoefficientsEquiv, k uint32) {
+	var t0, t1, t2, t3, t4, t5, t6 Fp2
+
+	x, z := &xP.X, &xP.Z
+	for i := uint32(0); i < k; i++ {
+		sub(&t0, x, z)           // t0  = Xp - Zp
+		sqr(&t2, &t0)            // t2  = t0^2
+		add(&t1, x, z)           // t1  = Xp + Zp
+		sqr(&t3, &t1)            // t3  = t1^2
+		add(&t4, &t1, &t0)       // t4  = t1 + t0
+		sub(&t0, &t1, &t0)       // t0  = t1 - t0
+		sqr(&t1, &t4)            // t1  = t4^2
+		sub(&t1, &t1, &t3)       // t1  = t1 - t3
+		sub(&t1, &t1, &t2)       // t1  = t1 - t2
+		mul(&t5, &t3, &params.A) // t5  = t3 * A24+
+		mul(&t3, &t3, &t5)       // t3  = t5 * t3
+		mul(&t6, &t2, &params.C) // t6  = t2 * A24-
+		mul(&t2, &t2, &t6)       // t2  = t2 * t6
+		sub(&t3, &t2, &t3)       // t3  = t2 - t3
+		sub(&t2, &t5, &t6)       // t2  = t5 - t6
+		mul(&t1, &t2, &t1)       // t1  = t2 * t1
+		add(&t2, &t3, &t1)       // t2  = t3 + t1
+		sqr(&t2, &t2)            // t2  = t2^2
+		mul(x, &t2, &t4)         // X3p = t2 * t4
+		sub(&t1, &t3, &t1)       // t1  = t3 - t1
+		sqr(&t1, &t1)            // t1  = t1^2
+		mul(z, &t1, &t0)         // Z3p = t1 * t0
+	}
+}
+
+// Set (y1, y2, y3)  = (1/x1, 1/x2, 1/x3).
+//
+// All xi, yi must be distinct.
+func Fp2Batch3Inv(x1, x2, x3, y1, y2, y3 *Fp2) {
+	var x1x2, t Fp2
+
+	mul(&x1x2, x1, x2) // x1*x2
+	mul(&t, &x1x2, x3) // 1/(x1*x2*x3)
+	inv(&t, &t)
+	mul(y1, &t, x2) // 1/x1
+	mul(y1, y1, x3)
+	mul(y2, &t, x1) // 1/x2
+	mul(y2, y2, x3)
+	mul(y3, &t, &x1x2) // 1/x3
+}
+
+// ScalarMul3Pt is a right-to-left point multiplication that given the
+// x-coordinate of P, Q and P-Q calculates the x-coordinate of R=Q+[scalar]P.
+// nbits must be smaller or equal to len(scalar).
+func ScalarMul3Pt(cparams *ProjectiveCurveParameters, P, Q, PmQ *ProjectivePoint, nbits uint, scalar []uint8) ProjectivePoint {
+	var R0, R2, R1 ProjectivePoint
+	aPlus2Over4 := calcAplus2Over4(cparams)
+	R1 = *P
+	R2 = *PmQ
+	R0 = *Q
+
+	// Iterate over the bits of the scalar, bottom to top
+	prevBit := uint8(0)
+	for i := uint(0); i < nbits; i++ {
+		bit := (scalar[i>>3] >> (i & 7) & 1)
+		swap := prevBit ^ bit
+		prevBit = bit
+		condSwap(&R1.X, &R1.Z, &R2.X, &R2.Z, swap)
+		R0, R2 = xDbladd(&R0, &R2, &R1, &aPlus2Over4)
+	}
+	condSwap(&R1.X, &R1.Z, &R2.X, &R2.Z, prevBit)
+	return R1
+}
+
+// Given a three-torsion point p = x(PB) on the curve E_(A:C), construct the
+// three-isogeny phi : E_(A:C) -> E_(A:C)/<P_3> = E_(A':C').
+//
+// Input: (XP_3: ZP_3), where P_3 has exact order 3 on E_A/C
+// Output: * Curve coordinates (A' + 2C', A' - 2C') corresponding to E_A'/C' = A_E/C/<P3>
+//         * isogeny phi with constants in F_p^2
+func (phi *isogeny3) GenerateCurve(p *ProjectivePoint) CurveCoefficientsEquiv {
+	var t0, t1, t2, t3, t4 Fp2
+	var coefEq CurveCoefficientsEquiv
+	var K1, K2 = &phi.K1, &phi.K2
+
+	sub(K1, &p.X, &p.Z)            // K1 = XP3 - ZP3
+	sqr(&t0, K1)                   // t0 = K1^2
+	add(K2, &p.X, &p.Z)            // K2 = XP3 + ZP3
+	sqr(&t1, K2)                   // t1 = K2^2
+	add(&t2, &t0, &t1)             // t2 = t0 + t1
+	add(&t3, K1, K2)               // t3 = K1 + K2
+	sqr(&t3, &t3)                  // t3 = t3^2
+	sub(&t3, &t3, &t2)             // t3 = t3 - t2
+	add(&t2, &t1, &t3)             // t2 = t1 + t3
+	add(&t3, &t3, &t0)             // t3 = t3 + t0
+	add(&t4, &t3, &t0)             // t4 = t3 + t0
+	add(&t4, &t4, &t4)             // t4 = t4 + t4
+	add(&t4, &t1, &t4)             // t4 = t1 + t4
+	mul(&coefEq.C, &t2, &t4)       // A24m = t2 * t4
+	add(&t4, &t1, &t2)             // t4 = t1 + t2
+	add(&t4, &t4, &t4)             // t4 = t4 + t4
+	add(&t4, &t0, &t4)             // t4 = t0 + t4
+	mul(&t4, &t3, &t4)             // t4 = t3 * t4
+	sub(&t0, &t4, &coefEq.C)       // t0 = t4 - A24m
+	add(&coefEq.A, &coefEq.C, &t0) // A24p = A24m + t0
+	return coefEq
+}
+
+// Given a 3-isogeny phi and a point pB = x(PB), compute x(QB), the x-coordinate
+// of the image QB = phi(PB) of PB under phi : E_(A:C) -> E_(A':C').
+//
+// The output xQ = x(Q) is then a point on the curve E_(A':C'); the curve
+// parameters are returned by the GenerateCurve function used to construct phi.
+func (phi *isogeny3) EvaluatePoint(p *ProjectivePoint) ProjectivePoint {
+	var t0, t1, t2 Fp2
+	var q ProjectivePoint
+	var K1, K2 = &phi.K1, &phi.K2
+	var px, pz = &p.X, &p.Z
+
+	add(&t0, px, pz)   // t0 = XQ + ZQ
+	sub(&t1, px, pz)   // t1 = XQ - ZQ
+	mul(&t0, K1, &t0)  // t2 = K1 * t0
+	mul(&t1, K2, &t1)  // t1 = K2 * t1
+	add(&t2, &t0, &t1) // t2 = t0 + t1
+	sub(&t0, &t1, &t0) // t0 = t1 - t0
+	sqr(&t2, &t2)      // t2 = t2 ^ 2
+	sqr(&t0, &t0)      // t0 = t0 ^ 2
+	mul(&q.X, px, &t2) // XQ'= XQ * t2
+	mul(&q.Z, pz, &t0) // ZQ'= ZQ * t0
+	return q
+}
+
+// Given a four-torsion point p = x(PB) on the curve E_(A:C), construct the
+// four-isogeny phi : E_(A:C) -> E_(A:C)/<P_4> = E_(A':C').
+//
+// Input: (XP_4: ZP_4), where P_4 has exact order 4 on E_A/C
+// Output: * Curve coordinates (A' + 2C', 4C') corresponding to E_A'/C' = A_E/C/<P4>
+//         * isogeny phi with constants in F_p^2
+func (phi *isogeny4) GenerateCurve(p *ProjectivePoint) CurveCoefficientsEquiv {
+	var coefEq CurveCoefficientsEquiv
+	var xp4, zp4 = &p.X, &p.Z
+	var K1, K2, K3 = &phi.K1, &phi.K2, &phi.K3
+
+	sub(K2, xp4, zp4)
+	add(K3, xp4, zp4)
+	sqr(K1, zp4)
+	add(K1, K1, K1)
+	sqr(&coefEq.C, K1)
+	add(K1, K1, K1)
+	sqr(&coefEq.A, xp4)
+	add(&coefEq.A, &coefEq.A, &coefEq.A)
+	sqr(&coefEq.A, &coefEq.A)
+	return coefEq
+}
+
+// Given a 4-isogeny phi and a point xP = x(P), compute x(Q), the x-coordinate
+// of the image Q = phi(P) of P under phi : E_(A:C) -> E_(A':C').
+//
+// Input: isogeny returned by GenerateCurve and point q=(Qx,Qz) from E0_A/C
+// Output: Corresponding point q from E1_A'/C', where E1 is 4-isogenous to E0
+func (phi *isogeny4) EvaluatePoint(p *ProjectivePoint) ProjectivePoint {
+	var t0, t1 Fp2
+	var q = *p
+	var xq, zq = &q.X, &q.Z
+	var K1, K2, K3 = &phi.K1, &phi.K2, &phi.K3
+
+	add(&t0, xq, zq)
+	sub(&t1, xq, zq)
+	mul(xq, &t0, K2)
+	mul(zq, &t1, K3)
+	mul(&t0, &t0, &t1)
+	mul(&t0, &t0, K1)
+	add(&t1, xq, zq)
+	sub(zq, xq, zq)
+	sqr(&t1, &t1)
+	sqr(zq, zq)
+	add(xq, &t0, &t1)
+	sub(&t0, zq, &t0)
+	mul(xq, xq, &t1)
+	mul(zq, zq, &t0)
+	return q
+}
diff --git a/src/ssl/test/runner/sike/sike.go b/src/ssl/test/runner/sike/sike.go
new file mode 100644
index 0000000..dcd6cfc
--- /dev/null
+++ b/src/ssl/test/runner/sike/sike.go
@@ -0,0 +1,683 @@
+// Copyright (c) 2019, Cloudflare 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.
+
+package sike
+
+import (
+	"crypto/sha256"
+	"crypto/subtle"
+	"errors"
+	"io"
+)
+
+// Zeroize Fp2
+func zeroize(fp *Fp2) {
+	// Zeroizing in 2 separated loops tells compiler to
+	// use fast runtime.memclr()
+	for i := range fp.A {
+		fp.A[i] = 0
+	}
+	for i := range fp.B {
+		fp.B[i] = 0
+	}
+}
+
+// Convert the input to wire format.
+//
+// The output byte slice must be at least 2*bytelen(p) bytes long.
+func convFp2ToBytes(output []byte, fp2 *Fp2) {
+	if len(output) < 2*Params.Bytelen {
+		panic("output byte slice too short")
+	}
+	var a Fp2
+	fromMontDomain(fp2, &a)
+
+	// convert to bytes in little endian form
+	for i := 0; i < Params.Bytelen; i++ {
+		// set i = j*8 + k
+		tmp := i / 8
+		k := uint64(i % 8)
+		output[i] = byte(a.A[tmp] >> (8 * k))
+		output[i+Params.Bytelen] = byte(a.B[tmp] >> (8 * k))
+	}
+}
+
+// Read 2*bytelen(p) bytes into the given ExtensionFieldElement.
+//
+// It is an error to call this function if the input byte slice is less than 2*bytelen(p) bytes long.
+func convBytesToFp2(fp2 *Fp2, input []byte) {
+	if len(input) < 2*Params.Bytelen {
+		panic("input byte slice too short")
+	}
+
+	for i := 0; i < Params.Bytelen; i++ {
+		j := i / 8
+		k := uint64(i % 8)
+		fp2.A[j] |= uint64(input[i]) << (8 * k)
+		fp2.B[j] |= uint64(input[i+Params.Bytelen]) << (8 * k)
+	}
+	toMontDomain(fp2)
+}
+
+// -----------------------------------------------------------------------------
+// Functions for traversing isogeny trees acoording to strategy. Key type 'A' is
+//
+
+// Traverses isogeny tree in order to compute xR, xP, xQ and xQmP needed
+// for public key generation.
+func traverseTreePublicKeyA(curve *ProjectiveCurveParameters, xR, phiP, phiQ, phiR *ProjectivePoint, pub *PublicKey) {
+	var points = make([]ProjectivePoint, 0, 8)
+	var indices = make([]int, 0, 8)
+	var i, sidx int
+
+	cparam := CalcCurveParamsEquiv4(curve)
+	phi := NewIsogeny4()
+	strat := pub.params.A.IsogenyStrategy
+	stratSz := len(strat)
+
+	for j := 1; j <= stratSz; j++ {
+		for i <= stratSz-j {
+			points = append(points, *xR)
+			indices = append(indices, i)
+
+			k := strat[sidx]
+			sidx++
+			Pow2k(xR, &cparam, 2*k)
+			i += int(k)
+		}
+
+		cparam = phi.GenerateCurve(xR)
+		for k := 0; k < len(points); k++ {
+			points[k] = phi.EvaluatePoint(&points[k])
+		}
+
+		*phiP = phi.EvaluatePoint(phiP)
+		*phiQ = phi.EvaluatePoint(phiQ)
+		*phiR = phi.EvaluatePoint(phiR)
+
+		// pop xR from points
+		*xR, points = points[len(points)-1], points[:len(points)-1]
+		i, indices = int(indices[len(indices)-1]), indices[:len(indices)-1]
+	}
+}
+
+// Traverses isogeny tree in order to compute xR needed
+// for public key generation.
+func traverseTreeSharedKeyA(curve *ProjectiveCurveParameters, xR *ProjectivePoint, pub *PublicKey) {
+	var points = make([]ProjectivePoint, 0, 8)
+	var indices = make([]int, 0, 8)
+	var i, sidx int
+
+	cparam := CalcCurveParamsEquiv4(curve)
+	phi := NewIsogeny4()
+	strat := pub.params.A.IsogenyStrategy
+	stratSz := len(strat)
+
+	for j := 1; j <= stratSz; j++ {
+		for i <= stratSz-j {
+			points = append(points, *xR)
+			indices = append(indices, i)
+
+			k := strat[sidx]
+			sidx++
+			Pow2k(xR, &cparam, 2*k)
+			i += int(k)
+		}
+
+		cparam = phi.GenerateCurve(xR)
+		for k := 0; k < len(points); k++ {
+			points[k] = phi.EvaluatePoint(&points[k])
+		}
+
+		// pop xR from points
+		*xR, points = points[len(points)-1], points[:len(points)-1]
+		i, indices = int(indices[len(indices)-1]), indices[:len(indices)-1]
+	}
+}
+
+// Traverses isogeny tree in order to compute xR, xP, xQ and xQmP needed
+// for public key generation.
+func traverseTreePublicKeyB(curve *ProjectiveCurveParameters, xR, phiP, phiQ, phiR *ProjectivePoint, pub *PublicKey) {
+	var points = make([]ProjectivePoint, 0, 8)
+	var indices = make([]int, 0, 8)
+	var i, sidx int
+
+	cparam := CalcCurveParamsEquiv3(curve)
+	phi := NewIsogeny3()
+	strat := pub.params.B.IsogenyStrategy
+	stratSz := len(strat)
+
+	for j := 1; j <= stratSz; j++ {
+		for i <= stratSz-j {
+			points = append(points, *xR)
+			indices = append(indices, i)
+
+			k := strat[sidx]
+			sidx++
+			Pow3k(xR, &cparam, k)
+			i += int(k)
+		}
+
+		cparam = phi.GenerateCurve(xR)
+		for k := 0; k < len(points); k++ {
+			points[k] = phi.EvaluatePoint(&points[k])
+		}
+
+		*phiP = phi.EvaluatePoint(phiP)
+		*phiQ = phi.EvaluatePoint(phiQ)
+		*phiR = phi.EvaluatePoint(phiR)
+
+		// pop xR from points
+		*xR, points = points[len(points)-1], points[:len(points)-1]
+		i, indices = int(indices[len(indices)-1]), indices[:len(indices)-1]
+	}
+}
+
+// Traverses isogeny tree in order to compute xR, xP, xQ and xQmP needed
+// for public key generation.
+func traverseTreeSharedKeyB(curve *ProjectiveCurveParameters, xR *ProjectivePoint, pub *PublicKey) {
+	var points = make([]ProjectivePoint, 0, 8)
+	var indices = make([]int, 0, 8)
+	var i, sidx int
+
+	cparam := CalcCurveParamsEquiv3(curve)
+	phi := NewIsogeny3()
+	strat := pub.params.B.IsogenyStrategy
+	stratSz := len(strat)
+
+	for j := 1; j <= stratSz; j++ {
+		for i <= stratSz-j {
+			points = append(points, *xR)
+			indices = append(indices, i)
+
+			k := strat[sidx]
+			sidx++
+			Pow3k(xR, &cparam, k)
+			i += int(k)
+		}
+
+		cparam = phi.GenerateCurve(xR)
+		for k := 0; k < len(points); k++ {
+			points[k] = phi.EvaluatePoint(&points[k])
+		}
+
+		// pop xR from points
+		*xR, points = points[len(points)-1], points[:len(points)-1]
+		i, indices = int(indices[len(indices)-1]), indices[:len(indices)-1]
+	}
+}
+
+// Generate a public key in the 2-torsion group
+func publicKeyGenA(prv *PrivateKey) (pub *PublicKey) {
+	var xPA, xQA, xRA ProjectivePoint
+	var xPB, xQB, xRB, xK ProjectivePoint
+	var invZP, invZQ, invZR Fp2
+
+	pub = NewPublicKey(KeyVariant_SIDH_A)
+	var phi = NewIsogeny4()
+
+	// Load points for A
+	xPA = ProjectivePoint{X: prv.params.A.Affine_P, Z: prv.params.OneFp2}
+	xQA = ProjectivePoint{X: prv.params.A.Affine_Q, Z: prv.params.OneFp2}
+	xRA = ProjectivePoint{X: prv.params.A.Affine_R, Z: prv.params.OneFp2}
+
+	// Load points for B
+	xRB = ProjectivePoint{X: prv.params.B.Affine_R, Z: prv.params.OneFp2}
+	xQB = ProjectivePoint{X: prv.params.B.Affine_Q, Z: prv.params.OneFp2}
+	xPB = ProjectivePoint{X: prv.params.B.Affine_P, Z: prv.params.OneFp2}
+
+	// Find isogeny kernel
+	xK = ScalarMul3Pt(&pub.params.InitCurve, &xPA, &xQA, &xRA, prv.params.A.SecretBitLen, prv.Scalar)
+	traverseTreePublicKeyA(&pub.params.InitCurve, &xK, &xPB, &xQB, &xRB, pub)
+
+	// Secret isogeny
+	phi.GenerateCurve(&xK)
+	xPA = phi.EvaluatePoint(&xPB)
+	xQA = phi.EvaluatePoint(&xQB)
+	xRA = phi.EvaluatePoint(&xRB)
+	Fp2Batch3Inv(&xPA.Z, &xQA.Z, &xRA.Z, &invZP, &invZQ, &invZR)
+
+	mul(&pub.affine_xP, &xPA.X, &invZP)
+	mul(&pub.affine_xQ, &xQA.X, &invZQ)
+	mul(&pub.affine_xQmP, &xRA.X, &invZR)
+	return
+}
+
+// Generate a public key in the 3-torsion group
+func publicKeyGenB(prv *PrivateKey) (pub *PublicKey) {
+	var xPB, xQB, xRB, xK ProjectivePoint
+	var xPA, xQA, xRA ProjectivePoint
+	var invZP, invZQ, invZR Fp2
+
+	pub = NewPublicKey(prv.keyVariant)
+	var phi = NewIsogeny3()
+
+	// Load points for B
+	xRB = ProjectivePoint{X: prv.params.B.Affine_R, Z: prv.params.OneFp2}
+	xQB = ProjectivePoint{X: prv.params.B.Affine_Q, Z: prv.params.OneFp2}
+	xPB = ProjectivePoint{X: prv.params.B.Affine_P, Z: prv.params.OneFp2}
+
+	// Load points for A
+	xPA = ProjectivePoint{X: prv.params.A.Affine_P, Z: prv.params.OneFp2}
+	xQA = ProjectivePoint{X: prv.params.A.Affine_Q, Z: prv.params.OneFp2}
+	xRA = ProjectivePoint{X: prv.params.A.Affine_R, Z: prv.params.OneFp2}
+
+	xK = ScalarMul3Pt(&pub.params.InitCurve, &xPB, &xQB, &xRB, prv.params.B.SecretBitLen, prv.Scalar)
+	traverseTreePublicKeyB(&pub.params.InitCurve, &xK, &xPA, &xQA, &xRA, pub)
+
+	phi.GenerateCurve(&xK)
+	xPB = phi.EvaluatePoint(&xPA)
+	xQB = phi.EvaluatePoint(&xQA)
+	xRB = phi.EvaluatePoint(&xRA)
+	Fp2Batch3Inv(&xPB.Z, &xQB.Z, &xRB.Z, &invZP, &invZQ, &invZR)
+
+	mul(&pub.affine_xP, &xPB.X, &invZP)
+	mul(&pub.affine_xQ, &xQB.X, &invZQ)
+	mul(&pub.affine_xQmP, &xRB.X, &invZR)
+	return
+}
+
+// -----------------------------------------------------------------------------
+// Key agreement functions
+//
+
+// Establishing shared keys in in 2-torsion group
+func deriveSecretA(prv *PrivateKey, pub *PublicKey) []byte {
+	var sharedSecret = make([]byte, pub.params.SharedSecretSize)
+	var xP, xQ, xQmP ProjectivePoint
+	var xK ProjectivePoint
+	var cparam ProjectiveCurveParameters
+	var phi = NewIsogeny4()
+	var jInv Fp2
+
+	// Recover curve coefficients
+	RecoverCoordinateA(&cparam, &pub.affine_xP, &pub.affine_xQ, &pub.affine_xQmP)
+	// C=1
+	cparam.C = Params.OneFp2
+
+	// Find kernel of the morphism
+	xP = ProjectivePoint{X: pub.affine_xP, Z: pub.params.OneFp2}
+	xQ = ProjectivePoint{X: pub.affine_xQ, Z: pub.params.OneFp2}
+	xQmP = ProjectivePoint{X: pub.affine_xQmP, Z: pub.params.OneFp2}
+	xK = ScalarMul3Pt(&cparam, &xP, &xQ, &xQmP, pub.params.A.SecretBitLen, prv.Scalar)
+
+	// Traverse isogeny tree
+	traverseTreeSharedKeyA(&cparam, &xK, pub)
+
+	// Calculate j-invariant on isogeneus curve
+	c := phi.GenerateCurve(&xK)
+	RecoverCurveCoefficients4(&cparam, &c)
+	Jinvariant(&cparam, &jInv)
+	convFp2ToBytes(sharedSecret, &jInv)
+	return sharedSecret
+}
+
+// Establishing shared keys in in 3-torsion group
+func deriveSecretB(prv *PrivateKey, pub *PublicKey) []byte {
+	var sharedSecret = make([]byte, pub.params.SharedSecretSize)
+	var xP, xQ, xQmP ProjectivePoint
+	var xK ProjectivePoint
+	var cparam ProjectiveCurveParameters
+	var phi = NewIsogeny3()
+	var jInv Fp2
+
+	// Recover curve A coefficient
+	RecoverCoordinateA(&cparam, &pub.affine_xP, &pub.affine_xQ, &pub.affine_xQmP)
+	// C=1
+	cparam.C = Params.OneFp2
+
+	// Find kernel of the morphism
+	xP = ProjectivePoint{X: pub.affine_xP, Z: pub.params.OneFp2}
+	xQ = ProjectivePoint{X: pub.affine_xQ, Z: pub.params.OneFp2}
+	xQmP = ProjectivePoint{X: pub.affine_xQmP, Z: pub.params.OneFp2}
+	xK = ScalarMul3Pt(&cparam, &xP, &xQ, &xQmP, pub.params.B.SecretBitLen, prv.Scalar)
+
+	// Traverse isogeny tree
+	traverseTreeSharedKeyB(&cparam, &xK, pub)
+
+	// Calculate j-invariant on isogeneus curve
+	c := phi.GenerateCurve(&xK)
+	RecoverCurveCoefficients3(&cparam, &c)
+	Jinvariant(&cparam, &jInv)
+	convFp2ToBytes(sharedSecret, &jInv)
+	return sharedSecret
+}
+
+func encrypt(skA *PrivateKey, pkA, pkB *PublicKey, ptext []byte) ([]byte, error) {
+	if pkB.keyVariant != KeyVariant_SIKE {
+		return nil, errors.New("wrong key type")
+	}
+
+	j, err := DeriveSecret(skA, pkB)
+	if err != nil {
+		return nil, err
+	}
+
+	if len(ptext) != pkA.params.KemSize {
+		panic("Implementation error")
+	}
+
+	digest := sha256.Sum256(j)
+	// Uses truncated digest (first 16-bytes)
+	for i, _ := range ptext {
+		digest[i] ^= ptext[i]
+	}
+
+	ret := make([]byte, pkA.Size()+len(ptext))
+	copy(ret, pkA.Export())
+	copy(ret[pkA.Size():], digest[:pkA.params.KemSize])
+	return ret, nil
+}
+
+// NewPrivateKey initializes private key.
+// Usage of this function guarantees that the object is correctly initialized.
+func NewPrivateKey(v KeyVariant) *PrivateKey {
+	prv := &PrivateKey{key: key{params: &Params, keyVariant: v}}
+	if (v & KeyVariant_SIDH_A) == KeyVariant_SIDH_A {
+		prv.Scalar = make([]byte, prv.params.A.SecretByteLen)
+	} else {
+		prv.Scalar = make([]byte, prv.params.B.SecretByteLen)
+	}
+	if v == KeyVariant_SIKE {
+		prv.S = make([]byte, prv.params.MsgLen)
+	}
+	return prv
+}
+
+// NewPublicKey initializes public key.
+// Usage of this function guarantees that the object is correctly initialized.
+func NewPublicKey(v KeyVariant) *PublicKey {
+	return &PublicKey{key: key{params: &Params, keyVariant: v}}
+}
+
+// Import clears content of the public key currently stored in the structure
+// and imports key stored in the byte string. Returns error in case byte string
+// size is wrong. Doesn't perform any validation.
+func (pub *PublicKey) Import(input []byte) error {
+	if len(input) != pub.Size() {
+		return errors.New("sidh: input to short")
+	}
+	ssSz := pub.params.SharedSecretSize
+	convBytesToFp2(&pub.affine_xP, input[0:ssSz])
+	convBytesToFp2(&pub.affine_xQ, input[ssSz:2*ssSz])
+	convBytesToFp2(&pub.affine_xQmP, input[2*ssSz:3*ssSz])
+	return nil
+}
+
+// Exports currently stored key. In case structure hasn't been filled with key data
+// returned byte string is filled with zeros.
+func (pub *PublicKey) Export() []byte {
+	output := make([]byte, pub.params.PublicKeySize)
+	ssSz := pub.params.SharedSecretSize
+	convFp2ToBytes(output[0:ssSz], &pub.affine_xP)
+	convFp2ToBytes(output[ssSz:2*ssSz], &pub.affine_xQ)
+	convFp2ToBytes(output[2*ssSz:3*ssSz], &pub.affine_xQmP)
+	return output
+}
+
+// Size returns size of the public key in bytes
+func (pub *PublicKey) Size() int {
+	return pub.params.PublicKeySize
+}
+
+// Exports currently stored key. In case structure hasn't been filled with key data
+// returned byte string is filled with zeros.
+func (prv *PrivateKey) Export() []byte {
+	ret := make([]byte, len(prv.Scalar)+len(prv.S))
+	copy(ret, prv.S)
+	copy(ret[len(prv.S):], prv.Scalar)
+	return ret
+}
+
+// Size returns size of the private key in bytes
+func (prv *PrivateKey) Size() int {
+	tmp := len(prv.Scalar)
+	if prv.keyVariant == KeyVariant_SIKE {
+		tmp += int(prv.params.MsgLen)
+	}
+	return tmp
+}
+
+// Import clears content of the private key currently stored in the structure
+// and imports key from octet string. In case of SIKE, the random value 'S'
+// must be prepended to the value of actual private key (see SIKE spec for details).
+// Function doesn't import public key value to PrivateKey object.
+func (prv *PrivateKey) Import(input []byte) error {
+	if len(input) != prv.Size() {
+		return errors.New("sidh: input to short")
+	}
+	copy(prv.S, input[:len(prv.S)])
+	copy(prv.Scalar, input[len(prv.S):])
+	return nil
+}
+
+// Generates random private key for SIDH or SIKE. Generated value is
+// formed as little-endian integer from key-space <2^(e2-1)..2^e2 - 1>
+// for KeyVariant_A or <2^(s-1)..2^s - 1>, where s = floor(log_2(3^e3)),
+// for KeyVariant_B.
+//
+// Returns error in case user provided RNG fails.
+func (prv *PrivateKey) Generate(rand io.Reader) error {
+	var err error
+	var dp *DomainParams
+
+	if (prv.keyVariant & KeyVariant_SIDH_A) == KeyVariant_SIDH_A {
+		dp = &prv.params.A
+	} else {
+		dp = &prv.params.B
+	}
+
+	if prv.keyVariant == KeyVariant_SIKE {
+		_, err = io.ReadFull(rand, prv.S)
+	}
+
+	// Private key generation takes advantage of the fact that keyspace for secret
+	// key is (0, 2^x - 1), for some possitivite value of 'x' (see SIKE, 1.3.8).
+	// It means that all bytes in the secret key, but the last one, can take any
+	// value between <0x00,0xFF>. Similarily for the last byte, but generation
+	// needs to chop off some bits, to make sure generated value is an element of
+	// a key-space.
+	_, err = io.ReadFull(rand, prv.Scalar)
+	if err != nil {
+		return err
+	}
+	prv.Scalar[len(prv.Scalar)-1] &= (1 << (dp.SecretBitLen % 8)) - 1
+	// Make sure scalar is SecretBitLen long. SIKE spec says that key
+	// space starts from 0, but I'm not confortable with having low
+	// value scalars used for private keys. It is still secrure as per
+	// table 5.1 in [SIKE].
+	prv.Scalar[len(prv.Scalar)-1] |= 1 << ((dp.SecretBitLen % 8) - 1)
+	return err
+}
+
+// Generates public key.
+//
+// Constant time.
+func (prv *PrivateKey) GeneratePublicKey() *PublicKey {
+	if (prv.keyVariant & KeyVariant_SIDH_A) == KeyVariant_SIDH_A {
+		return publicKeyGenA(prv)
+	}
+	return publicKeyGenB(prv)
+}
+
+// Computes a shared secret which is a j-invariant. Function requires that pub has
+// different KeyVariant than prv. Length of returned output is 2*ceil(log_2 P)/8),
+// where P is a prime defining finite field.
+//
+// It's important to notice that each keypair must not be used more than once
+// to calculate shared secret.
+//
+// Function may return error. This happens only in case provided input is invalid.
+// Constant time for properly initialized private and public key.
+func DeriveSecret(prv *PrivateKey, pub *PublicKey) ([]byte, error) {
+
+	if (pub == nil) || (prv == nil) {
+		return nil, errors.New("sidh: invalid arguments")
+	}
+
+	if (pub.keyVariant == prv.keyVariant) || (pub.params.Id != prv.params.Id) {
+		return nil, errors.New("sidh: public and private are incompatbile")
+	}
+
+	if (prv.keyVariant & KeyVariant_SIDH_A) == KeyVariant_SIDH_A {
+		return deriveSecretA(prv, pub), nil
+	} else {
+		return deriveSecretB(prv, pub), nil
+	}
+}
+
+// Uses SIKE public key to encrypt plaintext. Requires cryptographically secure PRNG
+// Returns ciphertext in case encryption succeeds. Returns error in case PRNG fails
+// or wrongly formatted input was provided.
+func Encrypt(rng io.Reader, pub *PublicKey, ptext []byte) ([]byte, error) {
+	var ptextLen = len(ptext)
+	// c1 must be security level + 64 bits (see [SIKE] 1.4 and 4.3.3)
+	if ptextLen != pub.params.KemSize {
+		return nil, errors.New("Unsupported message length")
+	}
+
+	skA := NewPrivateKey(KeyVariant_SIDH_A)
+	err := skA.Generate(rng)
+	if err != nil {
+		return nil, err
+	}
+
+	pkA := skA.GeneratePublicKey()
+	return encrypt(skA, pkA, pub, ptext)
+}
+
+// Uses SIKE private key to decrypt ciphertext. Returns plaintext in case
+// decryption succeeds or error in case unexptected input was provided.
+// Constant time
+func Decrypt(prv *PrivateKey, ctext []byte) ([]byte, error) {
+	var c1_len int
+	n := make([]byte, prv.params.KemSize)
+	pk_len := prv.params.PublicKeySize
+
+	if prv.keyVariant != KeyVariant_SIKE {
+		return nil, errors.New("wrong key type")
+	}
+
+	// ctext is a concatenation of (pubkey_A || c1=ciphertext)
+	// it must be security level + 64 bits (see [SIKE] 1.4 and 4.3.3)
+	c1_len = len(ctext) - pk_len
+	if c1_len != int(prv.params.KemSize) {
+		return nil, errors.New("wrong size of cipher text")
+	}
+
+	c0 := NewPublicKey(KeyVariant_SIDH_A)
+	err := c0.Import(ctext[:pk_len])
+	if err != nil {
+		return nil, err
+	}
+	j, err := DeriveSecret(prv, c0)
+	if err != nil {
+		return nil, err
+	}
+
+	digest := sha256.Sum256(j)
+	copy(n, digest[:])
+
+	for i, _ := range n {
+		n[i] ^= ctext[pk_len+i]
+	}
+	return n[:c1_len], nil
+}
+
+// Encapsulation receives the public key and generates SIKE ciphertext and shared secret.
+// The generated ciphertext is used for authentication.
+// The rng must be cryptographically secure PRNG.
+// Error is returned in case PRNG fails or wrongly formatted input was provided.
+func Encapsulate(rng io.Reader, pub *PublicKey) (ctext []byte, secret []byte, err error) {
+	// Buffer for random, secret message
+	ptext := make([]byte, pub.params.MsgLen)
+	// SHA256 hash context object
+	d := sha256.New()
+
+	// Generate ephemeral value
+	_, err = io.ReadFull(rng, ptext)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	// Implementation uses first 28-bytes of secret
+	d.Write(ptext)
+	d.Write(pub.Export())
+	digest := d.Sum(nil)
+	// r = G(ptext||pub)
+	r := digest[:pub.params.A.SecretByteLen]
+
+	// (c0 || c1) = Enc(pkA, ptext; r)
+	skA := NewPrivateKey(KeyVariant_SIDH_A)
+	err = skA.Import(r)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	pkA := skA.GeneratePublicKey()
+	ctext, err = encrypt(skA, pkA, pub, ptext)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	// K = H(ptext||(c0||c1))
+	d.Reset()
+	d.Write(ptext)
+	d.Write(ctext)
+	digest = d.Sum(digest[:0])
+	return ctext, digest[:pub.params.KemSize], nil
+}
+
+// Decapsulate given the keypair and ciphertext as inputs, Decapsulate outputs a shared
+// secret if plaintext verifies correctly, otherwise function outputs random value.
+// Decapsulation may fail in case input is wrongly formatted.
+// Constant time for properly initialized input.
+func Decapsulate(prv *PrivateKey, pub *PublicKey, ctext []byte) ([]byte, error) {
+	var skA = NewPrivateKey(KeyVariant_SIDH_A)
+	// SHA256 hash context object
+	d := sha256.New()
+
+	m, err := Decrypt(prv, ctext)
+	if err != nil {
+		return nil, err
+	}
+
+	// r' = G(m'||pub)
+	d.Write(m)
+	d.Write(pub.Export())
+	digest := d.Sum(nil)
+	// Never fails
+	skA.Import(digest[:pub.params.A.SecretByteLen])
+
+	// Never fails
+	pkA := skA.GeneratePublicKey()
+	c0 := pkA.Export()
+
+	d.Reset()
+	if subtle.ConstantTimeCompare(c0, ctext[:len(c0)]) == 1 {
+		d.Write(m)
+	} else {
+		// S is chosen at random when generating a key and is unknown to the other party. It
+		// may seem weird, but it's correct. It is important that S is unpredictable
+		// to other party. Without this check, it is possible to recover a secret, by
+		// providing series of invalid ciphertexts. It is also important that in case
+		//
+		// See more details in "On the security of supersingular isogeny cryptosystems"
+		// (S. Galbraith, et al., 2016, ePrint #859).
+		d.Write(prv.S)
+	}
+	d.Write(ctext)
+	digest = d.Sum(digest[:0])
+	return digest[:pub.params.KemSize], nil
+}
diff --git a/src/ssl/test/runner/sike/sike_test.go b/src/ssl/test/runner/sike/sike_test.go
new file mode 100644
index 0000000..2e146bc
--- /dev/null
+++ b/src/ssl/test/runner/sike/sike_test.go
@@ -0,0 +1,698 @@
+// Copyright (c) 2019, Cloudflare 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.
+
+package sike
+
+import (
+	"bufio"
+	"bytes"
+	"crypto/rand"
+	"encoding/hex"
+	"math/big"
+	"strings"
+	"testing"
+)
+
+var tdata = struct {
+	name     string
+	PrB_sidh string
+	PkB_sidh string
+	PrA_sidh string
+	PkA_sidh string
+	PkB_sike string
+	PrB_sike string
+}{
+	name:     "P-434",
+	PrA_sidh: "3A727E04EA9B7E2A766A6F846489E7E7B915263BCEED308BB10FC9",
+	PkA_sidh: "9E668D1E6750ED4B91EE052C32839CA9DD2E56D52BC24DECC950AA" +
+		"AD24CEED3F9049C77FE80F0B9B01E7F8DAD7833EEC2286544D6380" +
+		"009C379CDD3E7517CEF5E20EB01F8231D52FC30DC61D2F63FB357F" +
+		"85DC6396E8A95DB9740BD3A972C8DB7901B31F074CD3E45345CA78" +
+		"F900817130E688A29A7CF0073B5C00FF2C65FBE776918EF9BD8E75" +
+		"B29EF7FAB791969B60B0C5B37A8992EDEF95FA7BAC40A95DAFE02E" +
+		"237301FEE9A7A43FD0B73477E8035DD12B73FAFEF18D39904DDE36" +
+		"53A754F36BE1888F6607C6A7951349A414352CF31A29F2C40302DB" +
+		"406C48018C905EB9DC46AFBF42A9187A9BB9E51B587622A2862DC7" +
+		"D5CC598BF38ED6320FB51D8697AD3D7A72ABCC32A393F0133DA8DF" +
+		"5E253D9E00B760B2DF342FCE974DCFE946CFE4727783531882800F" +
+		"9E5DD594D6D5A6275EEFEF9713ED838F4A06BB34D7B8D46E0B385A" +
+		"AEA1C7963601",
+	PrB_sidh: "E37BFE55B43B32448F375903D8D226EC94ADBFEA1D2B3536EB987001",
+	PkB_sidh: "C9F73E4497AAA3FDF9EB688135866A8A83934BA10E273B8CC3808C" +
+		"F0C1F5FAB3E9BB295885881B73DEBC875670C0F51C4BB40DF5FEDE" +
+		"01B8AF32D1BF10508B8C17B2734EB93B2B7F5D84A4A0F2F816E9E2" +
+		"C32AC253C0B6025B124D05A87A9E2A8567930F44BAA14219B941B6" +
+		"B400B4AED1D796DA12A5A9F0B8F3F5EE9DD43F64CB24A3B1719DF2" +
+		"78ADF56B5F3395187829DA2319DEABF6BBD6EDA244DE2B62CC5AC2" +
+		"50C1009DD1CD4712B0B37406612AD002B5E51A62B51AC9C0374D14" +
+		"3ABBBD58275FAFC4A5E959C54838C2D6D9FB43B7B2609061267B6A" +
+		"2E6C6D01D295C4223E0D3D7A4CDCFB28A7818A737935279751A6DD" +
+		"8290FD498D1F6AD5F4FFF6BDFA536713F509DCE8047252F1E7D0DD" +
+		"9FCC414C0070B5DCCE3665A21A032D7FBE749181032183AFAD240B" +
+		"7E671E87FBBEC3A8CA4C11AA7A9A23AC69AE2ACF54B664DECD2775" +
+		"3D63508F1B02",
+	PrB_sike: "4B622DE1350119C45A9F2E2EF3DC5DF56A27FCDFCDDAF58CD69B90" +
+		"3752D68C200934E160B234E49EDE247601",
+	PkB_sike: "1BD0A2E81307B6F96461317DDF535ACC0E59C742627BAE60D27605" +
+		"E10FAF722D22A73E184CB572A12E79DCD58C6B54FB01442114CBE9" +
+		"010B6CAEC25D04C16C5E42540C1524C545B8C67614ED4183C9FA5B" +
+		"D0BE45A7F89FBC770EE8E7E5E391C7EE6F35F74C29E6D9E35B1663" +
+		"DA01E48E9DEB2347512D366FDE505161677055E3EF23054D276E81" +
+		"7E2C57025DA1C10D2461F68617F2D11256EEE4E2D7DBDF6C8E34F3" +
+		"A0FD00C625428CB41857002159DAB94267ABE42D630C6AAA91AF83" +
+		"7C7A6740754EA6634C45454C51B0BB4D44C3CCCCE4B32C00901CF6" +
+		"9C008D013348379B2F9837F428A01B6173584691F2A6F3A3C4CF48" +
+		"7D20D261B36C8CDB1BC158E2A5162A9DA4F7A97AA0879B9897E2B6" +
+		"891B672201F9AEFBF799C27B2587120AC586A511360926FB7DA8EB" +
+		"F5CB5272F396AE06608422BE9792E2CE9BEF21BF55B7EFF8DC7EC8" +
+		"C99910D3F800",
+}
+
+/* -------------------------------------------------------------------------
+   Helpers
+   -------------------------------------------------------------------------*/
+// Fail if err !=nil. Display msg as an error message
+func checkErr(t testing.TB, err error, msg string) {
+	t.Helper()
+	if err != nil {
+		t.Error(msg)
+	}
+}
+
+// Utility used for running same test with all registered prime fields
+type MultiIdTestingFunc func(testing.TB)
+
+// Converts string to private key
+func convToPrv(s string, v KeyVariant) *PrivateKey {
+	key := NewPrivateKey(v)
+	hex, e := hex.DecodeString(s)
+	if e != nil {
+		panic("non-hex number provided")
+	}
+	e = key.Import(hex)
+	if e != nil {
+		panic("Can't import private key")
+	}
+	return key
+}
+
+// Converts string to public key
+func convToPub(s string, v KeyVariant) *PublicKey {
+	key := NewPublicKey(v)
+	hex, e := hex.DecodeString(s)
+	if e != nil {
+		panic("non-hex number provided")
+	}
+	e = key.Import(hex)
+	if e != nil {
+		panic("Can't import public key")
+	}
+	return key
+}
+
+/* -------------------------------------------------------------------------
+   Unit tests
+   -------------------------------------------------------------------------*/
+func TestKeygen(t *testing.T) {
+	alicePrivate := convToPrv(tdata.PrA_sidh, KeyVariant_SIDH_A)
+	bobPrivate := convToPrv(tdata.PrB_sidh, KeyVariant_SIDH_B)
+	expPubA := convToPub(tdata.PkA_sidh, KeyVariant_SIDH_A)
+	expPubB := convToPub(tdata.PkB_sidh, KeyVariant_SIDH_B)
+
+	pubA := alicePrivate.GeneratePublicKey()
+	pubB := bobPrivate.GeneratePublicKey()
+
+	if !bytes.Equal(pubA.Export(), expPubA.Export()) {
+		t.Fatalf("unexpected value of public key A")
+	}
+	if !bytes.Equal(pubB.Export(), expPubB.Export()) {
+		t.Fatalf("unexpected value of public key B")
+	}
+}
+
+func TestImportExport(t *testing.T) {
+	var err error
+	a := NewPublicKey(KeyVariant_SIDH_A)
+	b := NewPublicKey(KeyVariant_SIDH_B)
+
+	// Import keys
+	a_hex, err := hex.DecodeString(tdata.PkA_sidh)
+	checkErr(t, err, "invalid hex-number provided")
+
+	err = a.Import(a_hex)
+	checkErr(t, err, "import failed")
+
+	b_hex, err := hex.DecodeString(tdata.PkB_sike)
+	checkErr(t, err, "invalid hex-number provided")
+
+	err = b.Import(b_hex)
+	checkErr(t, err, "import failed")
+
+	// Export and check if same
+	if !bytes.Equal(b.Export(), b_hex) || !bytes.Equal(a.Export(), a_hex) {
+		t.Fatalf("export/import failed")
+	}
+
+	if (len(b.Export()) != b.Size()) || (len(a.Export()) != a.Size()) {
+		t.Fatalf("wrong size of exported keys")
+	}
+}
+
+func testPrivateKeyBelowMax(t testing.TB) {
+	for variant, keySz := range map[KeyVariant]*DomainParams{
+		KeyVariant_SIDH_A: &Params.A,
+		KeyVariant_SIDH_B: &Params.B} {
+
+		func(v KeyVariant, dp *DomainParams) {
+			var blen = int(dp.SecretByteLen)
+			var prv = NewPrivateKey(v)
+
+			// Calculate either (2^e2 - 1) or (2^s - 1); where s=ceil(log_2(3^e3)))
+			maxSecertVal := big.NewInt(int64(dp.SecretBitLen))
+			maxSecertVal.Exp(big.NewInt(int64(2)), maxSecertVal, nil)
+			maxSecertVal.Sub(maxSecertVal, big.NewInt(1))
+
+			// Do same test 1000 times
+			for i := 0; i < 1000; i++ {
+				err := prv.Generate(rand.Reader)
+				checkErr(t, err, "Private key generation")
+
+				// Convert to big-endian, as that's what expected by (*Int)SetBytes()
+				secretBytes := prv.Export()
+				for i := 0; i < int(blen/2); i++ {
+					tmp := secretBytes[i] ^ secretBytes[blen-i-1]
+					secretBytes[i] = tmp ^ secretBytes[i]
+					secretBytes[blen-i-1] = tmp ^ secretBytes[blen-i-1]
+				}
+				prvBig := new(big.Int).SetBytes(secretBytes)
+				// Check if generated key is bigger than acceptable
+				if prvBig.Cmp(maxSecertVal) == 1 {
+					t.Error("Generated private key is wrong")
+				}
+			}
+		}(variant, keySz)
+	}
+}
+
+func testKeyAgreement(t *testing.T, pkA, prA, pkB, prB string) {
+	var e error
+
+	// KeyPairs
+	alicePublic := convToPub(pkA, KeyVariant_SIDH_A)
+	bobPublic := convToPub(pkB, KeyVariant_SIDH_B)
+	alicePrivate := convToPrv(prA, KeyVariant_SIDH_A)
+	bobPrivate := convToPrv(prB, KeyVariant_SIDH_B)
+
+	// Do actual test
+	s1, e := DeriveSecret(bobPrivate, alicePublic)
+	checkErr(t, e, "derivation s1")
+	s2, e := DeriveSecret(alicePrivate, bobPublic)
+	checkErr(t, e, "derivation s1")
+
+	if !bytes.Equal(s1[:], s2[:]) {
+		t.Fatalf("two shared keys: %d, %d do not match", s1, s2)
+	}
+
+	// Negative case
+	dec, e := hex.DecodeString(tdata.PkA_sidh)
+	if e != nil {
+		t.FailNow()
+	}
+	dec[0] = ^dec[0]
+	e = alicePublic.Import(dec)
+	if e != nil {
+		t.FailNow()
+	}
+
+	s1, e = DeriveSecret(bobPrivate, alicePublic)
+	checkErr(t, e, "derivation of s1 failed")
+	s2, e = DeriveSecret(alicePrivate, bobPublic)
+	checkErr(t, e, "derivation of s2 failed")
+
+	if bytes.Equal(s1[:], s2[:]) {
+		t.Fatalf("The two shared keys: %d, %d match", s1, s2)
+	}
+}
+
+func TestDerivationRoundTrip(t *testing.T) {
+	var err error
+
+	prvA := NewPrivateKey(KeyVariant_SIDH_A)
+	prvB := NewPrivateKey(KeyVariant_SIDH_B)
+
+	// Generate private keys
+	err = prvA.Generate(rand.Reader)
+	checkErr(t, err, "key generation failed")
+	err = prvB.Generate(rand.Reader)
+	checkErr(t, err, "key generation failed")
+
+	// Generate public keys
+	pubA := prvA.GeneratePublicKey()
+	pubB := prvB.GeneratePublicKey()
+
+	// Derive shared secret
+	s1, err := DeriveSecret(prvB, pubA)
+	checkErr(t, err, "")
+
+	s2, err := DeriveSecret(prvA, pubB)
+	checkErr(t, err, "")
+
+	if !bytes.Equal(s1[:], s2[:]) {
+		t.Fatalf("Two shared keys: \n%X, \n%X do not match", s1, s2)
+	}
+}
+
+// Encrypt, Decrypt, check if input/output plaintext is the same
+func testPKERoundTrip(t testing.TB, id uint8) {
+	// Message to be encrypted
+	var msg = make([]byte, Params.MsgLen)
+	for i, _ := range msg {
+		msg[i] = byte(i)
+	}
+
+	// Import keys
+	pkB := NewPublicKey(KeyVariant_SIKE)
+	skB := NewPrivateKey(KeyVariant_SIKE)
+	pk_hex, err := hex.DecodeString(tdata.PkB_sike)
+	if err != nil {
+		t.Fatal(err)
+	}
+	sk_hex, err := hex.DecodeString(tdata.PrB_sike)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if pkB.Import(pk_hex) != nil || skB.Import(sk_hex) != nil {
+		t.Error("Import")
+	}
+
+	ct, err := Encrypt(rand.Reader, pkB, msg[:])
+	if err != nil {
+		t.Fatal(err)
+	}
+	pt, err := Decrypt(skB, ct)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !bytes.Equal(pt[:], msg[:]) {
+		t.Errorf("Decryption failed \n got : %X\n exp : %X", pt, msg)
+	}
+}
+
+// Generate key and check if can encrypt
+func TestPKEKeyGeneration(t *testing.T) {
+	// Message to be encrypted
+	var msg = make([]byte, Params.MsgLen)
+	var err error
+	for i, _ := range msg {
+		msg[i] = byte(i)
+	}
+
+	sk := NewPrivateKey(KeyVariant_SIKE)
+	err = sk.Generate(rand.Reader)
+	checkErr(t, err, "PEK key generation")
+	pk := sk.GeneratePublicKey()
+
+	// Try to encrypt
+	ct, err := Encrypt(rand.Reader, pk, msg[:])
+	checkErr(t, err, "PEK encryption")
+	pt, err := Decrypt(sk, ct)
+	checkErr(t, err, "PEK key decryption")
+
+	if !bytes.Equal(pt[:], msg[:]) {
+		t.Fatalf("Decryption failed \n got : %X\n exp : %X", pt, msg)
+	}
+}
+
+func TestNegativePKE(t *testing.T) {
+	var msg [40]byte
+	var err error
+
+	// Generate key
+	sk := NewPrivateKey(KeyVariant_SIKE)
+	err = sk.Generate(rand.Reader)
+	checkErr(t, err, "key generation")
+
+	pk := sk.GeneratePublicKey()
+
+	// bytelen(msg) - 1
+	ct, err := Encrypt(rand.Reader, pk, msg[:Params.KemSize+8-1])
+	if err == nil {
+		t.Fatal("Error hasn't been returned")
+	}
+	if ct != nil {
+		t.Fatal("Ciphertext must be nil")
+	}
+
+	// KemSize - 1
+	pt, err := Decrypt(sk, msg[:Params.KemSize+8-1])
+	if err == nil {
+		t.Fatal("Error hasn't been returned")
+	}
+	if pt != nil {
+		t.Fatal("Ciphertext must be nil")
+	}
+}
+
+func testKEMRoundTrip(t *testing.T, pkB, skB []byte) {
+	// Import keys
+	pk := NewPublicKey(KeyVariant_SIKE)
+	sk := NewPrivateKey(KeyVariant_SIKE)
+	if pk.Import(pkB) != nil || sk.Import(skB) != nil {
+		t.Error("Import failed")
+	}
+
+	ct, ss_e, err := Encapsulate(rand.Reader, pk)
+	if err != nil {
+		t.Error("Encapsulate failed")
+	}
+
+	ss_d, err := Decapsulate(sk, pk, ct)
+	if err != nil {
+		t.Error("Decapsulate failed")
+	}
+	if !bytes.Equal(ss_e, ss_d) {
+		t.Error("Shared secrets from decapsulation and encapsulation differ")
+	}
+}
+
+func TestKEMRoundTrip(t *testing.T) {
+	pk, err := hex.DecodeString(tdata.PkB_sike)
+	checkErr(t, err, "public key B not a number")
+	sk, err := hex.DecodeString(tdata.PrB_sike)
+	checkErr(t, err, "private key B not a number")
+	testKEMRoundTrip(t, pk, sk)
+}
+
+func TestKEMKeyGeneration(t *testing.T) {
+	// Generate key
+	sk := NewPrivateKey(KeyVariant_SIKE)
+	checkErr(t, sk.Generate(rand.Reader), "error: key generation")
+	pk := sk.GeneratePublicKey()
+
+	// calculated shared secret
+	ct, ss_e, err := Encapsulate(rand.Reader, pk)
+
+	checkErr(t, err, "encapsulation failed")
+	ss_d, err := Decapsulate(sk, pk, ct)
+	checkErr(t, err, "decapsulation failed")
+
+	if !bytes.Equal(ss_e, ss_d) {
+		t.Fatalf("KEM failed \n encapsulated: %X\n decapsulated: %X", ss_d, ss_e)
+	}
+}
+
+func TestNegativeKEM(t *testing.T) {
+	sk := NewPrivateKey(KeyVariant_SIKE)
+	checkErr(t, sk.Generate(rand.Reader), "error: key generation")
+	pk := sk.GeneratePublicKey()
+
+	ct, ss_e, err := Encapsulate(rand.Reader, pk)
+	checkErr(t, err, "pre-requisite for a test failed")
+
+	ct[0] = ct[0] - 1
+	ss_d, err := Decapsulate(sk, pk, ct)
+	checkErr(t, err, "decapsulation returns error when invalid ciphertext provided")
+
+	if bytes.Equal(ss_e, ss_d) {
+		// no idea how this could ever happen, but it would be very bad
+		t.Error("critical error")
+	}
+
+	// Try encapsulating with SIDH key
+	pkSidh := NewPublicKey(KeyVariant_SIDH_B)
+	prSidh := NewPrivateKey(KeyVariant_SIDH_B)
+	_, _, err = Encapsulate(rand.Reader, pkSidh)
+	if err == nil {
+		t.Error("encapsulation accepts SIDH public key")
+	}
+	// Try decapsulating with SIDH key
+	_, err = Decapsulate(prSidh, pk, ct)
+	if err == nil {
+		t.Error("decapsulation accepts SIDH private key key")
+	}
+}
+
+// In case invalid ciphertext is provided, SIKE's decapsulation must
+// return same (but unpredictable) result for a given key.
+func TestNegativeKEMSameWrongResult(t *testing.T) {
+	sk := NewPrivateKey(KeyVariant_SIKE)
+	checkErr(t, sk.Generate(rand.Reader), "error: key generation")
+	pk := sk.GeneratePublicKey()
+
+	ct, encSs, err := Encapsulate(rand.Reader, pk)
+	checkErr(t, err, "pre-requisite for a test failed")
+
+	// make ciphertext wrong
+	ct[0] = ct[0] - 1
+	decSs1, err := Decapsulate(sk, pk, ct)
+	checkErr(t, err, "pre-requisite for a test failed")
+
+	// second decapsulation must be done with same, but imported private key
+	expSk := sk.Export()
+
+	// creat new private key
+	sk = NewPrivateKey(KeyVariant_SIKE)
+	err = sk.Import(expSk)
+	checkErr(t, err, "import failed")
+
+	// try decapsulating again. ss2 must be same as ss1 and different than
+	// original plaintext
+	decSs2, err := Decapsulate(sk, pk, ct)
+	checkErr(t, err, "pre-requisite for a test failed")
+
+	if !bytes.Equal(decSs1, decSs2) {
+		t.Error("decapsulation is insecure")
+	}
+
+	if bytes.Equal(encSs, decSs1) || bytes.Equal(encSs, decSs2) {
+		// this test requires that decapsulation returns wrong result
+		t.Errorf("test implementation error")
+	}
+}
+
+func readAndCheckLine(r *bufio.Reader) []byte {
+	// Read next line from buffer
+	line, isPrefix, err := r.ReadLine()
+	if err != nil || isPrefix {
+		panic("Wrong format of input file")
+	}
+
+	// Function expects that line is in format "KEY = HEX_VALUE". Get
+	// value, which should be a hex string
+	hexst := strings.Split(string(line), "=")[1]
+	hexst = strings.TrimSpace(hexst)
+	// Convert value to byte string
+	ret, err := hex.DecodeString(hexst)
+	if err != nil {
+		panic("Wrong format of input file")
+	}
+	return ret
+}
+
+func testKeygenSIKE(pk, sk []byte, id uint8) bool {
+	// Import provided private key
+	var prvKey = NewPrivateKey(KeyVariant_SIKE)
+	if prvKey.Import(sk) != nil {
+		panic("sike test: can't load KAT")
+	}
+
+	// Generate public key
+	pubKey := prvKey.GeneratePublicKey()
+	return bytes.Equal(pubKey.Export(), pk)
+}
+
+func testDecapsulation(pk, sk, ct, ssExpected []byte, id uint8) bool {
+	var pubKey = NewPublicKey(KeyVariant_SIKE)
+	var prvKey = NewPrivateKey(KeyVariant_SIKE)
+	if pubKey.Import(pk) != nil || prvKey.Import(sk) != nil {
+		panic("sike test: can't load KAT")
+	}
+
+	ssGot, err := Decapsulate(prvKey, pubKey, ct)
+	if err != nil {
+		panic("sike test: can't perform degcapsulation KAT")
+	}
+
+	return bytes.Equal(ssGot, ssExpected)
+}
+
+func TestKeyAgreement(t *testing.T) {
+	testKeyAgreement(t, tdata.PkA_sidh, tdata.PrA_sidh, tdata.PkB_sidh, tdata.PrB_sidh)
+}
+
+// Same values as in sike_test.cc
+func TestDecapsulation(t *testing.T) {
+	var sk = [16 + 28]byte{
+		0x04, 0x5E, 0x01, 0x42, 0xB8, 0x2F, 0xE1, 0x9A, 0x38, 0x25,
+		0x92, 0xE7, 0xDC, 0xBA, 0xF7, 0x1B, 0xB1, 0xFD, 0x34, 0x42,
+		0xDB, 0x02, 0xBC, 0x9D, 0x4C, 0xD0, 0x72, 0x34, 0x4D, 0xBD,
+		0x06, 0xDF, 0x1C, 0x7D, 0x0A, 0x88, 0xB2, 0x50, 0xC4, 0xF6,
+		0xAE, 0xE8, 0x25, 0x01,
+	}
+
+	var pk = [330]byte{
+		0x6D, 0x8D, 0xF5, 0x7B, 0xCD, 0x47, 0xCA, 0xCB, 0x7A, 0x38,
+		0xB7, 0xA6, 0x90, 0xB7, 0x37, 0x03, 0xD4, 0x6F, 0x27, 0x73,
+		0x74, 0x17, 0x5A, 0xA4, 0x0D, 0xC6, 0x81, 0xAD, 0xDB, 0xF7,
+		0x18, 0xB2, 0x3C, 0x30, 0xCF, 0xAA, 0x08, 0x11, 0x91, 0xCC,
+		0x27, 0x4E, 0xF1, 0xA6, 0xB7, 0xDA, 0xD2, 0xCF, 0x99, 0x7F,
+		0xF7, 0xE1, 0xD0, 0xCE, 0x00, 0xD2, 0x4B, 0xA4, 0x33, 0xB4,
+		0x87, 0x01, 0x3F, 0x02, 0xF7, 0xF9, 0xDE, 0xC3, 0x60, 0x62,
+		0xDA, 0x3F, 0x74, 0xA9, 0x44, 0xBE, 0x19, 0xD5, 0x03, 0x2A,
+		0x79, 0x8C, 0xA7, 0xFF, 0xEA, 0xB3, 0xBB, 0xB5, 0xD4, 0x1D,
+		0x8F, 0x92, 0xCE, 0x62, 0x6E, 0x99, 0x24, 0xD7, 0x57, 0xFA,
+		0xCD, 0xB6, 0xE2, 0x8E, 0xFD, 0x22, 0x0E, 0x31, 0x21, 0x01,
+		0x8D, 0x79, 0xF8, 0x3E, 0x27, 0xEC, 0x43, 0x40, 0xDB, 0x82,
+		0xE5, 0xEB, 0x6C, 0x97, 0x66, 0x29, 0x15, 0x68, 0xB7, 0x4D,
+		0x84, 0xD1, 0x8A, 0x0B, 0x12, 0x36, 0x2C, 0x0C, 0x0A, 0x6E,
+		0x4E, 0xDE, 0xA5, 0x8A, 0xDE, 0x77, 0xDD, 0x70, 0x49, 0x73,
+		0xAC, 0x27, 0x6D, 0x8D, 0x25, 0x9A, 0xE4, 0x25, 0xE8, 0x95,
+		0x8F, 0xFE, 0x90, 0x3B, 0x00, 0x69, 0x20, 0xE8, 0x7C, 0xA5,
+		0xF5, 0x79, 0xC0, 0x61, 0x51, 0x91, 0x35, 0x25, 0x3F, 0x17,
+		0x2F, 0x70, 0x73, 0xF0, 0x89, 0xB5, 0xC8, 0x25, 0xB8, 0xE5,
+		0x7E, 0x34, 0xDD, 0x11, 0xE5, 0xD6, 0xC3, 0xD5, 0x29, 0x89,
+		0xC6, 0x2C, 0x99, 0x53, 0x1D, 0x2C, 0x77, 0xB0, 0xB6, 0xA1,
+		0xBD, 0x79, 0xFB, 0x4A, 0xC2, 0x48, 0x4C, 0x62, 0x51, 0x00,
+		0xE3, 0x91, 0x2A, 0xCB, 0x84, 0x03, 0x5D, 0x2D, 0xC8, 0x33,
+		0xE9, 0x14, 0xBF, 0x74, 0x21, 0xBC, 0xF4, 0x76, 0xE5, 0x42,
+		0xB8, 0xBD, 0xE2, 0xE7, 0x20, 0x95, 0x54, 0xF2, 0xED, 0xC0,
+		0x79, 0x38, 0x1E, 0xD2, 0xEA, 0x1A, 0x63, 0x85, 0xE7, 0x3A,
+		0xDA, 0xAD, 0xAB, 0x1B, 0x1E, 0x19, 0x9E, 0x73, 0xD0, 0x10,
+		0x2E, 0x38, 0xAC, 0x8B, 0x00, 0x6A, 0x30, 0x2C, 0x3D, 0x70,
+		0x8E, 0x39, 0x6D, 0xC0, 0x12, 0x61, 0x7D, 0x2A, 0x0A, 0x04,
+		0x95, 0x8E, 0x09, 0x3C, 0x7B, 0xEC, 0x2E, 0xBC, 0xE8, 0xE8,
+		0xE8, 0x37, 0x29, 0xC4, 0x7E, 0x76, 0x48, 0xB9, 0x3B, 0x72,
+		0xE5, 0x99, 0x9B, 0xF9, 0xE3, 0x99, 0x72, 0x3F, 0x35, 0x29,
+		0x85, 0xE0, 0xC8, 0xBF, 0xB1, 0x6B, 0xB1, 0x6E, 0x72, 0x00,
+	}
+
+	var ct = [330 + 16]byte{
+		0xFF, 0xEB, 0xEF, 0x4A, 0xC0, 0x57, 0x0F, 0x26, 0xAC, 0x76,
+		0xA8, 0xB0, 0xA3, 0x5D, 0x9C, 0xD9, 0x25, 0xD1, 0x7F, 0x92,
+		0x5D, 0xF4, 0x23, 0x34, 0xC3, 0x03, 0x10, 0xE1, 0xB0, 0x24,
+		0x9B, 0x44, 0x58, 0x26, 0x13, 0x56, 0x83, 0x43, 0x72, 0x69,
+		0x28, 0x0D, 0x55, 0x07, 0x1F, 0xDB, 0xC0, 0x23, 0x34, 0x83,
+		0x1A, 0x09, 0x9B, 0x80, 0x00, 0x64, 0x56, 0xDC, 0x79, 0x7A,
+		0xD2, 0xCE, 0x23, 0xC9, 0x72, 0x27, 0xFC, 0x8D, 0xAB, 0xBF,
+		0xD3, 0x17, 0xF6, 0x91, 0x7B, 0x15, 0x93, 0x83, 0x8A, 0x4F,
+		0x6C, 0xCA, 0x4A, 0x94, 0xDA, 0xC7, 0x9D, 0xB6, 0xD6, 0xBA,
+		0xBD, 0x81, 0x9A, 0x78, 0xE5, 0xE5, 0xBE, 0x17, 0xBC, 0xCB,
+		0xC8, 0x23, 0x80, 0x5F, 0x75, 0xF8, 0xDB, 0x51, 0x55, 0x00,
+		0x25, 0x33, 0x52, 0x64, 0xB2, 0xD6, 0xD8, 0x9A, 0x2A, 0x9E,
+		0x29, 0x99, 0x13, 0x33, 0xE2, 0xA7, 0x98, 0xAC, 0xD7, 0x79,
+		0x5C, 0x2F, 0xBA, 0x07, 0xC3, 0x03, 0x37, 0xD6, 0xE6, 0xB5,
+		0xA1, 0xF5, 0x29, 0xB6, 0xF6, 0xC0, 0x5C, 0x44, 0x68, 0x2B,
+		0x0B, 0xF5, 0x00, 0x01, 0x44, 0xD5, 0xCC, 0x23, 0xB5, 0x27,
+		0x4F, 0xCA, 0xB4, 0x05, 0x01, 0xF9, 0xD4, 0x41, 0xE0, 0xE1,
+		0x1E, 0xCF, 0xA9, 0xBC, 0x79, 0xD7, 0xD5, 0xF5, 0x3C, 0xE6,
+		0x93, 0xF4, 0x6C, 0x84, 0x5A, 0x2C, 0x4B, 0xE4, 0x91, 0xB2,
+		0xB2, 0xB8, 0xAD, 0x74, 0x9A, 0x69, 0x79, 0x4C, 0x84, 0xB7,
+		0xBF, 0xF1, 0x68, 0x4B, 0xAE, 0x0F, 0x7F, 0x45, 0x3B, 0x18,
+		0x3F, 0xFA, 0x00, 0x48, 0xE0, 0x3A, 0xE2, 0xC0, 0xAE, 0x00,
+		0xCE, 0x90, 0x28, 0xA4, 0x1B, 0xBE, 0xCA, 0x0C, 0x21, 0x29,
+		0x64, 0x30, 0x5E, 0x35, 0xAD, 0xFD, 0x83, 0x47, 0x40, 0x6D,
+		0x15, 0x56, 0xFC, 0xF8, 0x5F, 0xAB, 0x81, 0xFE, 0x6B, 0xE9,
+		0x6B, 0xED, 0x27, 0x35, 0x7C, 0xD8, 0x2C, 0xD4, 0xF2, 0x11,
+		0xE6, 0xAF, 0xDF, 0xB8, 0x91, 0x96, 0xEB, 0xF7, 0x4C, 0x8D,
+		0x70, 0x77, 0x90, 0x81, 0x00, 0x09, 0x19, 0x27, 0x8A, 0x9E,
+		0xB6, 0x1A, 0xE9, 0xAC, 0x6C, 0xC9, 0xF8, 0xEA, 0xA2, 0x34,
+		0xB8, 0xAC, 0xB3, 0xB3, 0x68, 0xA1, 0xB7, 0x29, 0x55, 0xCA,
+		0x40, 0x23, 0x92, 0x5C, 0x0C, 0x79, 0x6B, 0xD6, 0x9F, 0x5B,
+		0xD2, 0xE6, 0xAE, 0x04, 0xCB, 0xEC, 0xC7, 0x88, 0x18, 0xDB,
+		0x7A, 0xE6, 0xD6, 0xC9, 0x39, 0xFD, 0x93, 0x9B, 0xC8, 0x01,
+		0x6F, 0x3E, 0x6C, 0x90, 0x3E, 0x73, 0x76, 0x99, 0x7C, 0x48,
+		0xDA, 0x68, 0x48, 0x80, 0x2B, 0x63,
+	}
+	var ssExp = [16]byte{
+		0xA1, 0xF9, 0x5A, 0x67, 0xB9, 0x3D, 0x1E, 0x72, 0xE8, 0xC5,
+		0x71, 0xF1, 0x4C, 0xB2, 0xAA, 0x6D,
+	}
+
+	var prvObj = NewPrivateKey(KeyVariant_SIKE)
+	var pubObj = NewPublicKey(KeyVariant_SIKE)
+
+	if pubObj.Import(pk[:]) != nil || prvObj.Import(sk[:]) != nil {
+		t.Error("Can't import one of the keys")
+	}
+
+	res, _ := Decapsulate(prvObj, pubObj, ct[:])
+	if !bytes.Equal(ssExp[:], res) {
+		t.Error("Wrong decapsulation result")
+	}
+}
+
+/* -------------------------------------------------------------------------
+   Benchmarking
+   -------------------------------------------------------------------------*/
+
+func BenchmarkSidhKeyAgreement(b *testing.B) {
+	// KeyPairs
+	alicePublic := convToPub(tdata.PkA_sidh, KeyVariant_SIDH_A)
+	alicePrivate := convToPrv(tdata.PrA_sidh, KeyVariant_SIDH_A)
+	bobPublic := convToPub(tdata.PkB_sidh, KeyVariant_SIDH_B)
+	bobPrivate := convToPrv(tdata.PrB_sidh, KeyVariant_SIDH_B)
+
+	for i := 0; i < b.N; i++ {
+		// Derive shared secret
+		DeriveSecret(bobPrivate, alicePublic)
+		DeriveSecret(alicePrivate, bobPublic)
+	}
+}
+
+func BenchmarkAliceKeyGenPrv(b *testing.B) {
+	prv := NewPrivateKey(KeyVariant_SIDH_A)
+	for n := 0; n < b.N; n++ {
+		prv.Generate(rand.Reader)
+	}
+}
+
+func BenchmarkBobKeyGenPrv(b *testing.B) {
+	prv := NewPrivateKey(KeyVariant_SIDH_B)
+	for n := 0; n < b.N; n++ {
+		prv.Generate(rand.Reader)
+	}
+}
+
+func BenchmarkAliceKeyGenPub(b *testing.B) {
+	prv := NewPrivateKey(KeyVariant_SIDH_A)
+	prv.Generate(rand.Reader)
+	for n := 0; n < b.N; n++ {
+		prv.GeneratePublicKey()
+	}
+}
+
+func BenchmarkBobKeyGenPub(b *testing.B) {
+	prv := NewPrivateKey(KeyVariant_SIDH_B)
+	prv.Generate(rand.Reader)
+	for n := 0; n < b.N; n++ {
+		prv.GeneratePublicKey()
+	}
+}
+
+func BenchmarkSharedSecretAlice(b *testing.B) {
+	aPr := convToPrv(tdata.PrA_sidh, KeyVariant_SIDH_A)
+	bPk := convToPub(tdata.PkB_sike, KeyVariant_SIDH_B)
+	for n := 0; n < b.N; n++ {
+		DeriveSecret(aPr, bPk)
+	}
+}
+
+func BenchmarkSharedSecretBob(b *testing.B) {
+	// m_B = 3*randint(0,3^238)
+	aPk := convToPub(tdata.PkA_sidh, KeyVariant_SIDH_A)
+	bPr := convToPrv(tdata.PrB_sidh, KeyVariant_SIDH_B)
+	for n := 0; n < b.N; n++ {
+		DeriveSecret(bPr, aPk)
+	}
+}
diff --git a/src/ssl/test/runner/tls.go b/src/ssl/test/runner/tls.go
index 128d22e..1862b3d 100644
--- a/src/ssl/test/runner/tls.go
+++ b/src/ssl/test/runner/tls.go
@@ -276,15 +276,13 @@
 }
 
 func getCertificatePublicKey(cert *x509.Certificate) crypto.PublicKey {
-	if cert.PublicKey != nil {
-		return cert.PublicKey
-	}
-
+	// TODO(davidben): When Go 1.13 is released, use the Ed25519 support in
+	// the standard library.
 	if isEd25519Certificate(cert) {
 		return ed25519.PublicKey(cert.RawSubjectPublicKeyInfo[len(ed25519SPKIPrefix):])
 	}
 
-	return nil
+	return cert.PublicKey
 }
 
 var ed25519PKCS8Prefix = []byte{0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70,
@@ -294,6 +292,12 @@
 // PKCS#1 private keys by default, while OpenSSL 1.0.0 generates PKCS#8 keys.
 // OpenSSL ecparam generates SEC1 EC private keys for ECDSA. We try all three.
 func parsePrivateKey(der []byte) (crypto.PrivateKey, error) {
+	// TODO(davidben): When Go 1.13 is released, use the Ed25519 support in
+	// the standard library.
+	if bytes.HasPrefix(der, ed25519PKCS8Prefix) && len(der) == len(ed25519PKCS8Prefix)+32 {
+		seed := der[len(ed25519PKCS8Prefix):]
+		return ed25519.NewKeyFromSeed(seed), nil
+	}
 	if key, err := x509.ParsePKCS1PrivateKey(der); err == nil {
 		return key, nil
 	}
@@ -309,10 +313,5 @@
 		return key, nil
 	}
 
-	if bytes.HasPrefix(der, ed25519PKCS8Prefix) && len(der) == len(ed25519PKCS8Prefix)+32 {
-		seed := der[len(ed25519PKCS8Prefix):]
-		return ed25519.NewKeyFromSeed(seed), nil
-	}
-
 	return nil, errors.New("crypto/tls: failed to parse private key")
 }
diff --git a/src/ssl/test/test_config.cc b/src/ssl/test/test_config.cc
index 70e061b..bd32ce9 100644
--- a/src/ssl/test/test_config.cc
+++ b/src/ssl/test/test_config.cc
@@ -51,181 +51,181 @@
 }
 
 const Flag<bool> kBoolFlags[] = {
-  { "-server", &TestConfig::is_server },
-  { "-dtls", &TestConfig::is_dtls },
-  { "-fallback-scsv", &TestConfig::fallback_scsv },
-  { "-require-any-client-certificate",
-    &TestConfig::require_any_client_certificate },
-  { "-false-start", &TestConfig::false_start },
-  { "-async", &TestConfig::async },
-  { "-write-different-record-sizes",
-    &TestConfig::write_different_record_sizes },
-  { "-cbc-record-splitting", &TestConfig::cbc_record_splitting },
-  { "-partial-write", &TestConfig::partial_write },
-  { "-no-tls13", &TestConfig::no_tls13 },
-  { "-no-tls12", &TestConfig::no_tls12 },
-  { "-no-tls11", &TestConfig::no_tls11 },
-  { "-no-tls1", &TestConfig::no_tls1 },
-  { "-no-ticket", &TestConfig::no_ticket },
-  { "-enable-channel-id", &TestConfig::enable_channel_id },
-  { "-shim-writes-first", &TestConfig::shim_writes_first },
-  { "-expect-session-miss", &TestConfig::expect_session_miss },
-  { "-decline-alpn", &TestConfig::decline_alpn },
-  { "-select-empty-alpn", &TestConfig::select_empty_alpn },
-  { "-expect-extended-master-secret",
-    &TestConfig::expect_extended_master_secret },
-  { "-enable-ocsp-stapling", &TestConfig::enable_ocsp_stapling },
-  { "-enable-signed-cert-timestamps",
-    &TestConfig::enable_signed_cert_timestamps },
-  { "-implicit-handshake", &TestConfig::implicit_handshake },
-  { "-use-early-callback", &TestConfig::use_early_callback },
-  { "-fail-early-callback", &TestConfig::fail_early_callback },
-  { "-install-ddos-callback", &TestConfig::install_ddos_callback },
-  { "-fail-ddos-callback", &TestConfig::fail_ddos_callback },
-  { "-fail-cert-callback", &TestConfig::fail_cert_callback },
-  { "-handshake-never-done", &TestConfig::handshake_never_done },
-  { "-use-export-context", &TestConfig::use_export_context },
-  { "-tls-unique", &TestConfig::tls_unique },
-  { "-expect-ticket-renewal", &TestConfig::expect_ticket_renewal },
-  { "-expect-no-session", &TestConfig::expect_no_session },
-  { "-expect-ticket-supports-early-data",
-    &TestConfig::expect_ticket_supports_early_data },
-  { "-use-ticket-callback", &TestConfig::use_ticket_callback },
-  { "-renew-ticket", &TestConfig::renew_ticket },
-  { "-enable-early-data", &TestConfig::enable_early_data },
-  { "-check-close-notify", &TestConfig::check_close_notify },
-  { "-shim-shuts-down", &TestConfig::shim_shuts_down },
-  { "-verify-fail", &TestConfig::verify_fail },
-  { "-verify-peer", &TestConfig::verify_peer },
-  { "-verify-peer-if-no-obc", &TestConfig::verify_peer_if_no_obc },
-  { "-expect-verify-result", &TestConfig::expect_verify_result },
-  { "-renegotiate-once", &TestConfig::renegotiate_once },
-  { "-renegotiate-freely", &TestConfig::renegotiate_freely },
-  { "-renegotiate-ignore", &TestConfig::renegotiate_ignore },
-  { "-forbid-renegotiation-after-handshake",
-    &TestConfig::forbid_renegotiation_after_handshake },
-  { "-enable-all-curves", &TestConfig::enable_all_curves },
-  { "-use-old-client-cert-callback",
-    &TestConfig::use_old_client_cert_callback },
-  { "-send-alert", &TestConfig::send_alert },
-  { "-peek-then-read", &TestConfig::peek_then_read },
-  { "-enable-grease", &TestConfig::enable_grease },
-  { "-use-exporter-between-reads", &TestConfig::use_exporter_between_reads },
-  { "-retain-only-sha256-client-cert",
-    &TestConfig::retain_only_sha256_client_cert },
-  { "-expect-sha256-client-cert",
-    &TestConfig::expect_sha256_client_cert },
-  { "-read-with-unfinished-write", &TestConfig::read_with_unfinished_write },
-  { "-expect-secure-renegotiation",
-    &TestConfig::expect_secure_renegotiation },
-  { "-expect-no-secure-renegotiation",
-    &TestConfig::expect_no_secure_renegotiation },
-  { "-expect-session-id", &TestConfig::expect_session_id },
-  { "-expect-no-session-id", &TestConfig::expect_no_session_id },
-  { "-expect-accept-early-data", &TestConfig::expect_accept_early_data },
-  { "-expect-reject-early-data", &TestConfig::expect_reject_early_data },
-  { "-expect-no-offer-early-data", &TestConfig::expect_no_offer_early_data },
-  { "-no-op-extra-handshake", &TestConfig::no_op_extra_handshake },
-  { "-handshake-twice", &TestConfig::handshake_twice },
-  { "-allow-unknown-alpn-protos", &TestConfig::allow_unknown_alpn_protos },
-  { "-enable-ed25519", &TestConfig::enable_ed25519 },
-  { "-use-custom-verify-callback", &TestConfig::use_custom_verify_callback },
-  { "-allow-false-start-without-alpn",
-    &TestConfig::allow_false_start_without_alpn },
-  { "-ignore-tls13-downgrade", &TestConfig::ignore_tls13_downgrade },
-  { "-expect-tls13-downgrade", &TestConfig::expect_tls13_downgrade },
-  { "-handoff", &TestConfig::handoff },
-  { "-no-rsa-pss-rsae-certs", &TestConfig::no_rsa_pss_rsae_certs },
-  { "-use-ocsp-callback", &TestConfig::use_ocsp_callback },
-  { "-set-ocsp-in-callback", &TestConfig::set_ocsp_in_callback },
-  { "-decline-ocsp-callback", &TestConfig::decline_ocsp_callback },
-  { "-fail-ocsp-callback", &TestConfig::fail_ocsp_callback },
-  { "-install-cert-compression-algs",
-    &TestConfig::install_cert_compression_algs },
-  { "-is-handshaker-supported", &TestConfig::is_handshaker_supported },
-  { "-handshaker-resume", &TestConfig::handshaker_resume },
-  { "-reverify-on-resume", &TestConfig::reverify_on_resume },
-  { "-enforce-rsa-key-usage", &TestConfig::enforce_rsa_key_usage },
-  { "-jdk11-workaround", &TestConfig::jdk11_workaround },
-  { "-server-preference", &TestConfig::server_preference },
-  { "-export-traffic-secrets", &TestConfig::export_traffic_secrets },
-  { "-key-update", &TestConfig::key_update },
+    {"-server", &TestConfig::is_server},
+    {"-dtls", &TestConfig::is_dtls},
+    {"-fallback-scsv", &TestConfig::fallback_scsv},
+    {"-require-any-client-certificate",
+     &TestConfig::require_any_client_certificate},
+    {"-false-start", &TestConfig::false_start},
+    {"-async", &TestConfig::async},
+    {"-write-different-record-sizes",
+     &TestConfig::write_different_record_sizes},
+    {"-cbc-record-splitting", &TestConfig::cbc_record_splitting},
+    {"-partial-write", &TestConfig::partial_write},
+    {"-no-tls13", &TestConfig::no_tls13},
+    {"-no-tls12", &TestConfig::no_tls12},
+    {"-no-tls11", &TestConfig::no_tls11},
+    {"-no-tls1", &TestConfig::no_tls1},
+    {"-no-ticket", &TestConfig::no_ticket},
+    {"-enable-channel-id", &TestConfig::enable_channel_id},
+    {"-shim-writes-first", &TestConfig::shim_writes_first},
+    {"-expect-session-miss", &TestConfig::expect_session_miss},
+    {"-decline-alpn", &TestConfig::decline_alpn},
+    {"-select-empty-alpn", &TestConfig::select_empty_alpn},
+    {"-expect-extended-master-secret",
+     &TestConfig::expect_extended_master_secret},
+    {"-enable-ocsp-stapling", &TestConfig::enable_ocsp_stapling},
+    {"-enable-signed-cert-timestamps",
+     &TestConfig::enable_signed_cert_timestamps},
+    {"-implicit-handshake", &TestConfig::implicit_handshake},
+    {"-use-early-callback", &TestConfig::use_early_callback},
+    {"-fail-early-callback", &TestConfig::fail_early_callback},
+    {"-install-ddos-callback", &TestConfig::install_ddos_callback},
+    {"-fail-ddos-callback", &TestConfig::fail_ddos_callback},
+    {"-fail-cert-callback", &TestConfig::fail_cert_callback},
+    {"-handshake-never-done", &TestConfig::handshake_never_done},
+    {"-use-export-context", &TestConfig::use_export_context},
+    {"-tls-unique", &TestConfig::tls_unique},
+    {"-expect-ticket-renewal", &TestConfig::expect_ticket_renewal},
+    {"-expect-no-session", &TestConfig::expect_no_session},
+    {"-expect-ticket-supports-early-data",
+     &TestConfig::expect_ticket_supports_early_data},
+    {"-use-ticket-callback", &TestConfig::use_ticket_callback},
+    {"-renew-ticket", &TestConfig::renew_ticket},
+    {"-enable-early-data", &TestConfig::enable_early_data},
+    {"-check-close-notify", &TestConfig::check_close_notify},
+    {"-shim-shuts-down", &TestConfig::shim_shuts_down},
+    {"-verify-fail", &TestConfig::verify_fail},
+    {"-verify-peer", &TestConfig::verify_peer},
+    {"-verify-peer-if-no-obc", &TestConfig::verify_peer_if_no_obc},
+    {"-expect-verify-result", &TestConfig::expect_verify_result},
+    {"-renegotiate-once", &TestConfig::renegotiate_once},
+    {"-renegotiate-freely", &TestConfig::renegotiate_freely},
+    {"-renegotiate-ignore", &TestConfig::renegotiate_ignore},
+    {"-forbid-renegotiation-after-handshake",
+     &TestConfig::forbid_renegotiation_after_handshake},
+    {"-enable-all-curves", &TestConfig::enable_all_curves},
+    {"-use-old-client-cert-callback",
+     &TestConfig::use_old_client_cert_callback},
+    {"-send-alert", &TestConfig::send_alert},
+    {"-peek-then-read", &TestConfig::peek_then_read},
+    {"-enable-grease", &TestConfig::enable_grease},
+    {"-use-exporter-between-reads", &TestConfig::use_exporter_between_reads},
+    {"-retain-only-sha256-client-cert",
+     &TestConfig::retain_only_sha256_client_cert},
+    {"-expect-sha256-client-cert", &TestConfig::expect_sha256_client_cert},
+    {"-read-with-unfinished-write", &TestConfig::read_with_unfinished_write},
+    {"-expect-secure-renegotiation", &TestConfig::expect_secure_renegotiation},
+    {"-expect-no-secure-renegotiation",
+     &TestConfig::expect_no_secure_renegotiation},
+    {"-expect-session-id", &TestConfig::expect_session_id},
+    {"-expect-no-session-id", &TestConfig::expect_no_session_id},
+    {"-expect-accept-early-data", &TestConfig::expect_accept_early_data},
+    {"-expect-reject-early-data", &TestConfig::expect_reject_early_data},
+    {"-expect-no-offer-early-data", &TestConfig::expect_no_offer_early_data},
+    {"-no-op-extra-handshake", &TestConfig::no_op_extra_handshake},
+    {"-handshake-twice", &TestConfig::handshake_twice},
+    {"-allow-unknown-alpn-protos", &TestConfig::allow_unknown_alpn_protos},
+    {"-enable-ed25519", &TestConfig::enable_ed25519},
+    {"-use-custom-verify-callback", &TestConfig::use_custom_verify_callback},
+    {"-allow-false-start-without-alpn",
+     &TestConfig::allow_false_start_without_alpn},
+    {"-ignore-tls13-downgrade", &TestConfig::ignore_tls13_downgrade},
+    {"-expect-tls13-downgrade", &TestConfig::expect_tls13_downgrade},
+    {"-handoff", &TestConfig::handoff},
+    {"-no-rsa-pss-rsae-certs", &TestConfig::no_rsa_pss_rsae_certs},
+    {"-use-ocsp-callback", &TestConfig::use_ocsp_callback},
+    {"-set-ocsp-in-callback", &TestConfig::set_ocsp_in_callback},
+    {"-decline-ocsp-callback", &TestConfig::decline_ocsp_callback},
+    {"-fail-ocsp-callback", &TestConfig::fail_ocsp_callback},
+    {"-install-cert-compression-algs",
+     &TestConfig::install_cert_compression_algs},
+    {"-is-handshaker-supported", &TestConfig::is_handshaker_supported},
+    {"-handshaker-resume", &TestConfig::handshaker_resume},
+    {"-reverify-on-resume", &TestConfig::reverify_on_resume},
+    {"-enforce-rsa-key-usage", &TestConfig::enforce_rsa_key_usage},
+    {"-jdk11-workaround", &TestConfig::jdk11_workaround},
+    {"-server-preference", &TestConfig::server_preference},
+    {"-export-traffic-secrets", &TestConfig::export_traffic_secrets},
+    {"-key-update", &TestConfig::key_update},
+    {"-expect-delegated-credential-used",
+     &TestConfig::expect_delegated_credential_used},
+    {"-enable-pq-experiment-signal", &TestConfig::enable_pq_experiment_signal},
+    {"-expect-pq-experiment-signal", &TestConfig::expect_pq_experiment_signal},
 };
 
 const Flag<std::string> kStringFlags[] = {
-  { "-write-settings", &TestConfig::write_settings },
-  { "-key-file", &TestConfig::key_file },
-  { "-cert-file", &TestConfig::cert_file },
-  { "-expect-server-name", &TestConfig::expected_server_name },
-  { "-advertise-npn", &TestConfig::advertise_npn },
-  { "-expect-next-proto", &TestConfig::expected_next_proto },
-  { "-select-next-proto", &TestConfig::select_next_proto },
-  { "-send-channel-id", &TestConfig::send_channel_id },
-  { "-host-name", &TestConfig::host_name },
-  { "-advertise-alpn", &TestConfig::advertise_alpn },
-  { "-expect-alpn", &TestConfig::expected_alpn },
-  { "-expect-late-alpn", &TestConfig::expected_late_alpn },
-  { "-expect-advertised-alpn", &TestConfig::expected_advertised_alpn },
-  { "-select-alpn", &TestConfig::select_alpn },
-  { "-psk", &TestConfig::psk },
-  { "-psk-identity", &TestConfig::psk_identity },
-  { "-srtp-profiles", &TestConfig::srtp_profiles },
-  { "-cipher", &TestConfig::cipher },
-  { "-export-label", &TestConfig::export_label },
-  { "-export-context", &TestConfig::export_context },
-  { "-expect-peer-cert-file", &TestConfig::expect_peer_cert_file },
-  { "-use-client-ca-list", &TestConfig::use_client_ca_list },
-  { "-expect-client-ca-list", &TestConfig::expected_client_ca_list },
-  { "-expect-msg-callback", &TestConfig::expect_msg_callback },
-  { "-handshaker-path", &TestConfig::handshaker_path },
-  { "-delegated-credential", &TestConfig::delegated_credential },
+    {"-write-settings", &TestConfig::write_settings},
+    {"-key-file", &TestConfig::key_file},
+    {"-cert-file", &TestConfig::cert_file},
+    {"-expect-server-name", &TestConfig::expect_server_name},
+    {"-advertise-npn", &TestConfig::advertise_npn},
+    {"-expect-next-proto", &TestConfig::expect_next_proto},
+    {"-select-next-proto", &TestConfig::select_next_proto},
+    {"-send-channel-id", &TestConfig::send_channel_id},
+    {"-host-name", &TestConfig::host_name},
+    {"-advertise-alpn", &TestConfig::advertise_alpn},
+    {"-expect-alpn", &TestConfig::expect_alpn},
+    {"-expect-late-alpn", &TestConfig::expect_late_alpn},
+    {"-expect-advertised-alpn", &TestConfig::expect_advertised_alpn},
+    {"-select-alpn", &TestConfig::select_alpn},
+    {"-psk", &TestConfig::psk},
+    {"-psk-identity", &TestConfig::psk_identity},
+    {"-srtp-profiles", &TestConfig::srtp_profiles},
+    {"-cipher", &TestConfig::cipher},
+    {"-export-label", &TestConfig::export_label},
+    {"-export-context", &TestConfig::export_context},
+    {"-expect-peer-cert-file", &TestConfig::expect_peer_cert_file},
+    {"-use-client-ca-list", &TestConfig::use_client_ca_list},
+    {"-expect-client-ca-list", &TestConfig::expect_client_ca_list},
+    {"-expect-msg-callback", &TestConfig::expect_msg_callback},
+    {"-handshaker-path", &TestConfig::handshaker_path},
+    {"-delegated-credential", &TestConfig::delegated_credential},
+    {"-expect-early-data-reason", &TestConfig::expect_early_data_reason},
 };
 
 const Flag<std::string> kBase64Flags[] = {
-  { "-expect-certificate-types", &TestConfig::expected_certificate_types },
-  { "-expect-channel-id", &TestConfig::expected_channel_id },
-  { "-token-binding-params", &TestConfig::send_token_binding_params },
-  { "-expect-ocsp-response", &TestConfig::expected_ocsp_response },
-  { "-expect-signed-cert-timestamps",
-    &TestConfig::expected_signed_cert_timestamps },
-  { "-ocsp-response", &TestConfig::ocsp_response },
-  { "-signed-cert-timestamps", &TestConfig::signed_cert_timestamps },
-  { "-ticket-key", &TestConfig::ticket_key },
-  { "-quic-transport-params", &TestConfig::quic_transport_params },
-  { "-expected-quic-transport-params",
-    &TestConfig::expected_quic_transport_params },
+    {"-expect-certificate-types", &TestConfig::expect_certificate_types},
+    {"-expect-channel-id", &TestConfig::expect_channel_id},
+    {"-token-binding-params", &TestConfig::send_token_binding_params},
+    {"-expect-ocsp-response", &TestConfig::expect_ocsp_response},
+    {"-expect-signed-cert-timestamps",
+     &TestConfig::expect_signed_cert_timestamps},
+    {"-ocsp-response", &TestConfig::ocsp_response},
+    {"-signed-cert-timestamps", &TestConfig::signed_cert_timestamps},
+    {"-ticket-key", &TestConfig::ticket_key},
+    {"-quic-transport-params", &TestConfig::quic_transport_params},
+    {"-expect-quic-transport-params",
+     &TestConfig::expect_quic_transport_params},
 };
 
 const Flag<int> kIntFlags[] = {
-  { "-port", &TestConfig::port },
-  { "-resume-count", &TestConfig::resume_count },
-  { "-expected-token-binding-param",
-    &TestConfig::expected_token_binding_param },
-  { "-min-version", &TestConfig::min_version },
-  { "-max-version", &TestConfig::max_version },
-  { "-expect-version", &TestConfig::expect_version },
-  { "-mtu", &TestConfig::mtu },
-  { "-export-early-keying-material",
-    &TestConfig::export_early_keying_material },
-  { "-export-keying-material", &TestConfig::export_keying_material },
-  { "-expect-total-renegotiations", &TestConfig::expect_total_renegotiations },
-  { "-expect-peer-signature-algorithm",
-    &TestConfig::expect_peer_signature_algorithm },
-  { "-expect-curve-id", &TestConfig::expect_curve_id },
-  { "-initial-timeout-duration-ms", &TestConfig::initial_timeout_duration_ms },
-  { "-max-cert-list", &TestConfig::max_cert_list },
-  { "-expect-cipher-aes", &TestConfig::expect_cipher_aes },
-  { "-expect-cipher-no-aes", &TestConfig::expect_cipher_no_aes },
-  { "-resumption-delay", &TestConfig::resumption_delay },
-  { "-max-send-fragment", &TestConfig::max_send_fragment },
-  { "-read-size", &TestConfig::read_size },
-  { "-expect-ticket-age-skew", &TestConfig::expect_ticket_age_skew },
+    {"-port", &TestConfig::port},
+    {"-resume-count", &TestConfig::resume_count},
+    {"-expect-token-binding-param", &TestConfig::expect_token_binding_param},
+    {"-min-version", &TestConfig::min_version},
+    {"-max-version", &TestConfig::max_version},
+    {"-expect-version", &TestConfig::expect_version},
+    {"-mtu", &TestConfig::mtu},
+    {"-export-keying-material", &TestConfig::export_keying_material},
+    {"-expect-total-renegotiations", &TestConfig::expect_total_renegotiations},
+    {"-expect-peer-signature-algorithm",
+     &TestConfig::expect_peer_signature_algorithm},
+    {"-expect-curve-id", &TestConfig::expect_curve_id},
+    {"-initial-timeout-duration-ms", &TestConfig::initial_timeout_duration_ms},
+    {"-max-cert-list", &TestConfig::max_cert_list},
+    {"-expect-cipher-aes", &TestConfig::expect_cipher_aes},
+    {"-expect-cipher-no-aes", &TestConfig::expect_cipher_no_aes},
+    {"-resumption-delay", &TestConfig::resumption_delay},
+    {"-max-send-fragment", &TestConfig::max_send_fragment},
+    {"-read-size", &TestConfig::read_size},
+    {"-expect-ticket-age-skew", &TestConfig::expect_ticket_age_skew},
 };
 
 const Flag<std::vector<int>> kIntVectorFlags[] = {
     {"-signing-prefs", &TestConfig::signing_prefs},
     {"-verify-prefs", &TestConfig::verify_prefs},
-    {"-expect-peer-verify-pref", &TestConfig::expected_peer_verify_prefs},
+    {"-expect-peer-verify-pref", &TestConfig::expect_peer_verify_prefs},
     {"-curves", &TestConfig::curves},
 };
 
@@ -243,7 +243,7 @@
   if (string_field != NULL) {
     *i = *i + 1;
     if (*i >= argc) {
-      fprintf(stderr, "Missing parameter\n");
+      fprintf(stderr, "Missing parameter.\n");
       return false;
     }
     if (!skip) {
@@ -256,19 +256,19 @@
   if (base64_field != NULL) {
     *i = *i + 1;
     if (*i >= argc) {
-      fprintf(stderr, "Missing parameter\n");
+      fprintf(stderr, "Missing parameter.\n");
       return false;
     }
     size_t len;
     if (!EVP_DecodedLength(&len, strlen(argv[*i]))) {
-      fprintf(stderr, "Invalid base64: %s\n", argv[*i]);
+      fprintf(stderr, "Invalid base64: %s.\n", argv[*i]);
       return false;
     }
     std::unique_ptr<uint8_t[]> decoded(new uint8_t[len]);
     if (!EVP_DecodeBase64(decoded.get(), &len, len,
                           reinterpret_cast<const uint8_t *>(argv[*i]),
                           strlen(argv[*i]))) {
-      fprintf(stderr, "Invalid base64: %s\n", argv[*i]);
+      fprintf(stderr, "Invalid base64: %s.\n", argv[*i]);
       return false;
     }
     if (!skip) {
@@ -282,7 +282,7 @@
   if (int_field) {
     *i = *i + 1;
     if (*i >= argc) {
-      fprintf(stderr, "Missing parameter\n");
+      fprintf(stderr, "Missing parameter.\n");
       return false;
     }
     if (!skip) {
@@ -296,7 +296,7 @@
   if (int_vector_field) {
     *i = *i + 1;
     if (*i >= argc) {
-      fprintf(stderr, "Missing parameter\n");
+      fprintf(stderr, "Missing parameter.\n");
       return false;
     }
 
@@ -307,7 +307,7 @@
     return true;
   }
 
-  fprintf(stderr, "Unknown argument: %s\n", flag);
+  fprintf(stderr, "Unknown argument: %s.\n", flag);
   return false;
 }
 
@@ -403,9 +403,9 @@
   const TestConfig *config = GetTestConfig(ssl);
   const char *server_name = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
   if (server_name == nullptr ||
-      std::string(server_name) != config->expected_server_name) {
-    fprintf(stderr, "servername mismatch (got %s; want %s)\n", server_name,
-            config->expected_server_name.c_str());
+      std::string(server_name) != config->expect_server_name) {
+    fprintf(stderr, "servername mismatch (got %s; want %s).\n", server_name,
+            config->expect_server_name.c_str());
     return SSL_TLSEXT_ERR_ALERT_FATAL;
   }
 
@@ -449,7 +449,7 @@
   if (content_type == SSL3_RT_HEADER) {
     if (len !=
         (config->is_dtls ? DTLS1_RT_HEADER_LENGTH : SSL3_RT_HEADER_LENGTH)) {
-      fprintf(stderr, "Incorrect length for record header: %zu\n", len);
+      fprintf(stderr, "Incorrect length for record header: %zu.\n", len);
       state->msg_callback_ok = false;
     }
     return;
@@ -459,7 +459,7 @@
   switch (content_type) {
     case 0:
       if (version != SSL2_VERSION) {
-        fprintf(stderr, "Incorrect version for V2ClientHello: %x\n", version);
+        fprintf(stderr, "Incorrect version for V2ClientHello: %x.\n", version);
         state->msg_callback_ok = false;
         return;
       }
@@ -509,7 +509,7 @@
       return;
 
     default:
-      fprintf(stderr, "Invalid content_type: %d\n", content_type);
+      fprintf(stderr, "Invalid content_type: %d.\n", content_type);
       state->msg_callback_ok = false;
   }
 }
@@ -618,11 +618,11 @@
     return SSL_TLSEXT_ERR_NOACK;
   }
 
-  if (!config->expected_advertised_alpn.empty() &&
-      (config->expected_advertised_alpn.size() != inlen ||
-       OPENSSL_memcmp(config->expected_advertised_alpn.data(), in, inlen) !=
+  if (!config->expect_advertised_alpn.empty() &&
+      (config->expect_advertised_alpn.size() != inlen ||
+       OPENSSL_memcmp(config->expect_advertised_alpn.data(), in, inlen) !=
            0)) {
-    fprintf(stderr, "bad ALPN select callback inputs\n");
+    fprintf(stderr, "bad ALPN select callback inputs.\n");
     exit(1);
   }
 
@@ -634,12 +634,12 @@
 
 static bool CheckVerifyCallback(SSL *ssl) {
   const TestConfig *config = GetTestConfig(ssl);
-  if (!config->expected_ocsp_response.empty()) {
+  if (!config->expect_ocsp_response.empty()) {
     const uint8_t *data;
     size_t len;
     SSL_get0_ocsp_response(ssl, &data, &len);
     if (len == 0) {
-      fprintf(stderr, "OCSP response not available in verify callback\n");
+      fprintf(stderr, "OCSP response not available in verify callback.\n");
       return false;
     }
   }
@@ -808,7 +808,7 @@
   for (const auto &part : parts) {
     std::string binary;
     if (!HexDecode(&binary, part)) {
-      fprintf(stderr, "Bad hex string: %s\n", part.c_str());
+      fprintf(stderr, "Bad hex string: %s.\n", part.c_str());
       return ret;
     }
 
@@ -847,22 +847,22 @@
 
 static bool CheckPeerVerifyPrefs(SSL *ssl) {
   const TestConfig *config = GetTestConfig(ssl);
-  if (!config->expected_peer_verify_prefs.empty()) {
+  if (!config->expect_peer_verify_prefs.empty()) {
     const uint16_t *peer_sigalgs;
     size_t num_peer_sigalgs =
         SSL_get0_peer_verify_algorithms(ssl, &peer_sigalgs);
-    if (config->expected_peer_verify_prefs.size() != num_peer_sigalgs) {
+    if (config->expect_peer_verify_prefs.size() != num_peer_sigalgs) {
       fprintf(stderr,
               "peer verify preferences length mismatch (got %zu, wanted %zu)\n",
-              num_peer_sigalgs, config->expected_peer_verify_prefs.size());
+              num_peer_sigalgs, config->expect_peer_verify_prefs.size());
       return false;
     }
     for (size_t i = 0; i < num_peer_sigalgs; i++) {
       if (static_cast<int>(peer_sigalgs[i]) !=
-          config->expected_peer_verify_prefs[i]) {
+          config->expect_peer_verify_prefs[i]) {
         fprintf(stderr,
                 "peer verify preference %zu mismatch (got %04x, wanted %04x\n",
-                i, peer_sigalgs[i], config->expected_peer_verify_prefs[i]);
+                i, peer_sigalgs[i], config->expect_peer_verify_prefs[i]);
         return false;
       }
     }
@@ -877,29 +877,29 @@
     return false;
   }
 
-  if (!config->expected_certificate_types.empty()) {
+  if (!config->expect_certificate_types.empty()) {
     const uint8_t *certificate_types;
     size_t certificate_types_len =
         SSL_get0_certificate_types(ssl, &certificate_types);
-    if (certificate_types_len != config->expected_certificate_types.size() ||
+    if (certificate_types_len != config->expect_certificate_types.size() ||
         OPENSSL_memcmp(certificate_types,
-                       config->expected_certificate_types.data(),
+                       config->expect_certificate_types.data(),
                        certificate_types_len) != 0) {
-      fprintf(stderr, "certificate types mismatch\n");
+      fprintf(stderr, "certificate types mismatch.\n");
       return false;
     }
   }
 
-  if (!config->expected_client_ca_list.empty()) {
+  if (!config->expect_client_ca_list.empty()) {
     bssl::UniquePtr<STACK_OF(X509_NAME)> expected =
-        DecodeHexX509Names(config->expected_client_ca_list);
+        DecodeHexX509Names(config->expect_client_ca_list);
     const size_t num_expected = sk_X509_NAME_num(expected.get());
 
     const STACK_OF(X509_NAME) *received = SSL_get_client_CA_list(ssl);
     const size_t num_received = sk_X509_NAME_num(received);
 
     if (num_received != num_expected) {
-      fprintf(stderr, "expected %u names in CertificateRequest but got %u\n",
+      fprintf(stderr, "expected %u names in CertificateRequest but got %u.\n",
               static_cast<unsigned>(num_expected),
               static_cast<unsigned>(num_received));
       return false;
@@ -908,7 +908,7 @@
     for (size_t i = 0; i < num_received; i++) {
       if (X509_NAME_cmp(sk_X509_NAME_value(received, i),
                         sk_X509_NAME_value(expected.get(), i)) != 0) {
-        fprintf(stderr, "names in CertificateRequest differ at index #%d\n",
+        fprintf(stderr, "names in CertificateRequest differ at index #%d.\n",
                 static_cast<unsigned>(i));
         return false;
       }
@@ -1099,35 +1099,16 @@
   const TestConfig *config = GetTestConfig(client_hello->ssl);
   GetTestState(client_hello->ssl)->early_callback_called = true;
 
-  if (!config->expected_server_name.empty()) {
-    const uint8_t *extension_data;
-    size_t extension_len;
-    CBS extension, server_name_list, host_name;
-    uint8_t name_type;
-
-    if (!SSL_early_callback_ctx_extension_get(
-            client_hello, TLSEXT_TYPE_server_name, &extension_data,
-            &extension_len)) {
-      fprintf(stderr, "Could not find server_name extension.\n");
+  if (!config->expect_server_name.empty()) {
+    const char *server_name =
+        SSL_get_servername(client_hello->ssl, TLSEXT_NAMETYPE_host_name);
+    if (server_name == nullptr ||
+        std::string(server_name) != config->expect_server_name) {
+      fprintf(stderr,
+              "Server name mismatch in early callback (got %s; want %s).\n",
+              server_name, config->expect_server_name.c_str());
       return ssl_select_cert_error;
     }
-
-    CBS_init(&extension, extension_data, extension_len);
-    if (!CBS_get_u16_length_prefixed(&extension, &server_name_list) ||
-        CBS_len(&extension) != 0 ||
-        !CBS_get_u8(&server_name_list, &name_type) ||
-        name_type != TLSEXT_NAMETYPE_host_name ||
-        !CBS_get_u16_length_prefixed(&server_name_list, &host_name) ||
-        CBS_len(&server_name_list) != 0) {
-      fprintf(stderr, "Could not decode server_name extension.\n");
-      return ssl_select_cert_error;
-    }
-
-    if (!CBS_mem_equal(&host_name,
-                       (const uint8_t *)config->expected_server_name.data(),
-                       config->expected_server_name.size())) {
-      fprintf(stderr, "Server name mismatch.\n");
-    }
   }
 
   if (config->fail_early_callback) {
@@ -1240,7 +1221,7 @@
     SSL_CTX_set_grease_enabled(ssl_ctx.get(), 1);
   }
 
-  if (!expected_server_name.empty()) {
+  if (!expect_server_name.empty()) {
     SSL_CTX_set_tlsext_servername_callback(ssl_ctx.get(), ServerNameCallback);
   }
 
@@ -1344,6 +1325,10 @@
     SSL_CTX_set_options(ssl_ctx.get(), SSL_OP_CIPHER_SERVER_PREFERENCE);
   }
 
+  if (enable_pq_experiment_signal) {
+    SSL_CTX_enable_pq_experiment_signal(ssl_ctx.get());
+  }
+
   return ssl_ctx;
 }
 
@@ -1371,7 +1356,7 @@
   // Account for the trailing '\0' for the identity.
   if (config->psk_identity.size() >= max_identity_len ||
       config->psk.size() > max_psk_len) {
-    fprintf(stderr, "PSK buffers too small\n");
+    fprintf(stderr, "PSK buffers too small.\n");
     return 0;
   }
 
@@ -1390,7 +1375,7 @@
   }
 
   if (config->psk.size() > max_psk_len) {
-    fprintf(stderr, "PSK buffers too small\n");
+    fprintf(stderr, "PSK buffers too small.\n");
     return 0;
   }
 
@@ -1520,7 +1505,7 @@
   if (no_ticket) {
     SSL_set_options(ssl.get(), SSL_OP_NO_TICKET);
   }
-  if (!expected_channel_id.empty() || enable_channel_id) {
+  if (!expect_channel_id.empty() || enable_channel_id) {
     SSL_set_tls_channel_id_enabled(ssl.get(), 1);
   }
   if (!send_channel_id.empty()) {
@@ -1622,6 +1607,9 @@
         case SSL_CURVE_CECPQ2:
           nids.push_back(NID_CECPQ2);
           break;
+        case SSL_CURVE_CECPQ2b:
+          nids.push_back(NID_CECPQ2b);
+          break;
       }
       if (!SSL_set1_curves(ssl.get(), &nids[0], nids.size())) {
         return nullptr;
@@ -1630,8 +1618,8 @@
   }
   if (enable_all_curves) {
     static const int kAllCurves[] = {
-        NID_secp224r1, NID_X9_62_prime256v1, NID_secp384r1,
-        NID_secp521r1, NID_X25519,           NID_CECPQ2,
+        NID_secp224r1, NID_X9_62_prime256v1, NID_secp384r1, NID_secp521r1,
+        NID_X25519,    NID_CECPQ2,           NID_CECPQ2b,
     };
     if (!SSL_set1_curves(ssl.get(), kAllCurves,
                          OPENSSL_ARRAY_SIZE(kAllCurves))) {
@@ -1678,7 +1666,8 @@
   if (!delegated_credential.empty()) {
     std::string::size_type comma = delegated_credential.find(',');
     if (comma == std::string::npos) {
-      fprintf(stderr, "failed to find comma in delegated credential argument");
+      fprintf(stderr,
+              "failed to find comma in delegated credential argument.\n");
       return nullptr;
     }
 
@@ -1686,7 +1675,7 @@
     const std::string pkcs8_hex = delegated_credential.substr(comma + 1);
     std::string dc, pkcs8;
     if (!HexDecode(&dc, dc_hex) || !HexDecode(&pkcs8, pkcs8_hex)) {
-      fprintf(stderr, "failed to hex decode delegated credential argument");
+      fprintf(stderr, "failed to hex decode delegated credential argument.\n");
       return nullptr;
     }
 
@@ -1697,7 +1686,7 @@
 
     bssl::UniquePtr<EVP_PKEY> priv(EVP_parse_private_key(&pkcs8_cbs));
     if (!priv) {
-      fprintf(stderr, "failed to parse delegated credential private key");
+      fprintf(stderr, "failed to parse delegated credential private key.\n");
       return nullptr;
     }
 
diff --git a/src/ssl/test/test_config.h b/src/ssl/test/test_config.h
index 9221d6f..ce4b416 100644
--- a/src/ssl/test/test_config.h
+++ b/src/ssl/test/test_config.h
@@ -32,15 +32,15 @@
   bool fallback_scsv = false;
   std::vector<int> signing_prefs;
   std::vector<int> verify_prefs;
-  std::vector<int> expected_peer_verify_prefs;
+  std::vector<int> expect_peer_verify_prefs;
   std::vector<int> curves;
   std::string key_file;
   std::string cert_file;
-  std::string expected_server_name;
-  std::string expected_certificate_types;
+  std::string expect_server_name;
+  std::string expect_certificate_types;
   bool require_any_client_certificate = false;
   std::string advertise_npn;
-  std::string expected_next_proto;
+  std::string expect_next_proto;
   bool false_start = false;
   std::string select_next_proto;
   bool async = false;
@@ -52,31 +52,31 @@
   bool no_tls11 = false;
   bool no_tls1 = false;
   bool no_ticket = false;
-  std::string expected_channel_id;
+  std::string expect_channel_id;
   bool enable_channel_id = false;
   std::string send_channel_id;
-  int expected_token_binding_param = -1;
+  int expect_token_binding_param = -1;
   std::string send_token_binding_params;
   bool shim_writes_first = false;
   std::string host_name;
   std::string advertise_alpn;
-  std::string expected_alpn;
-  std::string expected_late_alpn;
-  std::string expected_advertised_alpn;
+  std::string expect_alpn;
+  std::string expect_late_alpn;
+  std::string expect_advertised_alpn;
   std::string select_alpn;
   bool decline_alpn = false;
   bool select_empty_alpn = false;
   std::string quic_transport_params;
-  std::string expected_quic_transport_params;
+  std::string expect_quic_transport_params;
   bool expect_session_miss = false;
   bool expect_extended_master_secret = false;
   std::string psk;
   std::string psk_identity;
   std::string srtp_profiles;
   bool enable_ocsp_stapling = false;
-  std::string expected_ocsp_response;
+  std::string expect_ocsp_response;
   bool enable_signed_cert_timestamps = false;
-  std::string expected_signed_cert_timestamps;
+  std::string expect_signed_cert_timestamps;
   int min_version = 0;
   int max_version = 0;
   int expect_version = 0;
@@ -89,7 +89,6 @@
   bool fail_cert_callback = false;
   std::string cipher;
   bool handshake_never_done = false;
-  int export_early_keying_material = 0;
   int export_keying_material = 0;
   std::string export_label;
   std::string export_context;
@@ -127,7 +126,7 @@
   bool use_old_client_cert_callback = false;
   int initial_timeout_duration_ms = 0;
   std::string use_client_ca_list;
-  std::string expected_client_ca_list;
+  std::string expect_client_ca_list;
   bool send_alert = false;
   bool peek_then_read = false;
   bool enable_grease = false;
@@ -173,7 +172,11 @@
   bool server_preference = false;
   bool export_traffic_secrets = false;
   bool key_update = false;
+  bool expect_delegated_credential_used = false;
   std::string delegated_credential;
+  std::string expect_early_data_reason;
+  bool enable_pq_experiment_signal = false;
+  bool expect_pq_experiment_signal = false;
 
   int argc;
   char **argv;
diff --git a/src/ssl/tls13_both.cc b/src/ssl/tls13_both.cc
index ba5719f..1a49e4c 100644
--- a/src/ssl/tls13_both.cc
+++ b/src/ssl/tls13_both.cc
@@ -370,13 +370,8 @@
     return false;
   }
 
-  bool sig_ok = ssl_public_key_verify(ssl, signature, signature_algorithm,
-                                      hs->peer_pubkey.get(), input);
-#if defined(BORINGSSL_UNSAFE_FUZZER_MODE)
-  sig_ok = true;
-  ERR_clear_error();
-#endif
-  if (!sig_ok) {
+  if (!ssl_public_key_verify(ssl, signature, signature_algorithm,
+                             hs->peer_pubkey.get(), input)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_SIGNATURE);
     ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECRYPT_ERROR);
     return false;
@@ -488,15 +483,16 @@
 
   if (ssl_signing_with_dc(hs)) {
     const CRYPTO_BUFFER *raw = dc->raw.get();
+    CBB child;
     if (!CBB_add_u16(&extensions, TLSEXT_TYPE_delegated_credential) ||
-        !CBB_add_u16(&extensions, CRYPTO_BUFFER_len(raw)) ||
-        !CBB_add_bytes(&extensions,
-                       CRYPTO_BUFFER_data(raw),
+        !CBB_add_u16_length_prefixed(&extensions, &child) ||
+        !CBB_add_bytes(&child, CRYPTO_BUFFER_data(raw),
                        CRYPTO_BUFFER_len(raw)) ||
         !CBB_flush(&extensions)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
       return 0;
     }
+    ssl->s3->delegated_credential_used = true;
   }
 
   for (size_t i = 1; i < sk_CRYPTO_BUFFER_num(cert->chain.get()); i++) {
diff --git a/src/ssl/tls13_client.cc b/src/ssl/tls13_client.cc
index ac97165..f411e19 100644
--- a/src/ssl/tls13_client.cc
+++ b/src/ssl/tls13_client.cc
@@ -188,6 +188,7 @@
   hs->tls13_state = state_send_second_client_hello;
   // 0-RTT is rejected if we receive a HelloRetryRequest.
   if (hs->in_early_data) {
+    ssl->s3->early_data_reason = ssl_early_data_hello_retry_request;
     return ssl_hs_early_data_rejected;
   }
   return ssl_hs_ok;
@@ -900,7 +901,7 @@
     return false;
   }
 
-  if (have_early_data_info && ssl->enable_early_data) {
+  if (have_early_data_info) {
     if (!CBS_get_u32(&early_data_info, &session->ticket_max_early_data) ||
         CBS_len(&early_data_info) != 0) {
       ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
diff --git a/src/ssl/tls13_enc.cc b/src/ssl/tls13_enc.cc
index 7353561..b6a402f 100644
--- a/src/ssl/tls13_enc.cc
+++ b/src/ssl/tls13_enc.cc
@@ -38,6 +38,7 @@
     return false;
   }
 
+  assert(hs->transcript.DigestLen() <= SSL_MAX_MD_SIZE);
   hs->hash_len = hs->transcript.DigestLen();
 
   // Initialize the secret to the zero key.
@@ -215,7 +216,6 @@
 
 
 static const char kTLS13LabelExporter[] = "exp master";
-static const char kTLS13LabelEarlyExporter[] = "e exp master";
 
 static const char kTLS13LabelClientEarlyTraffic[] = "c e traffic";
 static const char kTLS13LabelClientHandshakeTraffic[] = "c hs traffic";
@@ -229,13 +229,9 @@
                      kTLS13LabelClientEarlyTraffic,
                      strlen(kTLS13LabelClientEarlyTraffic)) ||
       !ssl_log_secret(ssl, "CLIENT_EARLY_TRAFFIC_SECRET",
-                      hs->early_traffic_secret, hs->hash_len) ||
-      !derive_secret(hs, ssl->s3->early_exporter_secret, hs->hash_len,
-                     kTLS13LabelEarlyExporter,
-                     strlen(kTLS13LabelEarlyExporter))) {
+                      hs->early_traffic_secret, hs->hash_len)) {
     return false;
   }
-  ssl->s3->early_exporter_secret_len = hs->hash_len;
 
   if (ssl->quic_method != nullptr) {
     if (ssl->server) {
diff --git a/src/ssl/tls13_server.cc b/src/ssl/tls13_server.cc
index caaf0c7..4ebea5b 100644
--- a/src/ssl/tls13_server.cc
+++ b/src/ssl/tls13_server.cc
@@ -53,6 +53,12 @@
 
 static const uint8_t kZeroes[EVP_MAX_MD_SIZE] = {0};
 
+// Allow a minute of ticket age skew in either direction. This covers
+// transmission delays in ClientHello and NewSessionTicket, as well as
+// drift between client and server clock rate since the ticket was issued.
+// See RFC 8446, section 8.3.
+static const int32_t kMaxTicketAgeSkewSeconds = 60;
+
 static int resolve_ecdhe_secret(SSL_HANDSHAKE *hs, bool *out_need_retry,
                                 SSL_CLIENT_HELLO *client_hello) {
   SSL *const ssl = hs->ssl;
@@ -97,77 +103,15 @@
   return 1;
 }
 
-// CipherScorer produces a "score" for each possible cipher suite offered by
-// the client.
-class CipherScorer {
- public:
-  CipherScorer(uint16_t group_id)
-      : aes_is_fine_(EVP_has_aes_hardware()),
-        security_128_is_fine_(group_id != SSL_CURVE_CECPQ2) {}
-
-  typedef std::tuple<bool, bool, bool> Score;
-
-  // MinScore returns a |Score| that will compare less than the score of all
-  // cipher suites.
-  Score MinScore() const {
-    return Score(false, false, false);
-  }
-
-  Score Evaluate(const SSL_CIPHER *a) const {
-    return Score(
-        // Something is always preferable to nothing.
-        true,
-        // Either 128-bit is fine, or 256-bit is preferred.
-        security_128_is_fine_ || a->algorithm_enc != SSL_AES128GCM,
-        // Either AES is fine, or else ChaCha20 is preferred.
-        aes_is_fine_ || a->algorithm_enc == SSL_CHACHA20POLY1305);
-  }
-
- private:
-  const bool aes_is_fine_;
-  const bool security_128_is_fine_;
-};
-
 static const SSL_CIPHER *choose_tls13_cipher(
     const SSL *ssl, const SSL_CLIENT_HELLO *client_hello, uint16_t group_id) {
-  if (client_hello->cipher_suites_len % 2 != 0) {
-    return nullptr;
-  }
-
   CBS cipher_suites;
   CBS_init(&cipher_suites, client_hello->cipher_suites,
            client_hello->cipher_suites_len);
 
   const uint16_t version = ssl_protocol_version(ssl);
 
-  const SSL_CIPHER *best = nullptr;
-  CipherScorer scorer(group_id);
-  CipherScorer::Score best_score = scorer.MinScore();
-
-  while (CBS_len(&cipher_suites) > 0) {
-    uint16_t cipher_suite;
-    if (!CBS_get_u16(&cipher_suites, &cipher_suite)) {
-      return nullptr;
-    }
-
-    // Limit to TLS 1.3 ciphers we know about.
-    const SSL_CIPHER *candidate = SSL_get_cipher_by_value(cipher_suite);
-    if (candidate == nullptr ||
-        SSL_CIPHER_get_min_version(candidate) > version ||
-        SSL_CIPHER_get_max_version(candidate) < version) {
-      continue;
-    }
-
-    const CipherScorer::Score candidate_score = scorer.Evaluate(candidate);
-    // |candidate_score| must be larger to displace the current choice. That way
-    // the client's order controls between ciphers with an equal score.
-    if (candidate_score > best_score) {
-      best = candidate;
-      best_score = candidate_score;
-    }
-  }
-
-  return best;
+  return ssl_choose_tls13_cipher(cipher_suites, version, group_id);
 }
 
 static bool add_new_session_tickets(SSL_HANDSHAKE *hs, bool *out_sent_tickets) {
@@ -307,16 +251,15 @@
 
 static enum ssl_ticket_aead_result_t select_session(
     SSL_HANDSHAKE *hs, uint8_t *out_alert, UniquePtr<SSL_SESSION> *out_session,
-    int32_t *out_ticket_age_skew, const SSLMessage &msg,
-    const SSL_CLIENT_HELLO *client_hello) {
+    int32_t *out_ticket_age_skew, bool *out_offered_ticket,
+    const SSLMessage &msg, const SSL_CLIENT_HELLO *client_hello) {
   SSL *const ssl = hs->ssl;
-  *out_session = NULL;
+  *out_session = nullptr;
 
-  // Decode the ticket if we agreed on a PSK key exchange mode.
   CBS pre_shared_key;
-  if (!hs->accept_psk_mode ||
-      !ssl_client_hello_get_extension(client_hello, &pre_shared_key,
-                                      TLSEXT_TYPE_pre_shared_key)) {
+  *out_offered_ticket = ssl_client_hello_get_extension(
+      client_hello, &pre_shared_key, TLSEXT_TYPE_pre_shared_key);
+  if (!*out_offered_ticket) {
     return ssl_ticket_aead_ignore_ticket;
   }
 
@@ -337,6 +280,11 @@
     return ssl_ticket_aead_error;
   }
 
+  // If the peer did not offer psk_dhe, ignore the resumption.
+  if (!hs->accept_psk_mode) {
+    return ssl_ticket_aead_ignore_ticket;
+  }
+
   // TLS 1.3 session tickets are renewed separately as part of the
   // NewSessionTicket.
   bool unused_renew;
@@ -406,10 +354,18 @@
 
   uint8_t alert = SSL_AD_DECODE_ERROR;
   UniquePtr<SSL_SESSION> session;
-  switch (select_session(hs, &alert, &session, &ssl->s3->ticket_age_skew, msg,
-                         &client_hello)) {
+  bool offered_ticket = false;
+  switch (select_session(hs, &alert, &session, &ssl->s3->ticket_age_skew,
+                         &offered_ticket, msg, &client_hello)) {
     case ssl_ticket_aead_ignore_ticket:
       assert(!session);
+      if (!ssl->enable_early_data) {
+        ssl->s3->early_data_reason = ssl_early_data_disabled;
+      } else if (!offered_ticket) {
+        ssl->s3->early_data_reason = ssl_early_data_no_session_offered;
+      } else {
+        ssl->s3->early_data_reason = ssl_early_data_session_not_resumed;
+      }
       if (!ssl_get_new_session(hs, 1 /* server */)) {
         ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
         return ssl_hs_error;
@@ -421,26 +377,34 @@
       // a fresh session.
       hs->new_session =
           SSL_SESSION_dup(session.get(), SSL_SESSION_DUP_AUTH_ONLY);
-
-      if (ssl->enable_early_data &&
-          // Early data must be acceptable for this ticket.
-          session->ticket_max_early_data != 0 &&
-          // The client must have offered early data.
-          hs->early_data_offered &&
-          // Channel ID is incompatible with 0-RTT.
-          !ssl->s3->channel_id_valid &&
-          // If Token Binding is negotiated, reject 0-RTT.
-          !ssl->s3->token_binding_negotiated &&
-          // The negotiated ALPN must match the one in the ticket.
-          MakeConstSpan(ssl->s3->alpn_selected) == session->early_alpn) {
-        ssl->s3->early_data_accepted = true;
-      }
-
-      if (hs->new_session == NULL) {
+      if (hs->new_session == nullptr) {
         ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
         return ssl_hs_error;
       }
 
+      if (!ssl->enable_early_data) {
+        ssl->s3->early_data_reason = ssl_early_data_disabled;
+      } else if (session->ticket_max_early_data == 0) {
+        ssl->s3->early_data_reason = ssl_early_data_unsupported_for_session;
+      } else if (!hs->early_data_offered) {
+        ssl->s3->early_data_reason = ssl_early_data_peer_declined;
+      } else if (ssl->s3->channel_id_valid) {
+          // Channel ID is incompatible with 0-RTT.
+        ssl->s3->early_data_reason = ssl_early_data_channel_id;
+      } else if (ssl->s3->token_binding_negotiated) {
+          // Token Binding is incompatible with 0-RTT.
+        ssl->s3->early_data_reason = ssl_early_data_token_binding;
+      } else if (MakeConstSpan(ssl->s3->alpn_selected) != session->early_alpn) {
+        // The negotiated ALPN must match the one in the ticket.
+        ssl->s3->early_data_reason = ssl_early_data_alpn_mismatch;
+      } else if (ssl->s3->ticket_age_skew < -kMaxTicketAgeSkewSeconds ||
+                 kMaxTicketAgeSkewSeconds < ssl->s3->ticket_age_skew) {
+        ssl->s3->early_data_reason = ssl_early_data_ticket_age_skew;
+      } else {
+        ssl->s3->early_data_reason = ssl_early_data_accepted;
+        ssl->s3->early_data_accepted = true;
+      }
+
       ssl->s3->session_reused = true;
 
       // Resumption incorporates fresh key material, so refresh the timeout.
@@ -499,7 +463,10 @@
   bool need_retry;
   if (!resolve_ecdhe_secret(hs, &need_retry, &client_hello)) {
     if (need_retry) {
-      ssl->s3->early_data_accepted = false;
+      if (ssl->s3->early_data_accepted) {
+        ssl->s3->early_data_reason = ssl_early_data_hello_retry_request;
+        ssl->s3->early_data_accepted = false;
+      }
       ssl->s3->skip_early_data = true;
       ssl->method->next_message(ssl);
       if (!hs->transcript.UpdateForHelloRetryRequest()) {
@@ -950,7 +917,15 @@
   }
 
   hs->tls13_state = state_done;
-  return sent_tickets ? ssl_hs_flush : ssl_hs_ok;
+  // In TLS 1.3, the NewSessionTicket isn't flushed until the server performs a
+  // write, to prevent a non-reading client from causing the server to hang in
+  // the case of a small server write buffer. Consumers which don't write data
+  // to the client will need to do a zero-byte write if they wish to flush the
+  // tickets.
+  if (hs->ssl->ctx->quic_method != nullptr && sent_tickets) {
+    return ssl_hs_flush;
+  }
+  return ssl_hs_ok;
 }
 
 enum ssl_hs_wait_t tls13_server_handshake(SSL_HANDSHAKE *hs) {
diff --git a/src/ssl/tls_method.cc b/src/ssl/tls_method.cc
index bc9410b..95fac4d 100644
--- a/src/ssl/tls_method.cc
+++ b/src/ssl/tls_method.cc
@@ -125,9 +125,9 @@
     ssl3_set_write_state,
 };
 
-static int ssl_noop_x509_check_client_CA_names(
+static bool ssl_noop_x509_check_client_CA_names(
     STACK_OF(CRYPTO_BUFFER) *names) {
-  return 1;
+  return true;
 }
 
 static void ssl_noop_x509_clear(CERT *cert) {}
@@ -135,29 +135,29 @@
 static void ssl_noop_x509_dup(CERT *new_cert, const CERT *cert) {}
 static void ssl_noop_x509_flush_cached_leaf(CERT *cert) {}
 static void ssl_noop_x509_flush_cached_chain(CERT *cert) {}
-static int ssl_noop_x509_session_cache_objects(SSL_SESSION *sess) {
-  return 1;
+static bool ssl_noop_x509_session_cache_objects(SSL_SESSION *sess) {
+  return true;
 }
-static int ssl_noop_x509_session_dup(SSL_SESSION *new_session,
-                                       const SSL_SESSION *session) {
-  return 1;
+static bool ssl_noop_x509_session_dup(SSL_SESSION *new_session,
+                                      const SSL_SESSION *session) {
+  return true;
 }
 static void ssl_noop_x509_session_clear(SSL_SESSION *session) {}
-static int ssl_noop_x509_session_verify_cert_chain(SSL_SESSION *session,
-                                                   SSL_HANDSHAKE *hs,
-                                                   uint8_t *out_alert) {
-  return 0;
+static bool ssl_noop_x509_session_verify_cert_chain(SSL_SESSION *session,
+                                                    SSL_HANDSHAKE *hs,
+                                                    uint8_t *out_alert) {
+  return false;
 }
 
 static void ssl_noop_x509_hs_flush_cached_ca_names(SSL_HANDSHAKE *hs) {}
-static int ssl_noop_x509_ssl_new(SSL_HANDSHAKE *hs) { return 1; }
+static bool ssl_noop_x509_ssl_new(SSL_HANDSHAKE *hs) { return true; }
 static void ssl_noop_x509_ssl_config_free(SSL_CONFIG *cfg) {}
 static void ssl_noop_x509_ssl_flush_cached_client_CA(SSL_CONFIG *cfg) {}
-static int ssl_noop_x509_ssl_auto_chain_if_needed(SSL_HANDSHAKE *hs) {
-  return 1;
+static bool ssl_noop_x509_ssl_auto_chain_if_needed(SSL_HANDSHAKE *hs) {
+  return true;
 }
-static int ssl_noop_x509_ssl_ctx_new(SSL_CTX *ctx) { return 1; }
-static void ssl_noop_x509_ssl_ctx_free(SSL_CTX *ctx) { }
+static bool ssl_noop_x509_ssl_ctx_new(SSL_CTX *ctx) { return true; }
+static void ssl_noop_x509_ssl_ctx_free(SSL_CTX *ctx) {}
 static void ssl_noop_x509_ssl_ctx_flush_cached_client_CA(SSL_CTX *ctx) {}
 
 const SSL_X509_METHOD ssl_noop_x509_method = {