reflect the fact that the supported set of HPKE cipher-suites can be different between ECHConfigs
diff --git a/include/picotls.h b/include/picotls.h
index 641fd2d..ecd4620 100644
--- a/include/picotls.h
+++ b/include/picotls.h
@@ -728,8 +728,8 @@
  * corresponding private key, invokes `ptls_hpke_setup_base_r` with provided `cipher`, `enc`, and `info_prefix` (which will be
  * "tls ech" || 00).
  */
-PTLS_CALLBACK_TYPE(ptls_aead_context_t *, ech_create_opener, ptls_hpke_kem_t **kem, ptls_t *tls, uint8_t config_id,
-                   ptls_hpke_cipher_suite_t *cipher, ptls_iovec_t enc, ptls_iovec_t info_prefix);
+PTLS_CALLBACK_TYPE(ptls_aead_context_t *, ech_create_opener, ptls_hpke_kem_t **kem, ptls_hpke_cipher_suite_t **cipher, ptls_t *tls,
+                   uint8_t config_id, ptls_hpke_cipher_suite_id_t cipher_id, ptls_iovec_t enc, ptls_iovec_t info_prefix);
 
 /**
  * the configuration
@@ -762,22 +762,26 @@
      * ECH
      */
     struct {
-        /**
-         * list of HPKE symmetric cipher-suites (set to NULL to disable ECH altogether)
-         */
-        ptls_hpke_cipher_suite_t **ciphers;
-        /**
-         * client-only: KEMs being supported
-         */
-        ptls_hpke_kem_t **kems;
-        /**
-         * server-only: callback that does ECDH key exchange and returns the AEAD context
-         */
-        ptls_ech_create_opener_t *create_opener;
-        /**
-         * ECHConfigList to be sent to the client when there is mismatch (or when the client sends a grease)
-         */
-        ptls_iovec_t retry_configs;
+        struct {
+            /**
+             * list of HPKE symmetric cipher-suites (set to NULL to disable ECH altogether)
+             */
+            ptls_hpke_cipher_suite_t **ciphers;
+            /**
+             * KEMs being supported
+             */
+            ptls_hpke_kem_t **kems;
+        } client;
+        struct {
+            /**
+             * callback that does ECDH key exchange and returns the AEAD context
+             */
+            ptls_ech_create_opener_t *create_opener;
+            /**
+             * ECHConfigList to be sent to the client when there is mismatch (or when the client sends a grease)
+             */
+            ptls_iovec_t retry_configs;
+        } server;
     } ech;
     /**
      *
diff --git a/lib/picotls.c b/lib/picotls.c
index ad0c40f..9a0d983 100644
--- a/lib/picotls.c
+++ b/lib/picotls.c
@@ -374,7 +374,7 @@
     struct {
         uint8_t type;
         uint8_t config_id;
-        ptls_hpke_cipher_suite_t *cipher;
+        ptls_hpke_cipher_suite_id_t cipher_suite;
         ptls_iovec_t enc;
         ptls_iovec_t payload;
     } ech;
@@ -1091,7 +1091,7 @@
     return ret;
 }
 
-static int decode_ech_config_list(ptls_context_t *ctx, struct st_decoded_ech_config_t *decoded, ptls_iovec_t config_list)
+static int client_decode_ech_config_list(ptls_context_t *ctx, struct st_decoded_ech_config_t *decoded, ptls_iovec_t config_list)
 {
     const uint8_t *src = config_list.base, *const end = src + config_list.len;
     int match_found = 0, ret;
@@ -1108,7 +1108,7 @@
                 /* If the block is the one that we recognize, parse it, then adopt if if possible. Otherwise, skip. */
                 if (version == PTLS_ECH_CONFIG_VERSION) {
                     struct st_decoded_ech_config_t thisconf;
-                    if ((ret = decode_one_ech_config(ctx->ech.kems, ctx->ech.ciphers, &thisconf, &src, end)) != 0)
+                    if ((ret = decode_one_ech_config(ctx->ech.client.kems, ctx->ech.client.ciphers, &thisconf, &src, end)) != 0)
                         goto Exit;
                     if (!match_found && thisconf.kem != NULL && thisconf.cipher != NULL) {
                         *decoded = thisconf;
@@ -2265,10 +2265,10 @@
 
     if (properties != NULL) {
         /* try to use ECH (ignore broken ECHConfigList; it is delivered insecurely) */
-        if (!is_second_flight && sni_name != NULL && tls->ctx->ech.ciphers != NULL && tls->ctx->ech.kems != NULL &&
+        if (!is_second_flight && sni_name != NULL && tls->ctx->ech.client.ciphers != NULL &&
             properties->client.ech.configs.len != 0) {
             struct st_decoded_ech_config_t decoded;
-            decode_ech_config_list(tls->ctx, &decoded, properties->client.ech.configs);
+            client_decode_ech_config_list(tls->ctx, &decoded, properties->client.ech.configs);
             if (decoded.kem != NULL && decoded.cipher != NULL) {
                 if ((ret = client_setup_ech(&tls->ech, &decoded, tls->ctx->random_bytes)) != 0)
                     goto Exit;
@@ -2843,7 +2843,7 @@
             }
             /* parse retry_config, and if it is applicable, provide that to the application */
             struct st_decoded_ech_config_t decoded;
-            if ((ret = decode_ech_config_list(tls->ctx, &decoded, ptls_iovec_init(src, end - src))) != 0)
+            if ((ret = client_decode_ech_config_list(tls->ctx, &decoded, ptls_iovec_init(src, end - src))) != 0)
                 goto Exit;
             if (decoded.kem != NULL && decoded.cipher != NULL && properties != NULL &&
                 properties->client.ech.retry_configs != NULL) {
@@ -3686,19 +3686,10 @@
             if ((ret = ptls_decode8(&ch->ech.type, &src, end)) != 0)
                 goto Exit;
             switch (ch->ech.type) {
-            case PTLS_ECH_CLIENT_HELLO_TYPE_OUTER: {
-                ptls_hpke_cipher_suite_id_t cipher_id;
-                if ((ret = ptls_decode16(&cipher_id.kdf, &src, end)) != 0 || (ret = ptls_decode16(&cipher_id.aead, &src, end)) != 0)
+            case PTLS_ECH_CLIENT_HELLO_TYPE_OUTER:
+                if ((ret = ptls_decode16(&ch->ech.cipher_suite.kdf, &src, end)) != 0 ||
+                    (ret = ptls_decode16(&ch->ech.cipher_suite.aead, &src, end)) != 0)
                     goto Exit;
-                /* find corresponding cipher-suite; if not found, the field is left NULL */
-                if (ctx->ech.ciphers != NULL) {
-                    for (size_t i = 0; ctx->ech.ciphers[i] != NULL; ++i) {
-                        if (ctx->ech.ciphers[i]->id.kdf == cipher_id.kdf && ctx->ech.ciphers[i]->id.aead == cipher_id.aead) {
-                            ch->ech.cipher = ctx->ech.ciphers[i];
-                            break;
-                        }
-                    }
-                }
                 if ((ret = ptls_decode8(&ch->ech.config_id, &src, end)) != 0)
                     goto Exit;
                 ptls_decode_open_block(src, end, 2, {
@@ -3713,7 +3704,7 @@
                     ch->ech.payload = ptls_iovec_init(src, end - src);
                     src = end;
                 });
-            } break;
+                break;
             case PTLS_ECH_CLIENT_HELLO_TYPE_INNER:
                 if (src != end) {
                     ret = PTLS_ALERT_DECODE_ERROR;
@@ -4187,7 +4178,8 @@
                 ret = PTLS_ALERT_MISSING_EXTENSION;
                 goto Exit;
             }
-            if (!(ch->ech.config_id == tls->ech.config_id && ch->ech.cipher == tls->ech.cipher && ch->ech.enc.len == 0)) {
+            if (!(ch->ech.config_id == tls->ech.config_id && ch->ech.cipher_suite.kdf == tls->ech.cipher->id.kdf &&
+                  ch->ech.cipher_suite.aead == tls->ech.cipher->id.aead && ch->ech.enc.len == 0)) {
                 ret = PTLS_ALERT_ILLEGAL_PARAMETER;
                 goto Exit;
             }
@@ -4203,14 +4195,11 @@
         if (!is_second_flight)
             tls->ech.offered = 1;
         /* obtain AEAD context for opening inner CH */
-        if (!is_second_flight && ch->ech.cipher != NULL && ch->ech.payload.len > ch->ech.cipher->aead->tag_size &&
-            tls->ctx->ech.create_opener != NULL) {
-            if ((tls->ech.aead = tls->ctx->ech.create_opener->cb(
-                     tls->ctx->ech.create_opener, &tls->ech.kem, tls, ch->ech.config_id, ch->ech.cipher, ch->ech.enc,
-                     ptls_iovec_init(ech_info_prefix, sizeof(ech_info_prefix)))) != NULL) {
+        if (!is_second_flight && ch->ech.payload.base != NULL && tls->ctx->ech.server.create_opener != NULL) {
+            if ((tls->ech.aead = tls->ctx->ech.server.create_opener->cb(
+                     tls->ctx->ech.server.create_opener, &tls->ech.kem, &tls->ech.cipher, tls, ch->ech.config_id,
+                     ch->ech.cipher_suite, ch->ech.enc, ptls_iovec_init(ech_info_prefix, sizeof(ech_info_prefix)))) != NULL)
                 tls->ech.config_id = ch->ech.config_id;
-                tls->ech.cipher = ch->ech.cipher;
-            }
         }
         if (tls->ech.aead != NULL) {
             /* now that AEAD context is available, create AAD and decrypt inner CH */
@@ -4591,10 +4580,10 @@
             if (tls->pending_handshake_secret != NULL)
                 buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_EARLY_DATA, {});
             /* send ECH retry_configs, if ECH was offered by rejected, even though we (the server) could have accepted ECH */
-            if (tls->ech.offered && !ptls_is_ech_handshake(tls, NULL, NULL) && tls->ctx->ech.ciphers != NULL &&
-                tls->ctx->ech.create_opener != NULL && tls->ctx->ech.retry_configs.len != 0)
+            if (tls->ech.offered && !ptls_is_ech_handshake(tls, NULL, NULL) && tls->ctx->ech.server.create_opener != NULL &&
+                tls->ctx->ech.server.retry_configs.len != 0)
                 buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_ENCRYPTED_CLIENT_HELLO, {
-                    ptls_buffer_pushv(sendbuf, tls->ctx->ech.retry_configs.base, tls->ctx->ech.retry_configs.len);
+                    ptls_buffer_pushv(sendbuf, tls->ctx->ech.server.retry_configs.base, tls->ctx->ech.server.retry_configs.len);
                 });
             if ((ret = push_additional_extensions(properties, sendbuf)) != 0)
                 goto Exit;
diff --git a/t/cli.c b/t/cli.c
index 9ae3cd9..c696bda 100644
--- a/t/cli.c
+++ b/t/cli.c
@@ -78,38 +78,57 @@
     return NULL;
 }
 
-static ptls_aead_context_t *create_ech_opener(ptls_ech_create_opener_t *self, ptls_hpke_kem_t **kem, ptls_t *tls, uint8_t config_id,
-                                              ptls_hpke_cipher_suite_t *cipher, ptls_iovec_t enc, ptls_iovec_t info_prefix)
+static ptls_aead_context_t *create_ech_opener(ptls_ech_create_opener_t *self, ptls_hpke_kem_t **kem,
+                                              ptls_hpke_cipher_suite_t **cipher, ptls_t *tls, uint8_t config_id,
+                                              ptls_hpke_cipher_suite_id_t cipher_id, ptls_iovec_t enc, ptls_iovec_t info_prefix)
 {
     const uint8_t *src = ech.config_list.base, *const end = src + ech.config_list.len;
     size_t index = 0;
-    int ret;
+    int ret = 0;
+
+    /* look for the cipher implementation; this should better be specific to each ECHConfig (as each of them may advertise different
+     * set of values) */
+    *cipher = NULL;
+    for (size_t i = 0; ptls_openssl_hpke_cipher_suites[i] != NULL; ++i) {
+        if (ptls_openssl_hpke_cipher_suites[i]->id.kdf == cipher_id.kdf &&
+            ptls_openssl_hpke_cipher_suites[i]->id.aead == cipher_id.aead) {
+            *cipher = ptls_openssl_hpke_cipher_suites[i];
+            break;
+        }
+    }
+    if (*cipher == NULL)
+        goto Exit;
 
     ptls_decode_open_block(src, end, 2, {
         uint16_t version;
         if ((ret = ptls_decode16(&version, &src, end)) != 0)
             goto Exit;
-        ptls_decode_open_block(src, end, 2, {
-            if (src == end)
-                goto Exit;
-            if (*src == config_id) {
-                /* this is the ECHConfig that we have been looking for */
-                if (index >= ech.keyex.count) {
-                    fprintf(stderr, "ECH key missing for config %zu\n", index);
-                    return NULL;
+        do {
+            ptls_decode_open_block(src, end, 2, {
+                if (src == end) {
+                    ret = PTLS_ALERT_DECODE_ERROR;
+                    goto Exit;
                 }
-                uint8_t *info = malloc(info_prefix.len + end - (src - 4));
-                memcpy(info, info_prefix.base, info_prefix.len);
-                memcpy(info + info_prefix.len, src - 4, end - (src - 4));
-                ptls_aead_context_t *aead;
-                ptls_hpke_setup_base_r(ech.keyex.list[index].kem, cipher, ech.keyex.list[index].ctx, &aead, enc,
-                                       ptls_iovec_init(info, info_prefix.len + end - (src - 4)));
-                free(info);
-                *kem = ech.keyex.list[index].kem;
-                return aead;
-            }
-            ++index;
-        });
+                if (*src == config_id) {
+                    /* this is the ECHConfig that we have been looking for */
+                    if (index >= ech.keyex.count) {
+                        fprintf(stderr, "ECH key missing for config %zu\n", index);
+                        return NULL;
+                    }
+                    uint8_t *info = malloc(info_prefix.len + end - (src - 4));
+                    memcpy(info, info_prefix.base, info_prefix.len);
+                    memcpy(info + info_prefix.len, src - 4, end - (src - 4));
+                    ptls_aead_context_t *aead;
+                    ptls_hpke_setup_base_r(ech.keyex.list[index].kem, *cipher, ech.keyex.list[index].ctx, &aead, enc,
+                                           ptls_iovec_init(info, info_prefix.len + end - (src - 4)));
+                    free(info);
+                    *kem = ech.keyex.list[index].kem;
+                    return aead;
+                }
+                ++index;
+                src = end;
+            });
+        } while (src != end);
     });
 
 Exit:
@@ -490,7 +509,7 @@
         .get_time = &ptls_get_time,
         .key_exchanges = key_exchanges,
         .cipher_suites = cipher_suites,
-        .ech = {ptls_openssl_hpke_cipher_suites, ptls_openssl_hpke_kems, NULL /* activated by -K option */},
+        .ech = {.client = {ptls_openssl_hpke_cipher_suites, ptls_openssl_hpke_kems}, .server = {NULL /* activated by -K option */}},
     };
     ptls_handshake_properties_t hsprop = {{{{NULL}}}};
     const char *host, *port, *input_file = NULL;
@@ -591,7 +610,7 @@
             ++ech.keyex.count;
             EVP_PKEY_free(pkey);
             fclose(fp);
-            ctx.ech.create_opener = &ech_opener;
+            ctx.ech.server.create_opener = &ech_opener;
         } break;
         case 'l':
             setup_log_event(&ctx, optarg);
diff --git a/t/openssl.c b/t/openssl.c
index 73ae997..8e79b15 100644
--- a/t/openssl.c
+++ b/t/openssl.c
@@ -335,8 +335,9 @@
     test_hpke(ptls_openssl_hpke_kems, ptls_openssl_hpke_cipher_suites);
 }
 
-static ptls_aead_context_t *create_ech_opener(ptls_ech_create_opener_t *self, ptls_hpke_kem_t **kem, ptls_t *tls, uint8_t config_id,
-                                              ptls_hpke_cipher_suite_t *cipher, ptls_iovec_t enc, ptls_iovec_t info_prefix)
+static ptls_aead_context_t *create_ech_opener(ptls_ech_create_opener_t *self, ptls_hpke_kem_t **kem,
+                                              ptls_hpke_cipher_suite_t **cipher, ptls_t *tls, uint8_t config_id,
+                                              ptls_hpke_cipher_suite_id_t cipher_id, ptls_iovec_t enc, ptls_iovec_t info_prefix)
 {
     static ptls_key_exchange_context_t *pem = NULL;
     if (pem == NULL) {
@@ -344,6 +345,17 @@
         assert(pem != NULL);
     }
 
+    *cipher = NULL;
+    for (size_t i = 0; ptls_openssl_hpke_cipher_suites[i] != NULL; ++i) {
+        if (ptls_openssl_hpke_cipher_suites[i]->id.kdf == cipher_id.kdf &&
+            ptls_openssl_hpke_cipher_suites[i]->id.aead == cipher_id.aead) {
+            *cipher = ptls_openssl_hpke_cipher_suites[i];
+            break;
+        }
+    }
+    if (*cipher == NULL)
+        return NULL;
+
     ptls_aead_context_t *aead = NULL;
     ptls_buffer_t infobuf;
     int ret;
@@ -352,7 +364,7 @@
     ptls_buffer_pushv(&infobuf, info_prefix.base, info_prefix.len);
     ptls_buffer_pushv(&infobuf, (const uint8_t *)ECH_CONFIG_LIST + 2,
                       sizeof(ECH_CONFIG_LIST) - 3); /* choose the only ECHConfig from the list */
-    ret = ptls_hpke_setup_base_r(&ptls_openssl_hpke_kem_p256sha256, cipher, pem, &aead, enc,
+    ret = ptls_hpke_setup_base_r(&ptls_openssl_hpke_kem_p256sha256, *cipher, pem, &aead, enc,
                                  ptls_iovec_init(infobuf.base, infobuf.off));
 
 Exit:
@@ -544,10 +556,9 @@
                                   .cipher_suites = ptls_openssl_cipher_suites,
                                   .tls12_cipher_suites = ptls_openssl_tls12_cipher_suites,
                                   .certificates = {&cert, 1},
-                                  .ech = {.ciphers = ptls_openssl_hpke_cipher_suites,
-                                          .kems = ptls_openssl_hpke_kems,
-                                          .create_opener = &ech_create_opener,
-                                          .retry_configs = {(uint8_t *)ECH_CONFIG_LIST, sizeof(ECH_CONFIG_LIST) - 1}},
+                                  .ech = {.client = {.ciphers = ptls_openssl_hpke_cipher_suites, .kems = ptls_openssl_hpke_kems},
+                                          .server = {.create_opener = &ech_create_opener,
+                                                     .retry_configs = {(uint8_t *)ECH_CONFIG_LIST, sizeof(ECH_CONFIG_LIST) - 1}}},
                                   .sign_certificate = &openssl_sign_certificate.super};
     assert(openssl_ctx.cipher_suites[0]->hash->digest_size == 48); /* sha384 */
     ptls_context_t openssl_ctx_sha256only = openssl_ctx;
diff --git a/t/picotls.c b/t/picotls.c
index 96fd3f9..58d9b81 100644
--- a/t/picotls.c
+++ b/t/picotls.c
@@ -733,11 +733,11 @@
 
 static int can_ech(ptls_context_t *ctx, int is_server)
 {
-    if (ctx->ech.ciphers == NULL)
-        return 0;
-    if (is_server && ctx->ech.create_opener == NULL)
-        return 0;
-    return 1;
+    if (is_server) {
+        return ctx->ech.server.create_opener != NULL;
+    } else {
+        return ctx->ech.client.ciphers != NULL;
+    }
 }
 
 static void test_handshake(ptls_iovec_t ticket, int mode, int expect_ticket, int check_ch, int require_client_authentication)
@@ -1747,22 +1747,23 @@
     }
 
     struct {
-        ptls_hpke_cipher_suite_t **server, **client;
-    } orig_ech_ciphers = {ctx_peer->ech.ciphers, ctx->ech.ciphers};
+        ptls_ech_create_opener_t *create_opener;
+        ptls_hpke_cipher_suite_t **client_ciphers;
+    } orig_ech = {ctx_peer->ech.server.create_opener, ctx->ech.client.ciphers};
 
     /* first run tests wo. ECH */
-    ctx_peer->ech.ciphers = NULL;
-    ctx->ech.ciphers = NULL;
+    ctx_peer->ech.server.create_opener = NULL;
+    ctx->ech.client.ciphers = NULL;
     subtest("no-ech", test_all_handshakes_core);
-    ctx_peer->ech.ciphers = orig_ech_ciphers.server;
-    ctx->ech.ciphers = orig_ech_ciphers.client;
+    ctx_peer->ech.server.create_opener = orig_ech.create_opener;
+    ctx->ech.client.ciphers = orig_ech.client_ciphers;
 
     if (can_ech(ctx_peer, 1) && can_ech(ctx, 0)) {
         subtest("ech", test_all_handshakes_core);
         if (ctx != ctx_peer) {
-            ctx->ech.ciphers = NULL;
+            ctx->ech.client.ciphers = NULL;
             subtest("ech (server-only)", test_all_handshakes_core);
-            ctx->ech.ciphers = orig_ech_ciphers.client;
+            ctx->ech.client.ciphers = orig_ech.client_ciphers;
         }
         subtest("ech-config-mismatch", test_ech_config_mismatch);
     }