diff --git a/src/feature/dirauth/dirvote.c b/src/feature/dirauth/dirvote.c
index f8290cde6d3cf097a90a7d3a50ed3777b8423a45..1386d75e0fcb3b692d28659595964bbb31354506 100644
--- a/src/feature/dirauth/dirvote.c
+++ b/src/feature/dirauth/dirvote.c
@@ -3870,8 +3870,12 @@ dirvote_create_microdescriptor(const routerinfo_t *ri, int consensus_method)
   size_t keylen;
   smartlist_t *chunks = smartlist_new();
   char *output = NULL;
-  crypto_pk_t *rsa_pubkey = router_get_rsa_onion_pkey(ri->onion_pkey,
-                                                      ri->onion_pkey_len);
+  crypto_pk_t *rsa_pubkey = router_get_rsa_onion_pkey(ri->tap_onion_pkey,
+                                                      ri->tap_onion_pkey_len);
+  if (!rsa_pubkey) {
+    /* We do not yet support creating MDs for relays without TAP onion keys. */
+    goto done;
+  }
 
   if (crypto_pk_write_public_key_to_string(rsa_pubkey, &key, &keylen)<0)
     goto done;
diff --git a/src/feature/dirparse/routerparse.c b/src/feature/dirparse/routerparse.c
index 844057c47e26755052d2bba1aa04edcbd76292be..47f6803fcd53e9bf5fc0bf4d71339fb2c29d7301 100644
--- a/src/feature/dirparse/routerparse.c
+++ b/src/feature/dirparse/routerparse.c
@@ -601,8 +601,8 @@ router_parse_entry_from_string(const char *s, const char *end,
              "Relay's onion key had invalid exponent.");
     goto err;
   }
