diff --git a/Cargo.lock b/Cargo.lock
index 28b1cf261..a4957d866 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -63,6 +63,12 @@ dependencies = [
  "memchr",
 ]
 
+[[package]]
+name = "allocator-api2"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
+
 [[package]]
 name = "android-tzdata"
 version = "0.1.1"
@@ -133,6 +139,12 @@ version = "1.0.94"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7"
 
+[[package]]
+name = "arc-swap"
+version = "1.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
+
 [[package]]
 name = "arrayref"
 version = "0.3.9"
@@ -374,18 +386,18 @@ checksum = "597bb81c80a54b6a4381b23faba8d7774b144c94cbd1d6fe3f1329bd776554ab"
 
 [[package]]
 name = "bit-set"
-version = "0.5.3"
+version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
+checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
 dependencies = [
  "bit-vec",
 ]
 
 [[package]]
 name = "bit-vec"
-version = "0.6.3"
+version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
+checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
 
 [[package]]
 name = "bitflags"
@@ -468,9 +480,9 @@ dependencies = [
 
 [[package]]
 name = "cc"
-version = "1.2.3"
+version = "1.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d"
+checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf"
 dependencies = [
  "shlex",
 ]
@@ -603,15 +615,15 @@ dependencies = [
 
 [[package]]
 name = "console"
-version = "0.15.8"
+version = "0.15.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb"
+checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b"
 dependencies = [
  "encode_unicode",
- "lazy_static",
  "libc",
- "unicode-width 0.1.14",
- "windows-sys 0.52.0",
+ "once_cell",
+ "unicode-width",
+ "windows-sys 0.59.0",
 ]
 
 [[package]]
@@ -684,9 +696,9 @@ checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
 
 [[package]]
 name = "crossbeam-channel"
-version = "0.5.13"
+version = "0.5.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2"
+checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471"
 dependencies = [
  "crossbeam-utils",
 ]
@@ -702,9 +714,9 @@ dependencies = [
 
 [[package]]
 name = "crossbeam-utils"
-version = "0.8.20"
+version = "0.8.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
 
 [[package]]
 name = "crypto-bigint"
@@ -772,7 +784,9 @@ dependencies = [
  "curve25519-dalek-derive",
  "digest",
  "fiat-crypto",
+ "rand_core",
  "rustc_version",
+ "serde",
  "subtle",
  "zeroize",
 ]
@@ -851,7 +865,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
 dependencies = [
  "powerfmt",
- "serde",
 ]
 
 [[package]]
@@ -1009,9 +1022,9 @@ checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
 
 [[package]]
 name = "encode_unicode"
-version = "0.3.6"
+version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
+checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
 
 [[package]]
 name = "enum-as-inner"
@@ -1076,17 +1089,6 @@ dependencies = [
  "windows-sys 0.59.0",
 ]
 
-[[package]]
-name = "event-listener"
-version = "4.0.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e"
-dependencies = [
- "concurrent-queue",
- "parking",
- "pin-project-lite",
-]
-
 [[package]]
 name = "event-listener"
 version = "5.3.1"
@@ -1104,7 +1106,7 @@ version = "0.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2"
 dependencies = [
- "event-listener 5.3.1",
+ "event-listener",
  "pin-project-lite",
 ]
 
@@ -1169,6 +1171,12 @@ version = "1.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
 
+[[package]]
+name = "foldhash"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2"
+
 [[package]]
 name = "form_urlencoded"
 version = "1.2.1"
@@ -1482,6 +1490,11 @@ name = "hashbrown"
 version = "0.15.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
+dependencies = [
+ "allocator-api2",
+ "equivalent",
+ "foldhash",
+]
 
 [[package]]
 name = "hashlink"
@@ -1542,7 +1555,7 @@ dependencies = [
  "ipnet",
  "once_cell",
  "rand",
- "thiserror 2.0.6",
+ "thiserror 2.0.7",
  "tinyvec",
  "tokio",
  "tracing",
@@ -1565,7 +1578,7 @@ dependencies = [
  "rand",
  "resolv-conf",
  "smallvec",
- "thiserror 2.0.6",
+ "thiserror 2.0.7",
  "tokio",
  "tracing",
 ]
@@ -1682,9 +1695,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
 
 [[package]]
 name = "hyper"
-version = "1.5.1"
+version = "1.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f"
+checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0"
 dependencies = [
  "bytes",
  "futures-channel",
@@ -1940,7 +1953,7 @@ dependencies = [
  "console",
  "number_prefix",
  "portable-atomic",
- "unicode-width 0.2.0",
+ "unicode-width",
  "web-time",
 ]
 
@@ -1991,25 +2004,28 @@ checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708"
 
 [[package]]
 name = "iroh"
-version = "0.29.0"
-source = "git+https://github.com/n0-computer/iroh#ee72f6da7caed23c24d34c611b5de222898dcbd0"
+version = "0.30.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a59352a43dc4199fc804e1a7f3729bd14baff496fd3efbba98763e204bc4af0"
 dependencies = [
+ "aead",
  "anyhow",
  "axum",
  "backoff",
  "base64",
  "bytes",
  "concurrent-queue",
+ "crypto_box",
+ "data-encoding",
  "der",
  "derive_more",
+ "ed25519-dalek",
  "futures-buffered",
  "futures-concurrency",
  "futures-lite 2.5.0",
  "futures-sink",
  "futures-util",
- "genawaiter",
  "governor",
- "hex",
  "hickory-resolver",
  "hostname 0.4.0",
  "http 1.2.0",
@@ -2052,7 +2068,7 @@ dependencies = [
  "stun-rs",
  "surge-ping",
  "swarm-discovery",
- "thiserror 2.0.6",
+ "thiserror 2.0.7",
  "time",
  "tokio",
  "tokio-rustls",
@@ -2062,7 +2078,6 @@ dependencies = [
  "tokio-util",
  "tracing",
  "url",
- "watchable",
  "webpki-roots",
  "windows 0.58.0",
  "wmi",
@@ -2072,27 +2087,20 @@ dependencies = [
 
 [[package]]
 name = "iroh-base"
-version = "0.29.0"
-source = "git+https://github.com/n0-computer/iroh#ee72f6da7caed23c24d34c611b5de222898dcbd0"
+version = "0.30.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdd4101e3f0732d901beb5461cb9bc415feeda8d21281ab5bf5c0c3458eebde2"
 dependencies = [
- "aead",
- "anyhow",
- "crypto_box",
+ "curve25519-dalek",
  "data-encoding",
  "derive_more",
  "ed25519-dalek",
  "getrandom",
- "hex",
- "once_cell",
  "postcard",
- "rand",
  "rand_core",
  "serde",
- "ssh-key",
- "thiserror 2.0.6",
- "ttl_cache",
+ "thiserror 2.0.7",
  "url",
- "zeroize",
 ]
 
 [[package]]
@@ -2157,11 +2165,12 @@ dependencies = [
  "serde_json",
  "serde_test",
  "smallvec",
+ "ssh-key",
  "strum",
  "tempfile",
  "testdir",
  "testresult",
- "thiserror 2.0.6",
+ "thiserror 2.0.7",
  "tokio",
  "tokio-util",
  "tracing",
@@ -2185,11 +2194,10 @@ dependencies = [
 
 [[package]]
 name = "iroh-metrics"
-version = "0.29.0"
+version = "0.30.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a242381d5da20bb4a6cc7482b5cc687a739da8371aff0ea8c12aaf499801886b"
+checksum = "d7efd9d7437db258f4d44852beea820cd872e4db976928ee0c2bc615b8c4fe5a"
 dependencies = [
- "anyhow",
  "erased_set",
  "http-body-util",
  "hyper",
@@ -2199,15 +2207,16 @@ dependencies = [
  "reqwest",
  "serde",
  "struct_iterable",
- "time",
+ "thiserror 2.0.7",
  "tokio",
  "tracing",
 ]
 
 [[package]]
 name = "iroh-net-report"
-version = "0.29.0"
-source = "git+https://github.com/n0-computer/iroh#ee72f6da7caed23c24d34c611b5de222898dcbd0"
+version = "0.30.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ee04b3b957169e3833f08791802e6bd9878213655d1adbcd9191ea78b8d671a"
 dependencies = [
  "anyhow",
  "bytes",
@@ -2283,20 +2292,21 @@ dependencies = [
 
 [[package]]
 name = "iroh-relay"
-version = "0.29.0"
-source = "git+https://github.com/n0-computer/iroh#ee72f6da7caed23c24d34c611b5de222898dcbd0"
+version = "0.30.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa0080c8d0720009dc5fa109ef2ead96c5aeb8bb8e4534de8f13865520818207"
 dependencies = [
  "anyhow",
  "base64",
  "bytes",
  "clap",
+ "data-encoding",
  "derive_more",
  "futures-buffered",
  "futures-lite 2.5.0",
  "futures-sink",
  "futures-util",
  "governor",
- "hex",
  "hickory-proto",
  "hickory-resolver",
  "hostname 0.4.0",
@@ -2309,6 +2319,7 @@ dependencies = [
  "iroh-quinn",
  "iroh-quinn-proto",
  "libc",
+ "lru",
  "num_enum",
  "once_cell",
  "pin-project",
@@ -2316,16 +2327,19 @@ dependencies = [
  "rand",
  "rcgen",
  "regex",
+ "reloadable-state",
  "reqwest",
  "ring",
  "rustls",
+ "rustls-cert-file-reader",
+ "rustls-cert-reloadable-resolver",
  "rustls-pemfile",
  "rustls-webpki",
  "serde",
  "smallvec",
  "socket2",
  "stun-rs",
- "thiserror 2.0.6",
+ "thiserror 2.0.7",
  "time",
  "tokio",
  "tokio-rustls",
@@ -2342,9 +2356,9 @@ dependencies = [
 
 [[package]]
 name = "iroh-test"
-version = "0.29.0"
+version = "0.30.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87d03fbecae1752f869cfe768306d1ab25921df976818e87c7ad624743b4fa8f"
+checksum = "858814e2810a29cca50e5489f67dd417f38124315d466bf5420b1bedc6923703"
 dependencies = [
  "anyhow",
  "tokio",
@@ -2415,12 +2429,6 @@ version = "0.2.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
 
-[[package]]
-name = "linked-hash-map"
-version = "0.5.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
-
 [[package]]
 name = "linux-raw-sys"
 version = "0.4.14"
@@ -2473,6 +2481,9 @@ name = "lru"
 version = "0.12.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
+dependencies = [
+ "hashbrown 0.15.2",
+]
 
 [[package]]
 name = "mainline"
@@ -2735,7 +2746,7 @@ dependencies = [
  "rtnetlink 0.14.1",
  "serde",
  "socket2",
- "thiserror 2.0.6",
+ "thiserror 2.0.7",
  "time",
  "tokio",
  "tokio-util",
@@ -3066,7 +3077,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc"
 dependencies = [
  "memchr",
- "thiserror 2.0.6",
+ "thiserror 2.0.7",
  "ucd-trie",
 ]
 
@@ -3244,9 +3255,9 @@ checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6"
 
 [[package]]
 name = "portmapper"
-version = "0.2.1"
+version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68ea24e7552a28ee4a3478ae116c89080957d6816526d0a533bee6cd67048279"
+checksum = "9e6b2058e5b2c829b7dcc62bb94ec223e2fdf07cad157b09ab05c5520af6f5b6"
 dependencies = [
  "anyhow",
  "base64",
@@ -3263,7 +3274,7 @@ dependencies = [
  "serde",
  "smallvec",
  "socket2",
- "thiserror 2.0.6",
+ "thiserror 2.0.7",
  "time",
  "tokio",
  "tokio-util",
@@ -3439,9 +3450,9 @@ dependencies = [
 
 [[package]]
 name = "proptest"
-version = "1.5.0"
+version = "1.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d"
+checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50"
 dependencies = [
  "bit-set",
  "bit-vec",
@@ -3459,9 +3470,9 @@ dependencies = [
 
 [[package]]
 name = "quanta"
-version = "0.12.3"
+version = "0.12.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e5167a477619228a0b284fac2674e3c388cba90631d7b7de620e6f1fcd08da5"
+checksum = "773ce68d0bb9bc7ef20be3536ffe94e223e1f365bd374108b2659fac0c65cfe6"
 dependencies = [
  "crossbeam-utils",
  "libc",
@@ -3502,9 +3513,9 @@ dependencies = [
 
 [[package]]
 name = "quic-rpc-derive"
-version = "0.17.1"
+version = "0.17.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81701deb4fec21c393d66ca8b6e0cb2ced1a61c5ca409bbf552e726e048c00e3"
+checksum = "5a32e88a525c7616b2bfce4be94a875eeac46bf20faea5e580cb54dc739e64e5"
 dependencies = [
  "proc-macro2",
  "quic-rpc",
@@ -3531,7 +3542,7 @@ dependencies = [
  "rustc-hash",
  "rustls",
  "socket2",
- "thiserror 2.0.6",
+ "thiserror 2.0.7",
  "tokio",
  "tracing",
 ]
@@ -3550,7 +3561,7 @@ dependencies = [
  "rustls",
  "rustls-pki-types",
  "slab",
- "thiserror 2.0.6",
+ "thiserror 2.0.7",
  "tinyvec",
  "tracing",
  "web-time",
@@ -3761,6 +3772,23 @@ version = "0.8.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
 
+[[package]]
+name = "reloadable-core"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1dc20ac1418988b60072d783c9f68e28a173fb63493c127952f6face3b40c6e0"
+
+[[package]]
+name = "reloadable-state"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3853ef78d45b50f8b989896304a85239539d39b7f866a000e8846b9b72d74ce8"
+dependencies = [
+ "arc-swap",
+ "reloadable-core",
+ "tokio",
+]
+
 [[package]]
 name = "reqwest"
 version = "0.12.9"
@@ -3953,6 +3981,41 @@ dependencies = [
  "zeroize",
 ]
 
+[[package]]
+name = "rustls-cert-file-reader"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f351eaf1dd003022222d2b1399caac198fefeab45c46b0f98bb03fc7cda9bb27"
+dependencies = [
+ "rustls-cert-read",
+ "rustls-pemfile",
+ "rustls-pki-types",
+ "thiserror 2.0.7",
+ "tokio",
+]
+
+[[package]]
+name = "rustls-cert-read"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd46e8c5ae4de3345c4786a83f99ec7aff287209b9e26fa883c473aeb28f19d5"
+dependencies = [
+ "rustls-pki-types",
+]
+
+[[package]]
+name = "rustls-cert-reloadable-resolver"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe1baa8a3a1f05eaa9fc55aed4342867f70e5c170ea3bfed1b38c51a4857c0c8"
+dependencies = [
+ "futures-util",
+ "reloadable-state",
+ "rustls",
+ "rustls-cert-read",
+ "thiserror 2.0.7",
+]
+
 [[package]]
 name = "rustls-native-certs"
 version = "0.7.3"
@@ -3977,9 +4040,9 @@ dependencies = [
 
 [[package]]
 name = "rustls-pki-types"
-version = "1.10.0"
+version = "1.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b"
+checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37"
 dependencies = [
  "web-time",
 ]
@@ -4131,9 +4194,9 @@ checksum = "c2fdfc24bc566f839a2da4c4295b82db7d25a24253867d5c64355abb5799bdbe"
 
 [[package]]
 name = "semver"
-version = "1.0.23"
+version = "1.0.24"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
+checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba"
 dependencies = [
  "serde",
 ]
@@ -4686,11 +4749,11 @@ dependencies = [
 
 [[package]]
 name = "thiserror"
-version = "2.0.6"
+version = "2.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47"
+checksum = "93605438cbd668185516ab499d589afb7ee1859ea3d5fc8f6b0755e1c7443767"
 dependencies = [
- "thiserror-impl 2.0.6",
+ "thiserror-impl 2.0.7",
 ]
 
 [[package]]
@@ -4706,9 +4769,9 @@ dependencies = [
 
 [[package]]
 name = "thiserror-impl"
-version = "2.0.6"
+version = "2.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312"
+checksum = "e1d8749b4531af2117677a5fcd12b1348a3fe2b81e36e61ffeac5c4aa3273e36"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -4840,7 +4903,7 @@ dependencies = [
  "rustls",
  "serde",
  "serde_json",
- "thiserror 2.0.6",
+ "thiserror 2.0.7",
  "time",
  "tokio",
  "tokio-rustls",
@@ -5063,15 +5126,6 @@ version = "0.2.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
 
-[[package]]
-name = "ttl_cache"
-version = "0.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4189890526f0168710b6ee65ceaedf1460c48a14318ceec933cb26baa492096a"
-dependencies = [
- "linked-hash-map",
-]
-
 [[package]]
 name = "tungstenite"
 version = "0.21.0"
@@ -5133,12 +5187,6 @@ dependencies = [
  "tinyvec",
 ]
 
-[[package]]
-name = "unicode-width"
-version = "0.1.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
-
 [[package]]
 name = "unicode-width"
 version = "0.2.0"
@@ -5352,18 +5400,6 @@ version = "0.2.99"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6"
 
-[[package]]
-name = "watchable"
-version = "1.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "45b42a2f611916b5965120a9cde2b60f2db4454826dd9ad5e6f47c24a5b3b259"
-dependencies = [
- "event-listener 4.0.3",
- "futures-util",
- "parking_lot",
- "thiserror 1.0.69",
-]
-
 [[package]]
 name = "web-sys"
 version = "0.3.76"
@@ -5721,7 +5757,7 @@ dependencies = [
  "futures",
  "log",
  "serde",
- "thiserror 2.0.6",
+ "thiserror 2.0.7",
  "windows 0.58.0",
  "windows-core 0.58.0",
 ]
diff --git a/Cargo.toml b/Cargo.toml
index a05c5f9f6..5b6a9f45d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -40,10 +40,10 @@ genawaiter = { version = "0.99.1", features = ["futures03"] }
 hashlink = { version = "0.9.0", optional = true }
 hex = "0.4.3"
 indicatif = { version = "0.17.8", optional = true }
-iroh-base = { version = "0.29.0" }
+iroh-base = { version = "0.30" }
 iroh-io = { version = "0.6.0", features = ["stats"] }
-iroh-metrics = { version = "0.29.0", default-features = false }
-iroh = "0.29.0"
+iroh-metrics = { version = "0.30.0", default-features = false }
+iroh = "0.30"
 nested_enum_utils = { version = "0.1.0", optional = true }
 num_cpus = "1.15.0"
 oneshot = "0.1.8"
@@ -66,6 +66,7 @@ serde = { version = "1", features = ["derive"] }
 serde-error = "0.1.3"
 smallvec = { version = "1.10.0", features = ["serde", "const_new"] }
 strum = { version = "0.26.3", optional = true }
+ssh-key = { version = "0.6", optional = true, features = ["ed25519"] }
 tempfile = { version = "3.10.0", optional = true }
 thiserror = "2"
 tokio = { version = "1", features = ["fs"] }
@@ -79,8 +80,8 @@ console = { version = "0.15.8", optional = true }
 
 [dev-dependencies]
 http-body = "1.0"
-iroh-test = { version = "0.29" }
-iroh = { version = "0.29", features = ["test-utils"] }
+iroh-test = { version = "0.30" }
+iroh = { version = "0.30", features = ["test-utils"] }
 futures-buffered = "0.2.4"
 proptest = "1.0.0"
 serde_json = "1.0.107"
@@ -103,14 +104,15 @@ metrics = ["iroh-metrics/metrics"]
 redb = ["dep:redb"]
 cli = ["rpc", "dep:clap", "dep:indicatif", "dep:console"]
 rpc = [
-    "dep:quic-rpc",
-    "dep:quic-rpc-derive",
-    "dep:nested_enum_utils",
-    "dep:strum",
-    "dep:futures-util",
-    "dep:portable-atomic",
-    "dep:walkdir",
-    "downloader",
+  "dep:quic-rpc",
+  "dep:quic-rpc-derive",
+  "dep:nested_enum_utils",
+  "dep:strum",
+  "dep:futures-util",
+  "dep:portable-atomic",
+  "dep:walkdir",
+  "dep:ssh-key",
+  "downloader",
 ]
 
 example-iroh = [
@@ -182,7 +184,3 @@ debug-assertions = false
 opt-level = 3
 panic = 'abort'
 incremental = false
-
-[patch.crates-io]
-iroh-base = { git = "https://github.com/n0-computer/iroh" }
-iroh = { git = "https://github.com/n0-computer/iroh" }
diff --git a/deny.toml b/deny.toml
index c903f9260..28b26fe72 100644
--- a/deny.toml
+++ b/deny.toml
@@ -37,6 +37,4 @@ ignore = [
 ]
 
 [sources]
-allow-git = [
-    "https://github.com/n0-computer/iroh.git",
-]
+allow-git = []
diff --git a/examples/hello-world-fetch.rs b/examples/hello-world-fetch.rs
index 7a6c61407..d8fb68955 100644
--- a/examples/hello-world-fetch.rs
+++ b/examples/hello-world-fetch.rs
@@ -56,6 +56,7 @@ async fn main() -> Result<()> {
         "node relay server url: {:?}",
         node.endpoint()
             .home_relay()
+            .get()?
             .expect("a default relay url should be provided")
             .to_string()
     );
diff --git a/examples/local-swarm-discovery.rs b/examples/local-swarm-discovery.rs
index 7305b7755..ccbb93043 100644
--- a/examples/local-swarm-discovery.rs
+++ b/examples/local-swarm-discovery.rs
@@ -61,7 +61,7 @@ async fn main() -> anyhow::Result<()> {
     setup_logging();
     let cli = Cli::parse();
 
-    let key = SecretKey::generate();
+    let key = SecretKey::generate(rand::rngs::OsRng);
     let discovery = LocalSwarmDiscovery::new(key.public())?;
 
     println!("Starting iroh node with local node discovery...");
diff --git a/src/downloader/test.rs b/src/downloader/test.rs
index 1fab8ff8e..63c22fe9b 100644
--- a/src/downloader/test.rs
+++ b/src/downloader/test.rs
@@ -76,7 +76,7 @@ async fn smoke_test() {
         Downloader::spawn_for_test(dialer.clone(), getter.clone(), concurrency_limits);
 
     // send a request and make sure the peer is requested the corresponding download
-    let peer = SecretKey::generate().public();
+    let peer = SecretKey::generate(rand::thread_rng()).public();
     let kind: DownloadKind = HashAndFormat::raw(Hash::new([0u8; 32])).into();
     let req = DownloadRequest::new(kind, vec![peer]);
     let handle = downloader.queue(req).await;
@@ -101,7 +101,7 @@ async fn deduplication() {
     let (downloader, _lp) =
         Downloader::spawn_for_test(dialer.clone(), getter.clone(), concurrency_limits);
 
-    let peer = SecretKey::generate().public();
+    let peer = SecretKey::generate(rand::thread_rng()).public();
     let kind: DownloadKind = HashAndFormat::raw(Hash::new([0u8; 32])).into();
     let mut handles = Vec::with_capacity(10);
     for _ in 0..10 {
@@ -134,7 +134,7 @@ async fn cancellation() {
     let (downloader, _lp) =
         Downloader::spawn_for_test(dialer.clone(), getter.clone(), concurrency_limits);
 
-    let peer = SecretKey::generate().public();
+    let peer = SecretKey::generate(rand::thread_rng()).public();
     let kind_1: DownloadKind = HashAndFormat::raw(Hash::new([0u8; 32])).into();
     let req = DownloadRequest::new(kind_1, vec![peer]);
     let handle_a = downloader.queue(req.clone()).await;
@@ -175,7 +175,7 @@ async fn max_concurrent_requests_total() {
         Downloader::spawn_for_test(dialer.clone(), getter.clone(), concurrency_limits);
 
     // send the downloads
-    let peer = SecretKey::generate().public();
+    let peer = SecretKey::generate(rand::thread_rng()).public();
     let mut handles = Vec::with_capacity(5);
     let mut expected_history = Vec::with_capacity(5);
     for i in 0..5 {
@@ -219,7 +219,7 @@ async fn max_concurrent_requests_per_peer() {
         Downloader::spawn_for_test(dialer.clone(), getter.clone(), concurrency_limits);
 
     // send the downloads
-    let peer = SecretKey::generate().public();
+    let peer = SecretKey::generate(rand::thread_rng()).public();
     let mut handles = Vec::with_capacity(5);
     for i in 0..5 {
         let kind = HashAndFormat::raw(Hash::new([i; 32]));
@@ -275,7 +275,7 @@ async fn concurrent_progress() {
     let (downloader, _lp) =
         Downloader::spawn_for_test(dialer.clone(), getter.clone(), Default::default());
 
-    let peer = SecretKey::generate().public();
+    let peer = SecretKey::generate(rand::thread_rng()).public();
     let hash = Hash::new([0u8; 32]);
     let kind_1 = HashAndFormat::raw(hash);
 
@@ -369,11 +369,12 @@ async fn long_queue() {
 
     let (downloader, _lp) =
         Downloader::spawn_for_test(dialer.clone(), getter.clone(), concurrency_limits);
+    let mut rng = rand::thread_rng();
     // send the downloads
     let nodes = [
-        SecretKey::generate().public(),
-        SecretKey::generate().public(),
-        SecretKey::generate().public(),
+        SecretKey::generate(&mut rng).public(),
+        SecretKey::generate(&mut rng).public(),
+        SecretKey::generate(&mut rng).public(),
     ];
     let mut handles = vec![];
     for i in 0..100usize {
@@ -417,7 +418,7 @@ async fn fail_while_running() {
         .boxed()
     }));
 
-    let node = SecretKey::generate().public();
+    let node = SecretKey::generate(rand::thread_rng()).public();
     let req_success = DownloadRequest::new(blob_success, vec![node]);
     let req_fail = DownloadRequest::new(blob_fail, vec![node]);
     let handle_success = downloader.queue(req_success).await;
@@ -437,7 +438,7 @@ async fn retry_nodes_simple() {
     let getter = getter::TestingGetter::default();
     let (downloader, _lp) =
         Downloader::spawn_for_test(dialer.clone(), getter.clone(), Default::default());
-    let node = SecretKey::generate().public();
+    let node = SecretKey::generate(rand::thread_rng()).public();
     let dial_attempts = Arc::new(AtomicUsize::new(0));
     let dial_attempts2 = dial_attempts.clone();
     // fail on first dial, then succeed
@@ -467,7 +468,7 @@ async fn retry_nodes_fail() {
         Default::default(),
         config,
     );
-    let node = SecretKey::generate().public();
+    let node = SecretKey::generate(rand::thread_rng()).public();
     // fail always
     dialer.set_dial_outcome(move |_node| false);
 
@@ -504,8 +505,9 @@ async fn retry_nodes_jump_queue() {
     let (downloader, _lp) =
         Downloader::spawn_for_test(dialer.clone(), getter.clone(), concurrency_limits);
 
-    let good_node = SecretKey::generate().public();
-    let bad_node = SecretKey::generate().public();
+    let mut rng = rand::thread_rng();
+    let good_node = SecretKey::generate(&mut rng).public();
+    let bad_node = SecretKey::generate(&mut rng).public();
 
     dialer.set_dial_outcome(move |node| node == good_node);
     let kind1 = HashAndFormat::raw(Hash::new([0u8; 32]));
diff --git a/src/hash.rs b/src/hash.rs
index e0589ecde..abf2327c0 100644
--- a/src/hash.rs
+++ b/src/hash.rs
@@ -2,7 +2,6 @@
 
 use std::{borrow::Borrow, fmt, str::FromStr};
 
-use iroh_base::base32::{self, parse_array_hex_or_base32, HexOrBase32ParseError};
 use postcard::experimental::max_size::MaxSize;
 use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
 
@@ -52,10 +51,10 @@ impl Hash {
         self.0.to_hex().to_string()
     }
 
-    /// Convert to a base32 string limited to the first 10 bytes for a friendly string
+    /// Convert to a hex string limited to the first 5bytes for a friendly string
     /// representation of the hash.
     pub fn fmt_short(&self) -> String {
-        base32::fmt_short(self.as_bytes())
+        data_encoding::HEXLOWER.encode(&self.as_bytes()[..5])
     }
 }
 
@@ -134,11 +133,35 @@ impl fmt::Display for Hash {
     }
 }
 
+#[derive(Debug, thiserror::Error)]
+pub enum HexOrBase32ParseError {
+    #[error("Invalid length")]
+    DecodeInvalidLength,
+    #[error("Failed to decode {0}")]
+    Decode(#[from] data_encoding::DecodeError),
+}
+
 impl FromStr for Hash {
     type Err = HexOrBase32ParseError;
 
     fn from_str(s: &str) -> Result<Self, Self::Err> {
-        parse_array_hex_or_base32(s).map(Hash::from)
+        let mut bytes = [0u8; 32];
+
+        let res = if s.len() == 64 {
+            // hex
+            data_encoding::HEXLOWER.decode_mut(s.as_bytes(), &mut bytes)
+        } else {
+            data_encoding::BASE32_NOPAD.decode_mut(s.to_ascii_uppercase().as_bytes(), &mut bytes)
+        };
+        match res {
+            Ok(len) => {
+                if len != 32 {
+                    return Err(HexOrBase32ParseError::DecodeInvalidLength);
+                }
+            }
+            Err(partial) => return Err(partial.error.into()),
+        }
+        Ok(Self(blake3::Hash::from_bytes(bytes)))
     }
 }
 
diff --git a/src/rpc/client/blobs.rs b/src/rpc/client/blobs.rs
index 1809b6aaf..28d73316e 100644
--- a/src/rpc/client/blobs.rs
+++ b/src/rpc/client/blobs.rs
@@ -1903,7 +1903,9 @@ mod tests {
         let (relay_map, _relay_url, _guard) = iroh::test_utils::run_relay_server().await?;
         let dns_pkarr_server = DnsPkarrServer::run().await?;
 
-        let secret1 = SecretKey::generate();
+        let mut rng = rand::thread_rng();
+
+        let secret1 = SecretKey::generate(&mut rng);
         let endpoint1 = iroh::Endpoint::builder()
             .relay_mode(RelayMode::Custom(relay_map.clone()))
             .insecure_skip_relay_cert_verify(true)
@@ -1911,7 +1913,7 @@ mod tests {
             .secret_key(secret1.clone())
             .discovery(dns_pkarr_server.discovery(secret1));
         let node1 = Node::memory().endpoint(endpoint1).spawn().await?;
-        let secret2 = SecretKey::generate();
+        let secret2 = SecretKey::generate(&mut rng);
         let endpoint2 = iroh::Endpoint::builder()
             .relay_mode(RelayMode::Custom(relay_map.clone()))
             .insecure_skip_relay_cert_verify(true)
diff --git a/src/ticket.rs b/src/ticket.rs
index 8d1a45a5e..1cde760d7 100644
--- a/src/ticket.rs
+++ b/src/ticket.rs
@@ -154,14 +154,13 @@ mod tests {
     use std::net::SocketAddr;
 
     use iroh::{PublicKey, SecretKey};
-    use iroh_base::base32;
     use iroh_test::{assert_eq_hex, hexdump::parse_hexdump};
 
     use super::*;
 
     fn make_ticket() -> BlobTicket {
         let hash = Hash::new(b"hi there");
-        let peer = SecretKey::generate().public();
+        let peer = SecretKey::generate(rand::thread_rng()).public();
         let addr = SocketAddr::from_str("127.0.0.1:1234").unwrap();
         let relay_url = None;
         BlobTicket {
@@ -201,7 +200,11 @@ mod tests {
             format: BlobFormat::Raw,
             hash,
         };
-        let base32 = base32::parse_vec(ticket.to_string().strip_prefix("blob").unwrap()).unwrap();
+        let encoded = ticket.to_string();
+        let stripped = encoded.strip_prefix("blob").unwrap();
+        let base32 = data_encoding::BASE32_NOPAD
+            .decode(stripped.to_ascii_uppercase().as_bytes())
+            .unwrap();
         let expected = parse_hexdump("
             00 # discriminator for variant 0
             ae58ff8833241ac82d6ff7611046ed67b5072d142c588d0063e942d9a75502b6 # node id, 32 bytes, see above
diff --git a/src/util/fs.rs b/src/util/fs.rs
index b988a3bdc..dc868859d 100644
--- a/src/util/fs.rs
+++ b/src/util/fs.rs
@@ -133,11 +133,21 @@ pub async fn load_secret_key(key_path: PathBuf) -> anyhow::Result<iroh::SecretKe
 
     if key_path.exists() {
         let keystr = tokio::fs::read(key_path).await?;
-        let secret_key = SecretKey::try_from_openssh(keystr).context("invalid keyfile")?;
+
+        let ser_key = ssh_key::private::PrivateKey::from_openssh(keystr)?;
+        let ssh_key::private::KeypairData::Ed25519(kp) = ser_key.key_data() else {
+            bail!("invalid key format");
+        };
+        let secret_key = SecretKey::from_bytes(&kp.private.to_bytes());
         Ok(secret_key)
     } else {
-        let secret_key = SecretKey::generate();
-        let ser_key = secret_key.to_openssh()?;
+        let secret_key = SecretKey::generate(rand::rngs::OsRng);
+        let ckey = ssh_key::private::Ed25519Keypair {
+            public: secret_key.public().public().into(),
+            private: secret_key.secret().into(),
+        };
+        let ser_key =
+            ssh_key::private::PrivateKey::from(ckey).to_openssh(ssh_key::LineEnding::default())?;
 
         // Try to canonicalize if possible
         let key_path = key_path.canonicalize().unwrap_or(key_path);