-  router->onion_pkey = tor_memdup(tok->object_body, tok->object_size);
-  router->onion_pkey_len = tok->object_size;
+  router->tap_onion_pkey = tor_memdup(tok->object_body, tok->object_size);
+  router->tap_onion_pkey_len = tok->object_size;
   crypto_pk_free(tok->key);
 
   if ((tok = find_opt_by_keyword(tokens, K_ONION_KEY_NTOR))) {
@@ -776,8 +776,12 @@ router_parse_entry_from_string(const char *s, const char *end,
         goto err;
       }
 
-      rsa_pubkey = router_get_rsa_onion_pkey(router->onion_pkey,
-                                             router->onion_pkey_len);
+      rsa_pubkey = router_get_rsa_onion_pkey(router->tap_onion_pkey,
+                                             router->tap_onion_pkey_len);
+      if (rsa_pubkey == NULL) {
+        log_warn(LD_DIR, "No pubkey for TAP cross-verification.");
+        goto err;
+      }
       if (check_tap_onion_key_crosscert(
                       (const uint8_t*)cc_tap_tok->object_body,
                       (int)cc_tap_tok->object_size,
diff --git a/src/feature/nodelist/routerinfo_st.h b/src/feature/nodelist/routerinfo_st.h
index 50134b2b965f1fe2b44ce16de14b59104e62e5ca..a5c00c85c51605cf24fdec63c43a56e56c21d227 100644
--- a/src/feature/nodelist/routerinfo_st.h
+++ b/src/feature/nodelist/routerinfo_st.h
@@ -33,10 +33,13 @@ struct routerinfo_t {
   /**
    * Public RSA TAP key for onions, ASN.1 encoded.  We store this
    * in its encoded format since storing it as a crypto_pk_t uses
-   * significantly more memory. */
-  char *onion_pkey;
+   * significantly more memory.
+   *
+   * This may be absent.
+   */
+  char *tap_onion_pkey;
   /** Length of onion_pkey, in bytes. */
-  size_t onion_pkey_len;
+  size_t tap_onion_pkey_len;
 
   crypto_pk_t *identity_pkey;  /**< Public RSA key for signing. */
   /** Public curve25519 key for onions */
diff --git a/src/feature/nodelist/routerlist.c b/src/feature/nodelist/routerlist.c
index 63de68dda79230b95b41c7fa0f1a504d171f10d7..7904f7d03237a2664037e2e59087aa77449f8c72 100644
--- a/src/feature/nodelist/routerlist.c
+++ b/src/feature/nodelist/routerlist.c
@@ -930,8 +930,8 @@ routerinfo_free_(routerinfo_t *router)
   tor_free(router->platform);
   tor_free(router->protocol_list);
   tor_free(router->contact_info);
-  if (router->onion_pkey)
-    tor_free(router->onion_pkey);
+  if (router->tap_onion_pkey)
+    tor_free(router->tap_onion_pkey);
   tor_free(router->onion_curve25519_pkey);
   if (router->identity_pkey)
     crypto_pk_free(router->identity_pkey);
@@ -2957,6 +2957,24 @@ router_reset_descriptor_download_failures(void)
 /** We allow uptime to vary from how much it ought to be by this much. */
 #define ROUTER_ALLOW_UPTIME_DRIFT (6*60*60)
 
+/** Return true iff r1 and r2 have the same TAP onion keys. */
+static int
+router_tap_onion_keys_eq(const routerinfo_t *r1, const routerinfo_t *r2)
+{
+  if (r1->tap_onion_pkey_len != r2->tap_onion_pkey_len)
+    return 0;
+
+  if ((r1->tap_onion_pkey == NULL) && (r2->tap_onion_pkey == NULL)) {
+    return 1;
+  } else if ((r1->tap_onion_pkey != NULL) && (r2->tap_onion_pkey != NULL)) {
+    return tor_memeq(r1->tap_onion_pkey, r2->tap_onion_pkey,
+                     r1->tap_onion_pkey_len);
+  } else {
+    /* One is NULL; one is not. */
+    return 0;
+  }
+}
+
 /** Return true iff the only differences between r1 and r2 are such that
  * would not cause a recent (post 0.1.1.6) dirserver to republish.
  */
@@ -2982,8 +3000,7 @@ router_differences_are_cosmetic(const routerinfo_t *r1, const routerinfo_t *r2)
       r1->ipv6_orport != r2->ipv6_orport ||
       r1->ipv4_dirport != r2->ipv4_dirport ||
       r1->purpose != r2->purpose ||
-      r1->onion_pkey_len != r2->onion_pkey_len ||
-      !tor_memeq(r1->onion_pkey, r2->onion_pkey, r1->onion_pkey_len) ||
+      !router_tap_onion_keys_eq(r1,r2) ||
       !crypto_pk_eq_keys(r1->identity_pkey, r2->identity_pkey) ||
       strcasecmp(r1->platform, r2->platform) ||
       (r1->contact_info && !r2->contact_info) || /* contact_info is optional */
diff --git a/src/feature/relay/router.c b/src/feature/relay/router.c
index 1ed9630e09cd47a63867b4d645205b91f2cb05f3..1a29b54494d5ac4fd4b5a9d6b93bd7d4d6ed50ac 100644
--- a/src/feature/relay/router.c
+++ b/src/feature/relay/router.c
@@ -2139,8 +2139,8 @@ router_build_fresh_unsigned_routerinfo,(routerinfo_t **ri_out))
     directory_permits_begindir_requests(options);
   ri->cache_info.published_on = time(NULL);
   /* get_onion_key() must invoke from main thread */
-  router_set_rsa_onion_pkey(get_onion_key(), &ri->onion_pkey,
-                            &ri->onion_pkey_len);
+  router_set_rsa_onion_pkey(get_onion_key(), &ri->tap_onion_pkey,
+                            &ri->tap_onion_pkey_len);
 
   ri->onion_curve25519_pkey =
     tor_memdup(&get_current_curve25519_keypair()->pubkey,
@@ -2777,7 +2777,7 @@ router_dump_router_to_string(routerinfo_t *router,
   char published[ISO_TIME_LEN+1];
   char fingerprint[FINGERPRINT_LEN+1];
   char *extra_info_line = NULL;
-  size_t onion_pkeylen, identity_pkeylen;
+  size_t onion_pkeylen=0, identity_pkeylen;
   char *family_line = NULL;
   char *extra_or_address = NULL;
   const or_options_t *options = get_options();
@@ -2835,12 +2835,14 @@ router_dump_router_to_string(routerinfo_t *router,
   }
 
   /* PEM-encode the onion key */
-  rsa_pubkey = router_get_rsa_onion_pkey(router->onion_pkey,
-                                         router->onion_pkey_len);
-  if (crypto_pk_write_public_key_to_string(rsa_pubkey,
-                                           &onion_pkey,&onion_pkeylen)<0) {
-    log_warn(LD_BUG,"write onion_pkey to string failed!");
-    goto err;
+  rsa_pubkey = router_get_rsa_onion_pkey(router->tap_onion_pkey,
+                                         router->tap_onion_pkey_len);
+  if (rsa_pubkey) {
+    if (crypto_pk_write_public_key_to_string(rsa_pubkey,
+                                             &onion_pkey,&onion_pkeylen)<0) {
+      log_warn(LD_BUG,"write onion_pkey to string failed!");
+      goto err;
+    }
   }
 
   /* PEM-encode the identity key */
@@ -2851,7 +2853,7 @@ router_dump_router_to_string(routerinfo_t *router,
   }
 
   /* Cross-certify with RSA key */
-  if (tap_key && router->cache_info.signing_key_cert &&
+  if (tap_key && rsa_pubkey && router->cache_info.signing_key_cert &&
       router->cache_info.signing_key_cert->signing_key_included) {
     char buf[256];
     int tap_cc_len = 0;
@@ -2976,7 +2978,7 @@ router_dump_router_to_string(routerinfo_t *router,
                     "uptime %ld\n"
                     "bandwidth %d %d %d\n"
                     "%s%s"
-                    "onion-key\n%s"
+                    "%s%s"
                     "signing-key\n%s"
                     "%s%s"
                     "%s%s%s",
@@ -2997,7 +2999,8 @@ router_dump_router_to_string(routerinfo_t *router,
     extra_info_line ? extra_info_line : "",
     (options->DownloadExtraInfo || options->V3AuthoritativeDir) ?
                          "caches-extra-info\n" : "",
-    onion_pkey, identity_pkey,
+    onion_pkey?"onion-key\n":"", onion_pkey?onion_pkey:"",
+    identity_pkey,
     rsa_tap_cc_line ? rsa_tap_cc_line : "",
     ntor_cc_line ? ntor_cc_line : "",
     family_line,
diff --git a/src/test/test_dir.c b/src/test/test_dir.c
index a86638f8c9165b8b14a399933d00647ba42b65ba..9dd530fc77e2d5a9b6eddaa424fa1d576e96e324 100644
--- a/src/test/test_dir.c
+++ b/src/test/test_dir.c
@@ -217,7 +217,7 @@ basic_routerinfo_new(const char *nickname, uint32_t ipv4_addr,
   r1->ipv4_dirport = dir_port;
   r1->supports_tunnelled_dir_requests = 1;
 
-  router_set_rsa_onion_pkey(pk1, &r1->onion_pkey, &r1->onion_pkey_len);
+  router_set_rsa_onion_pkey(pk1, &r1->tap_onion_pkey, &r1->tap_onion_pkey_len);
   r1->identity_pkey = pk2;
 
   r1->bandwidthrate = bandwidthrate;
@@ -382,8 +382,8 @@ get_new_onion_key_block(const routerinfo_t *r1)
 {
   char *block = NULL;
   tor_assert(r1);
-  crypto_pk_t *pk_tmp = router_get_rsa_onion_pkey(r1->onion_pkey,
-                                                  r1->onion_pkey_len);
+  crypto_pk_t *pk_tmp = router_get_rsa_onion_pkey(r1->tap_onion_pkey,
+                                                  r1->tap_onion_pkey_len);
   block = get_new_rsa_key_block("onion-key", pk_tmp);
   crypto_pk_free(pk_tmp);
   return block;
@@ -587,8 +587,8 @@ setup_mocks_for_fresh_descriptor(const routerinfo_t *r1,
   if (rsa_onion_keypair) {
     mocked_onionkey = crypto_pk_dup_key(rsa_onion_keypair);
   } else {
-    mocked_onionkey = router_get_rsa_onion_pkey(r1->onion_pkey,
-                                                r1->onion_pkey_len);
+    mocked_onionkey = router_get_rsa_onion_pkey(r1->tap_onion_pkey,
+                                                r1->tap_onion_pkey_len);
   }
   MOCK(get_onion_key, mock_get_onion_key);
 }
@@ -643,10 +643,12 @@ STMT_BEGIN \
   tt_int_op(rp1->bandwidthrate,OP_EQ, r1->bandwidthrate); \
   tt_int_op(rp1->bandwidthburst,OP_EQ, r1->bandwidthburst); \
   tt_int_op(rp1->bandwidthcapacity,OP_EQ, r1->bandwidthcapacity); \
-  crypto_pk_t *rp1_onion_pkey = router_get_rsa_onion_pkey(rp1->onion_pkey, \
-                                                      rp1->onion_pkey_len); \
-  crypto_pk_t *r1_onion_pkey = router_get_rsa_onion_pkey(r1->onion_pkey, \
-                                                      r1->onion_pkey_len); \
+  crypto_pk_t *rp1_onion_pkey = router_get_rsa_onion_pkey( \
+                                                    rp1->tap_onion_pkey, \
+                                                    rp1->tap_onion_pkey_len); \
+  crypto_pk_t *r1_onion_pkey = router_get_rsa_onion_pkey( \
+                                                    r1->tap_onion_pkey, \
+                                                    r1->tap_onion_pkey_len); \
   tt_int_op(crypto_pk_cmp_keys(rp1_onion_pkey, r1_onion_pkey), OP_EQ, 0); \
   crypto_pk_free(rp1_onion_pkey); \
   crypto_pk_free(r1_onion_pkey); \
diff --git a/src/test/test_hs_service.c b/src/test/test_hs_service.c
index dc60c7ca2967adc1069308b982b6a4bcf8b14a6e..6f254f16e8fa6fabb97686b6a45ac61e560ac93d 100644
--- a/src/test/test_hs_service.c
+++ b/src/test/test_hs_service.c
@@ -1605,7 +1605,6 @@ test_build_update_descriptors(void *arg)
     tt_int_op(ret, OP_EQ, 0);
     ri.onion_curve25519_pkey =
       tor_malloc_zero(sizeof(curve25519_public_key_t));
-    ri.onion_pkey = tor_malloc_zero(140);
     curve25519_public_key_generate(ri.onion_curve25519_pkey,
                                    &curve25519_secret_key);
     memset(ri.cache_info.identity_digest, 'A', DIGEST_LEN);
@@ -1631,7 +1630,6 @@ test_build_update_descriptors(void *arg)
   update_all_descriptors_intro_points(now);
   tor_free(node->ri->onion_curve25519_pkey); /* Avoid memleak. */
   tor_free(node->ri->cache_info.signing_key_cert);
-  tor_free(node->ri->onion_pkey);
   expect_log_msg_containing("just picked 1 intro points and wanted 3 for next "
                             "descriptor. It currently has 0 intro points. "
                             "Launching ESTABLISH_INTRO circuit shortly.");
diff --git a/src/test/test_router.c b/src/test/test_router.c
index 47084bba012167796e5466ed4366e3b10eb3b895..64efedfa466481502525ab6d342cbd1d0d8d4121 100644
--- a/src/test/test_router.c
+++ b/src/test/test_router.c
@@ -60,8 +60,8 @@ rtr_tests_gen_routerinfo(crypto_pk_t *ident_key, crypto_pk_t *tap_key)
   mock_routerinfo->identity_pkey = crypto_pk_dup_key(ident_key);
   mock_routerinfo->protocol_list =
     tor_strdup("Cons=1-2 Desc=1-2 DirCache=1-2");
-  router_set_rsa_onion_pkey(tap_key, &mock_routerinfo->onion_pkey,
-                            &mock_routerinfo->onion_pkey_len);
+  router_set_rsa_onion_pkey(tap_key, &mock_routerinfo->tap_onion_pkey,
+                            &mock_routerinfo->tap_onion_pkey_len);
   mock_routerinfo->bandwidthrate = 9001;
   mock_routerinfo->bandwidthburst = 9002;