diff --git a/content-discovery/Cargo.toml b/content-discovery/Cargo.toml
index fe2e191..0d01186 100644
--- a/content-discovery/Cargo.toml
+++ b/content-discovery/Cargo.toml
@@ -3,6 +3,7 @@ members = [
   "iroh-mainline-content-discovery",
   "iroh-mainline-content-discovery-cli",
   "iroh-mainline-tracker",
+  "tls",
 ]
 resolver = "2"
 
@@ -26,5 +27,6 @@ missing_debug_implementations = "warn"
 unused-async = "warn"
 
 [workspace.dependencies]
-iroh = "0.29"
-iroh-blobs = "0.29"
+iroh = "0.30"
+iroh-base = "0.30"
+iroh-blobs = { version = "0.30", features = ["rpc"] }
diff --git a/content-discovery/iroh-mainline-content-discovery-cli/src/args.rs b/content-discovery/iroh-mainline-content-discovery-cli/src/args.rs
index fe15190..294e2a9 100644
--- a/content-discovery/iroh-mainline-content-discovery-cli/src/args.rs
+++ b/content-discovery/iroh-mainline-content-discovery-cli/src/args.rs
@@ -1,6 +1,7 @@
 //! Command line arguments.
 use clap::{Parser, Subcommand};
-use iroh::{ticket::BlobTicket, NodeId};
+use iroh::NodeId;
+use iroh_blobs::ticket::BlobTicket;
 use iroh_blobs::{Hash, HashAndFormat};
 use std::{
     fmt::Display,
diff --git a/content-discovery/iroh-mainline-content-discovery-cli/src/main.rs b/content-discovery/iroh-mainline-content-discovery-cli/src/main.rs
index 40aa37e..b47dd16 100644
--- a/content-discovery/iroh-mainline-content-discovery-cli/src/main.rs
+++ b/content-discovery/iroh-mainline-content-discovery-cli/src/main.rs
@@ -24,7 +24,7 @@ async fn announce(args: AnnounceArgs) -> anyhow::Result<()> {
         eprintln!("ANNOUNCE_SECRET environment variable must be set to a valid secret key");
         anyhow::bail!("ANNOUNCE_SECRET env var not set");
     };
-    let Ok(key) = iroh::key::SecretKey::from_str(&key) else {
+    let Ok(key) = iroh::SecretKey::from_str(&key) else {
         anyhow::bail!("ANNOUNCE_SECRET env var is not a valid secret key");
     };
     let content = args.content.hash_and_format();
diff --git a/content-discovery/iroh-mainline-content-discovery/Cargo.toml b/content-discovery/iroh-mainline-content-discovery/Cargo.toml
index 9d34d5b..45559f8 100644
--- a/content-discovery/iroh-mainline-content-discovery/Cargo.toml
+++ b/content-discovery/iroh-mainline-content-discovery/Cargo.toml
@@ -12,7 +12,9 @@ license = "MIT OR Apache-2.0"
 #
 # The protocol is using postcard, but we don't need a postcard dependency for just the type definitions
 iroh = { workspace = true }
+iroh-base = { workspace = true }
 iroh-blobs = { workspace = true }
+rand = "0.8.5"
 serde = { version = "1", features = ["derive"] }
 derive_more = { version = "1.0.0-beta.1", features = ["debug", "display", "from", "try_into"] }
 serde-big-array = "0.5.1"
@@ -30,7 +32,8 @@ rustls = { version = "0.23", default-features = false, features = ["ring"], opti
 genawaiter = { version = "0.99.1", features = ["futures03"], optional = true }
 tokio = { version = "1.36.0", optional = true }
 flume = "0.11.0"
+tls = { path = "../tls", optional = true }
 
 [features]
-client = ["mainline", "iroh-quinn", "tracing", "anyhow", "rcgen", "genawaiter", "rustls", "futures", "postcard", "tokio"]
+client = ["mainline", "iroh-quinn", "tracing", "anyhow", "rcgen", "genawaiter", "rustls", "futures", "postcard", "tokio", "tls"]
 default = ["client"]
diff --git a/content-discovery/iroh-mainline-content-discovery/src/client.rs b/content-discovery/iroh-mainline-content-discovery/src/client.rs
index fd82d64..89ec3ae 100644
--- a/content-discovery/iroh-mainline-content-discovery/src/client.rs
+++ b/content-discovery/iroh-mainline-content-discovery/src/client.rs
@@ -210,9 +210,8 @@ pub fn create_quinn_client(
     alpn_protocols: Vec<Vec<u8>>,
     keylog: bool,
 ) -> anyhow::Result<iroh_quinn::Endpoint> {
-    let secret_key = iroh::key::SecretKey::generate();
-    let tls_client_config =
-        iroh::tls::make_client_config(&secret_key, None, alpn_protocols, keylog)?;
+    let secret_key = iroh::SecretKey::generate(rand::thread_rng());
+    let tls_client_config = tls::make_client_config(&secret_key, None, alpn_protocols, keylog)?;
     let mut client_config = iroh_quinn::ClientConfig::new(Arc::new(tls_client_config));
     let mut endpoint = iroh_quinn::Endpoint::client(bind_addr)?;
     let mut transport_config = iroh_quinn::TransportConfig::default();
@@ -223,7 +222,7 @@ pub fn create_quinn_client(
 }
 
 async fn create_endpoint(
-    key: iroh::key::SecretKey,
+    key: iroh::SecretKey,
     ipv4_addr: SocketAddrV4,
     ipv6_addr: SocketAddrV6,
     publish: bool,
@@ -301,7 +300,7 @@ async fn connect_iroh(
     // todo: uncomment once the connection problems are fixed
     // for now, a random node id is more reliable.
     // let key = load_secret_key(tracker_path(CLIENT_KEY)?).await?;
-    let key = iroh::key::SecretKey::generate();
+    let key = iroh::SecretKey::generate(rand::thread_rng());
     let endpoint = create_endpoint(key, local_ipv4_addr, local_ipv6_addr, false).await?;
     tracing::info!("trying to connect to tracker at {:?}", tracker);
     let connection = endpoint.connect(tracker, ALPN).await?;
diff --git a/content-discovery/iroh-mainline-content-discovery/src/protocol.rs b/content-discovery/iroh-mainline-content-discovery/src/protocol.rs
index a6fa2d9..5e7ffb1 100644
--- a/content-discovery/iroh-mainline-content-discovery/src/protocol.rs
+++ b/content-discovery/iroh-mainline-content-discovery/src/protocol.rs
@@ -118,7 +118,7 @@ impl Deref for SignedAnnounce {
 
 impl SignedAnnounce {
     /// Create a new signed announce.
-    pub fn new(announce: Announce, secret_key: &iroh::key::SecretKey) -> anyhow::Result<Self> {
+    pub fn new(announce: Announce, secret_key: &iroh::SecretKey) -> anyhow::Result<Self> {
         let announce_bytes = postcard::to_allocvec(&announce)?;
         let signature = secret_key.sign(&announce_bytes).to_bytes();
         Ok(Self {
@@ -130,7 +130,7 @@ impl SignedAnnounce {
     /// Verify the announce, and return the announce if it's valid.
     pub fn verify(&self) -> anyhow::Result<()> {
         let announce_bytes = postcard::to_allocvec(&self.announce)?;
-        let signature = iroh::key::Signature::from_bytes(&self.signature);
+        let signature = iroh_base::Signature::from_bytes(&self.signature);
         self.announce.host.verify(&announce_bytes, &signature)?;
         Ok(())
     }
diff --git a/content-discovery/iroh-mainline-tracker/Cargo.toml b/content-discovery/iroh-mainline-tracker/Cargo.toml
index 6641751..5e4f840 100644
--- a/content-discovery/iroh-mainline-tracker/Cargo.toml
+++ b/content-discovery/iroh-mainline-tracker/Cargo.toml
@@ -41,6 +41,7 @@ url = "2.5.0"
 flume = "0.11.0"
 genawaiter = { version = "0.99.1", features = ["futures03"] }
 iroh-mainline-content-discovery = { path = "../iroh-mainline-content-discovery", features = ["client"] }
+tls = { path = "../tls" }
 
 clap = { version = "4", features = ["derive"], optional = true }
 serde-big-array = "0.5.1"
diff --git a/content-discovery/iroh-mainline-tracker/src/main.rs b/content-discovery/iroh-mainline-tracker/src/main.rs
index 1b32fc8..ecb70cb 100644
--- a/content-discovery/iroh-mainline-tracker/src/main.rs
+++ b/content-discovery/iroh-mainline-tracker/src/main.rs
@@ -62,7 +62,7 @@ async fn await_relay_region(endpoint: &Endpoint) -> anyhow::Result<()> {
 }
 
 async fn create_endpoint(
-    key: iroh::key::SecretKey,
+    key: iroh::SecretKey,
     ipv4_addr: SocketAddrV4,
     publish: bool,
 ) -> anyhow::Result<Endpoint> {
@@ -187,18 +187,18 @@ async fn main() -> anyhow::Result<()> {
 
 /// Returns default server configuration along with its certificate.
 #[allow(clippy::field_reassign_with_default)] // https://github.com/rust-lang/rust-clippy/issues/6527
-fn configure_server(secret_key: &iroh::key::SecretKey) -> anyhow::Result<iroh_quinn::ServerConfig> {
+fn configure_server(secret_key: &iroh::SecretKey) -> anyhow::Result<iroh_quinn::ServerConfig> {
     make_server_config(secret_key, 8, 1024, vec![ALPN.to_vec()])
 }
 
 /// Create a [`quinn::ServerConfig`] with the given secret key and limits.
 pub fn make_server_config(
-    secret_key: &iroh::key::SecretKey,
+    secret_key: &iroh::SecretKey,
     max_streams: u64,
     max_connections: u32,
     alpn_protocols: Vec<Vec<u8>>,
 ) -> anyhow::Result<iroh_quinn::ServerConfig> {
-    let tls_server_config = iroh::tls::make_server_config(secret_key, alpn_protocols, false)?;
+    let tls_server_config = tls::make_server_config(secret_key, alpn_protocols, false)?;
     let mut server_config = iroh_quinn::ServerConfig::with_crypto(Arc::new(tls_server_config));
     let mut transport_config = iroh_quinn::TransportConfig::default();
     transport_config
diff --git a/content-discovery/tls/Cargo.toml b/content-discovery/tls/Cargo.toml
new file mode 100644
index 0000000..08a958c
--- /dev/null
+++ b/content-discovery/tls/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "tls"
+version = "0.1.0"
+edition = "2021"
+description = "create tls configuration for quic connections"
+license = "MIT OR Apache-2.0"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+iroh-base = { workspace = true }
+der = { version = "0.7", features = ["alloc", "derive"] }
+derive_more = { version = "1.0.0-beta.1", features = ["debug", "display", "from", "try_into"] }
+quinn = { package = "iroh-quinn", version = "0.12.0" }
+rand = "0.8.5"
+rcgen = "0.13"
+ring = "0.17" 
+rustls = { version = "0.23", default-features = false, features = ["ring"] }
+thiserror = "2" 
+tracing = "0.1"
+webpki = { package = "rustls-webpki", version = "0.102" }
+x509-parser = "0.16"
diff --git a/content-discovery/tls/src/certificate.rs b/content-discovery/tls/src/certificate.rs
new file mode 100644
index 0000000..9797d30
--- /dev/null
+++ b/content-discovery/tls/src/certificate.rs
@@ -0,0 +1,444 @@
+//! X.509 certificate handling.
+//!
+//! This module handles generation, signing, and verification of certificates.
+//!
+//! Based on rust-libp2p/transports/tls/src/certificate.rs originally licensed under MIT by Parity
+//! Technologies (UK) Ltd.
+
+use std::sync::Arc;
+
+use der::{asn1::OctetStringRef, Decode, Encode, Sequence};
+use iroh_base::{PublicKey, SecretKey, Signature};
+use x509_parser::prelude::*;
+
+/// The libp2p Public Key Extension is a X.509 extension
+/// with the Object Identifier 1.3.6.1.4.1.53594.1.1,
+/// allocated by IANA to the libp2p project at Protocol Labs.
+const P2P_EXT_OID: [u64; 9] = [1, 3, 6, 1, 4, 1, 53594, 1, 1];
+
+/// The peer signs the concatenation of the string `libp2p-tls-handshake:`
+/// and the public key that it used to generate the certificate carrying
+/// the libp2p Public Key Extension, using its private host key.
+/// This signature provides cryptographic proof that the peer was
+/// in possession of the private host key at the time the certificate was signed.
+const P2P_SIGNING_PREFIX: [u8; 21] = *b"libp2p-tls-handshake:";
+
+// Certificates MUST use the NamedCurve encoding for elliptic curve parameters.
+// Similarly, hash functions with an output length less than 256 bits MUST NOT be used.
+static P2P_SIGNATURE_ALGORITHM: &rcgen::SignatureAlgorithm = &rcgen::PKCS_ECDSA_P256_SHA256;
+
+#[derive(Debug)]
+pub(crate) struct AlwaysResolvesCert(Arc<rustls::sign::CertifiedKey>);
+
+impl AlwaysResolvesCert {
+    pub(crate) fn new(
+        cert: rustls::pki_types::CertificateDer<'static>,
+        key: &rustls::pki_types::PrivateKeyDer<'_>,
+    ) -> Result<Self, rustls::Error> {
+        let certified_key = rustls::sign::CertifiedKey::new(
+            vec![cert],
+            rustls::crypto::ring::sign::any_ecdsa_type(key)?,
+        );
+        Ok(Self(Arc::new(certified_key)))
+    }
+}
+
+impl rustls::client::ResolvesClientCert for AlwaysResolvesCert {
+    fn resolve(
+        &self,
+        _root_hint_subjects: &[&[u8]],
+        _sigschemes: &[rustls::SignatureScheme],
+    ) -> Option<Arc<rustls::sign::CertifiedKey>> {
+        Some(Arc::clone(&self.0))
+    }
+
+    fn has_certs(&self) -> bool {
+        true
+    }
+}
+
+impl rustls::server::ResolvesServerCert for AlwaysResolvesCert {
+    fn resolve(
+        &self,
+        _client_hello: rustls::server::ClientHello<'_>,
+    ) -> Option<Arc<rustls::sign::CertifiedKey>> {
+        Some(Arc::clone(&self.0))
+    }
+}
+
+/// The public host key and the signature are ANS.1-encoded
+/// into the SignedKey data structure, which is carried  in the libp2p Public Key Extension.
+#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
+struct SignedKey<'a> {
+    public_key: OctetStringRef<'a>,
+    signature: OctetStringRef<'a>,
+}
+
+/// Generates a self-signed TLS certificate that includes a libp2p-specific
+/// certificate extension containing the public key of the given secret key.
+pub fn generate(
+    identity_secret_key: &SecretKey,
+) -> Result<
+    (
+        rustls::pki_types::CertificateDer<'static>,
+        rustls::pki_types::PrivateKeyDer<'static>,
+    ),
+    GenError,
+> {
+    // SecretKey used to sign the certificate.
+    // SHOULD NOT be related to the host's key.
+    // Endpoints MAY generate a new key and certificate
+    // for every connection attempt, or they MAY reuse the same key
+    // and certificate for multiple connections.
+    let certificate_keypair = rcgen::KeyPair::generate_for(P2P_SIGNATURE_ALGORITHM)?;
+    let rustls_key =
+        rustls::pki_types::PrivateKeyDer::try_from(certificate_keypair.serialize_der())
+            .expect("checked");
+    let certificate = {
+        let mut params = rcgen::CertificateParams::default();
+        params.distinguished_name = rcgen::DistinguishedName::new();
+        params.custom_extensions.push(make_libp2p_extension(
+            identity_secret_key,
+            &certificate_keypair,
+        )?);
+        params
+            .self_signed(&certificate_keypair)
+            .expect("self signed certificate to be generated")
+    };
+
+    Ok((certificate.der().clone(), rustls_key))
+}
+
+/// Attempts to parse the provided bytes as a [`P2pCertificate`].
+///
+/// For this to succeed, the certificate must contain the specified extension and the signature must
+/// match the embedded public key.
+pub fn parse<'a>(
+    certificate: &'a rustls::pki_types::CertificateDer<'_>,
+) -> Result<P2pCertificate<'a>, ParseError> {
+    let certificate = parse_unverified(certificate.as_ref())?;
+
+    certificate.verify()?;
+
+    Ok(certificate)
+}
+
+/// An X.509 certificate with a libp2p-specific extension
+/// is used to secure libp2p connections.
+#[derive(Debug)]
+pub struct P2pCertificate<'a> {
+    certificate: X509Certificate<'a>,
+    /// This is a specific libp2p Public Key Extension with two values:
+    /// * the public host key
+    /// * a signature performed using the private host key
+    extension: P2pExtension,
+}
+
+/// The contents of the specific libp2p extension, containing the public host key
+/// and a signature performed using the private host key.
+#[derive(Debug)]
+pub struct P2pExtension {
+    public_key: PublicKey,
+    /// This signature provides cryptographic proof that the peer was
+    /// in possession of the private host key at the time the certificate was signed.
+    signature: Signature,
+}
+
+/// An error that occurs during certificate generation.
+#[derive(Debug, thiserror::Error)]
+#[error(transparent)]
+pub struct GenError(#[from] rcgen::Error);
+
+/// An error that occurs during certificate parsing.
+#[derive(Debug, thiserror::Error)]
+#[error(transparent)]
+pub struct ParseError(#[from] pub(crate) webpki::Error);
+
+/// An error that occurs during signature verification.
+#[derive(Debug, thiserror::Error)]
+#[error(transparent)]
+pub struct VerificationError(#[from] pub(crate) webpki::Error);
+
+/// Internal function that only parses but does not verify the certificate.
+///
+/// Useful for testing but unsuitable for production.
+fn parse_unverified(der_input: &[u8]) -> Result<P2pCertificate, webpki::Error> {
+    let x509 = X509Certificate::from_der(der_input)
+        .map(|(_rest_input, x509)| x509)
+        .map_err(|_| webpki::Error::BadDer)?;
+
+    let p2p_ext_oid = der_parser::oid::Oid::from(&P2P_EXT_OID)
+        .expect("This is a valid OID of p2p extension; qed");
+
+    let mut libp2p_extension = None;
+
+    for ext in x509.extensions() {
+        let oid = &ext.oid;
+        if oid == &p2p_ext_oid && libp2p_extension.is_some() {
+            // The extension was already parsed
+            return Err(webpki::Error::BadDer);
+        }
+
+        if oid == &p2p_ext_oid {
+            let signed_key =
+                SignedKey::from_der(ext.value).map_err(|_| webpki::Error::ExtensionValueInvalid)?;
+            let public_key_raw = signed_key.public_key.as_bytes();
+            let public_key =
+                PublicKey::try_from(public_key_raw).map_err(|_| webpki::Error::UnknownIssuer)?;
+
+            let signature = Signature::from_slice(signed_key.signature.as_bytes())
+                .map_err(|_| webpki::Error::UnknownIssuer)?;
+            let ext = P2pExtension {
+                public_key,
+                signature,
+            };
+            libp2p_extension = Some(ext);
+            continue;
+        }
+
+        if ext.critical {
+            // Endpoints MUST abort the connection attempt if the certificate
+            // contains critical extensions that the endpoint does not understand.
+            return Err(webpki::Error::UnsupportedCriticalExtension);
+        }
+
+        // Implementations MUST ignore non-critical extensions with unknown OIDs.
+    }
+
+    // The certificate MUST contain the libp2p Public Key Extension.
+    // If this extension is missing, endpoints MUST abort the connection attempt.
+    let extension = libp2p_extension.ok_or(webpki::Error::BadDer)?;
+
+    let certificate = P2pCertificate {
+        certificate: x509,
+        extension,
+    };
+
+    Ok(certificate)
+}
+
+fn make_libp2p_extension(
+    identity_secret_key: &SecretKey,
+    certificate_keypair: &rcgen::KeyPair,
+) -> Result<rcgen::CustomExtension, rcgen::Error> {
+    // The peer signs the concatenation of the string `libp2p-tls-handshake:`
+    // and the public key that it used to generate the certificate carrying
+    // the libp2p Public Key Extension, using its private host key.
+    let signature = {
+        let mut msg = vec![];
+        msg.extend(P2P_SIGNING_PREFIX);
+        msg.extend(certificate_keypair.public_key_der());
+
+        identity_secret_key.sign(&msg)
+    };
+
+    let public_key = identity_secret_key.public();
+    let public_key_ref = OctetStringRef::new(&public_key.as_bytes()[..])
+        .map_err(|_| rcgen::Error::CouldNotParseKeyPair)?;
+    let signature = signature.to_bytes();
+    let signature_ref =
+        OctetStringRef::new(&signature).map_err(|_| rcgen::Error::CouldNotParseCertificate)?;
+    let key = SignedKey {
+        public_key: public_key_ref,
+        signature: signature_ref,
+    };
+
+    let mut extension_content = Vec::new();
+    key.encode_to_vec(&mut extension_content).expect("vec");
+
+    // This extension MAY be marked critical.
+    let mut ext = rcgen::CustomExtension::from_oid_content(&P2P_EXT_OID, extension_content);
+    ext.set_criticality(true);
+
+    Ok(ext)
+}
+
+impl P2pCertificate<'_> {
+    /// The [`PublicKey`] of the remote peer.
+    pub fn peer_id(&self) -> PublicKey {
+        self.extension.public_key
+    }
+
+    /// Verify the `signature` of the `message` signed by the secret key corresponding to the public key stored
+    /// in the certificate.
+    pub fn verify_signature(
+        &self,
+        signature_scheme: rustls::SignatureScheme,
+        message: &[u8],
+        signature: &[u8],
+    ) -> Result<(), VerificationError> {
+        let pk = self.public_key(signature_scheme)?;
+        pk.verify(message, signature)
+            .map_err(|_| webpki::Error::InvalidSignatureForPublicKey)?;
+
+        Ok(())
+    }
+
+    /// Get a [`ring::signature::UnparsedPublicKey`] for this `signature_scheme`.
+    /// Return `Error` if the `signature_scheme` does not match the public key signature
+    /// and hashing algorithm or if the `signature_scheme` is not supported.
+    fn public_key(
+        &self,
+        signature_scheme: rustls::SignatureScheme,
+    ) -> Result<ring::signature::UnparsedPublicKey<&[u8]>, webpki::Error> {
+        use ring::signature;
+        use rustls::SignatureScheme::*;
+
+        let current_signature_scheme = self.signature_scheme()?;
+        if signature_scheme != current_signature_scheme {
+            // This certificate was signed with a different signature scheme
+            return Err(webpki::Error::UnsupportedSignatureAlgorithmForPublicKey);
+        }
+
+        let verification_algorithm: &dyn signature::VerificationAlgorithm = match signature_scheme {
+            ECDSA_NISTP256_SHA256 => &signature::ECDSA_P256_SHA256_ASN1,
+            ECDSA_NISTP384_SHA384 => &signature::ECDSA_P384_SHA384_ASN1,
+            ECDSA_NISTP521_SHA512 => {
+                // See https://github.com/briansmith/ring/issues/824
+                return Err(webpki::Error::UnsupportedSignatureAlgorithm);
+            }
+            ED25519 => &signature::ED25519,
+            ED448 => {
+                // See https://github.com/briansmith/ring/issues/463
+                return Err(webpki::Error::UnsupportedSignatureAlgorithm);
+            }
+            // No support for RSA
+            RSA_PKCS1_SHA256 | RSA_PKCS1_SHA384 | RSA_PKCS1_SHA512 | RSA_PSS_SHA256
+            | RSA_PSS_SHA384 | RSA_PSS_SHA512 => {
+                return Err(webpki::Error::UnsupportedSignatureAlgorithm)
+            }
+            // Similarly, hash functions with an output length less than 256 bits
+            // MUST NOT be used, due to the possibility of collision attacks.
+            // In particular, MD5 and SHA1 MUST NOT be used.
+            RSA_PKCS1_SHA1 => return Err(webpki::Error::UnsupportedSignatureAlgorithm),
+            ECDSA_SHA1_Legacy => return Err(webpki::Error::UnsupportedSignatureAlgorithm),
+            Unknown(_) => return Err(webpki::Error::UnsupportedSignatureAlgorithm),
+            _ => return Err(webpki::Error::UnsupportedSignatureAlgorithm),
+        };
+        let spki = &self.certificate.tbs_certificate.subject_pki;
+        let key = signature::UnparsedPublicKey::new(
+            verification_algorithm,
+            spki.subject_public_key.as_ref(),
+        );
+
+        Ok(key)
+    }
+
+    /// This method validates the certificate according to libp2p TLS 1.3 specs.
+    /// The certificate MUST:
+    /// 1. be valid at the time it is received by the peer;
+    /// 2. use the NamedCurve encoding;
+    /// 3. use hash functions with an output length not less than 256 bits;
+    /// 4. be self signed;
+    /// 5. contain a valid signature in the specific libp2p extension.
+    fn verify(&self) -> Result<(), webpki::Error> {
+        use webpki::Error;
+
+        // The certificate MUST have NotBefore and NotAfter fields set
+        // such that the certificate is valid at the time it is received by the peer.
+        if !self.certificate.validity().is_valid() {
+            return Err(Error::InvalidCertValidity);
+        }
+
+        // Certificates MUST use the NamedCurve encoding for elliptic curve parameters.
+        // Similarly, hash functions with an output length less than 256 bits
+        // MUST NOT be used, due to the possibility of collision attacks.
+        // In particular, MD5 and SHA1 MUST NOT be used.
+        // Endpoints MUST abort the connection attempt if it is not used.
+        let signature_scheme = self.signature_scheme()?;
+        // Endpoints MUST abort the connection attempt if the certificate’s
+        // self-signature is not valid.
+        let raw_certificate = self.certificate.tbs_certificate.as_ref();
+        let signature = self.certificate.signature_value.as_ref();
+        // check if self signed
+        self.verify_signature(signature_scheme, raw_certificate, signature)
+            .map_err(|_| Error::SignatureAlgorithmMismatch)?;
+
+        let subject_pki = self.certificate.public_key().raw;
+
+        // The peer signs the concatenation of the string `libp2p-tls-handshake:`
+        // and the public key that it used to generate the certificate carrying
+        // the libp2p Public Key Extension, using its private host key.
+        let mut msg = vec![];
+        msg.extend(P2P_SIGNING_PREFIX);
+        msg.extend(subject_pki);
+
+        // This signature provides cryptographic proof that the peer was in possession
+        // of the private host key at the time the certificate was signed.
+        // Peers MUST verify the signature, and abort the connection attempt
+        // if signature verification fails.
+        let user_owns_sk = self
+            .extension
+            .public_key
+            .verify(&msg, &self.extension.signature)
+            .is_ok();
+        if !user_owns_sk {
+            return Err(Error::UnknownIssuer);
+        }
+
+        Ok(())
+    }
+
+    /// Return the signature scheme corresponding to [`AlgorithmIdentifier`]s
+    /// of `subject_pki` and `signature_algorithm`
+    /// according to <https://www.rfc-editor.org/rfc/rfc8446.html#section-4.2.3>.
+    fn signature_scheme(&self) -> Result<rustls::SignatureScheme, webpki::Error> {
+        // Certificates MUST use the NamedCurve encoding for elliptic curve parameters.
+        // Endpoints MUST abort the connection attempt if it is not used.
+        use oid_registry::*;
+        use rustls::SignatureScheme::*;
+
+        let signature_algorithm = &self.certificate.signature_algorithm;
+        let pki_algorithm = &self.certificate.tbs_certificate.subject_pki.algorithm;
+
+        if pki_algorithm.algorithm == OID_KEY_TYPE_EC_PUBLIC_KEY {
+            let signature_param = pki_algorithm
+                .parameters
+                .as_ref()
+                .ok_or(webpki::Error::BadDer)?
+                .as_oid()
+                .map_err(|_| webpki::Error::BadDer)?;
+            if signature_param == OID_EC_P256
+                && signature_algorithm.algorithm == OID_SIG_ECDSA_WITH_SHA256
+            {
+                return Ok(ECDSA_NISTP256_SHA256);
+            }
+            if signature_param == OID_NIST_EC_P384
+                && signature_algorithm.algorithm == OID_SIG_ECDSA_WITH_SHA384
+            {
+                return Ok(ECDSA_NISTP384_SHA384);
+            }
+            if signature_param == OID_NIST_EC_P521
+                && signature_algorithm.algorithm == OID_SIG_ECDSA_WITH_SHA512
+            {
+                return Ok(ECDSA_NISTP521_SHA512);
+            }
+            return Err(webpki::Error::UnsupportedSignatureAlgorithm);
+        }
+
+        if signature_algorithm.algorithm == OID_SIG_ED25519 {
+            return Ok(ED25519);
+        }
+        if signature_algorithm.algorithm == OID_SIG_ED448 {
+            return Ok(ED448);
+        }
+
+        Err(webpki::Error::UnsupportedSignatureAlgorithm)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn sanity_check() {
+        let secret_key = SecretKey::generate(rand::thread_rng());
+
+        let (cert, _) = generate(&secret_key).unwrap();
+        let parsed_cert = parse(&cert).unwrap();
+
+        assert!(parsed_cert.verify().is_ok());
+        assert_eq!(secret_key.public(), parsed_cert.extension.public_key);
+    }
+}
diff --git a/content-discovery/tls/src/lib.rs b/content-discovery/tls/src/lib.rs
new file mode 100644
index 0000000..ef22a33
--- /dev/null
+++ b/content-discovery/tls/src/lib.rs
@@ -0,0 +1,96 @@
+//! TLS configuration based on libp2p TLS specs.
+//!
+//! See <https://github.com/libp2p/specs/blob/master/tls/tls.md>.
+//! Based on rust-libp2p/transports/tls
+
+use std::sync::Arc;
+
+use iroh_base::{PublicKey, SecretKey};
+use quinn::crypto::rustls::{NoInitialCipherSuite, QuicClientConfig, QuicServerConfig};
+use tracing::warn;
+
+use self::certificate::AlwaysResolvesCert;
+
+pub mod certificate;
+mod verifier;
+
+/// Error for generating iroh p2p TLS configs.
+#[derive(Debug, thiserror::Error)]
+pub enum CreateConfigError {
+    /// Error generating the certificate.
+    #[error("Error generating the certificate")]
+    CertError(#[from] certificate::GenError),
+    /// Error creating QUIC config.
+    #[error("Error creating QUIC config")]
+    ConfigError(#[from] NoInitialCipherSuite),
+}
+
+/// Create a TLS client configuration.
+///
+/// If *keylog* is `true` this will enable logging of the pre-master key to the file in the
+/// `SSLKEYLOGFILE` environment variable.  This can be used to inspect the traffic for
+/// debugging purposes.
+pub fn make_client_config(
+    secret_key: &SecretKey,
+    remote_peer_id: Option<PublicKey>,
+    alpn_protocols: Vec<Vec<u8>>,
+    keylog: bool,
+) -> Result<QuicClientConfig, CreateConfigError> {
+    let (certificate, secret_key) = certificate::generate(secret_key)?;
+
+    let cert_resolver = Arc::new(
+        AlwaysResolvesCert::new(certificate, &secret_key)
+            .expect("Client cert key DER is valid; qed"),
+    );
+
+    let mut crypto = rustls::ClientConfig::builder_with_provider(Arc::new(
+        rustls::crypto::ring::default_provider(),
+    ))
+    .with_protocol_versions(verifier::PROTOCOL_VERSIONS)
+    .expect("version supported by ring")
+    .dangerous()
+    .with_custom_certificate_verifier(Arc::new(
+        verifier::Libp2pCertificateVerifier::with_remote_peer_id(remote_peer_id),
+    ))
+    .with_client_cert_resolver(cert_resolver);
+    crypto.alpn_protocols = alpn_protocols;
+    if keylog {
+        warn!("enabling SSLKEYLOGFILE for TLS pre-master keys");
+        crypto.key_log = Arc::new(rustls::KeyLogFile::new());
+    }
+    let config = crypto.try_into()?;
+    Ok(config)
+}
+
+/// Create a TLS server configuration.
+///
+/// If *keylog* is `true` this will enable logging of the pre-master key to the file in the
+/// `SSLKEYLOGFILE` environment variable.  This can be used to inspect the traffic for
+/// debugging purposes.
+pub fn make_server_config(
+    secret_key: &SecretKey,
+    alpn_protocols: Vec<Vec<u8>>,
+    keylog: bool,
+) -> Result<QuicServerConfig, CreateConfigError> {
+    let (certificate, secret_key) = certificate::generate(secret_key)?;
+
+    let cert_resolver = Arc::new(
+        AlwaysResolvesCert::new(certificate, &secret_key)
+            .expect("Server cert key DER is valid; qed"),
+    );
+
+    let mut crypto = rustls::ServerConfig::builder_with_provider(Arc::new(
+        rustls::crypto::ring::default_provider(),
+    ))
+    .with_protocol_versions(verifier::PROTOCOL_VERSIONS)
+    .expect("fixed config")
+    .with_client_cert_verifier(Arc::new(verifier::Libp2pCertificateVerifier::new()))
+    .with_cert_resolver(cert_resolver);
+    crypto.alpn_protocols = alpn_protocols;
+    if keylog {
+        warn!("enabling SSLKEYLOGFILE for TLS pre-master keys");
+        crypto.key_log = Arc::new(rustls::KeyLogFile::new());
+    }
+    let config = crypto.try_into()?;
+    Ok(config)
+}
diff --git a/content-discovery/tls/src/verifier.rs b/content-discovery/tls/src/verifier.rs
new file mode 100644
index 0000000..8841d77
--- /dev/null
+++ b/content-discovery/tls/src/verifier.rs
@@ -0,0 +1,227 @@
+//! TLS 1.3 certificates and handshakes handling for libp2p
+//!
+//! This module handles a verification of a client/server certificate chain
+//! and signatures allegedly by the given certificates.
+//!
+//! Based on rust-libp2p/transports/tls/src/verifier.rs originally licensed under MIT by Parity
+//! Technologies (UK) Ltd.
+use std::sync::Arc;
+
+use iroh_base::PublicKey;
+use rustls::{
+    client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
+    pki_types::CertificateDer as Certificate,
+    server::danger::{ClientCertVerified, ClientCertVerifier},
+    CertificateError, DigitallySignedStruct, DistinguishedName, OtherError, PeerMisbehaved,
+    SignatureScheme, SupportedProtocolVersion,
+};
+
+use super::certificate;
+
+/// The protocol versions supported by this verifier.
+///
+/// The spec says:
+///
+/// > The libp2p handshake uses TLS 1.3 (and higher).
+/// > Endpoints MUST NOT negotiate lower TLS versions.
+pub static PROTOCOL_VERSIONS: &[&SupportedProtocolVersion] = &[&rustls::version::TLS13];
+
+/// Implementation of the `rustls` certificate verification traits for libp2p.
+///
+/// Only TLS 1.3 is supported. TLS 1.2 should be disabled in the configuration of `rustls`.
+#[derive(Debug)]
+pub struct Libp2pCertificateVerifier {
+    /// The peer ID we intend to connect to
+    remote_peer_id: Option<PublicKey>,
+}
+
+/// libp2p requires the following of X.509 server certificate chains:
+///
+/// - Exactly one certificate must be presented.
+/// - The certificate must be self-signed.
+/// - The certificate must have a valid libp2p extension that includes a
+///   signature of its public key.
+impl Libp2pCertificateVerifier {
+    pub fn new() -> Self {
+        Self {
+            remote_peer_id: None,
+        }
+    }
+    pub fn with_remote_peer_id(remote_peer_id: Option<PublicKey>) -> Self {
+        Self { remote_peer_id }
+    }
+
+    /// Return the list of SignatureSchemes that this verifier will handle,
+    /// in `verify_tls12_signature` and `verify_tls13_signature` calls.
+    ///
+    /// This should be in priority order, with the most preferred first.
+    fn verification_schemes() -> Vec<SignatureScheme> {
+        vec![
+            // TODO SignatureScheme::ECDSA_NISTP521_SHA512 is not supported by `ring` yet
+            SignatureScheme::ECDSA_NISTP384_SHA384,
+            SignatureScheme::ECDSA_NISTP256_SHA256,
+            // TODO SignatureScheme::ED448 is not supported by `ring` yet
+            SignatureScheme::ED25519,
+            // In particular, RSA SHOULD NOT be used.
+        ]
+    }
+}
+
+impl ServerCertVerifier for Libp2pCertificateVerifier {
+    fn verify_server_cert(
+        &self,
+        end_entity: &Certificate,
+        intermediates: &[Certificate],
+        _server_name: &rustls::pki_types::ServerName,
+        _ocsp_response: &[u8],
+        _now: rustls::pki_types::UnixTime,
+    ) -> Result<ServerCertVerified, rustls::Error> {
+        let peer_id = verify_presented_certs(end_entity, intermediates)?;
+
+        if let Some(ref remote_peer_id) = self.remote_peer_id {
+            // The public host key allows the peer to calculate the peer ID of the peer
+            // it is connecting to. Clients MUST verify that the peer ID derived from
+            // the certificate matches the peer ID they intended to connect to,
+            // and MUST abort the connection if there is a mismatch.
+            if remote_peer_id != &peer_id {
+                return Err(rustls::Error::PeerMisbehaved(
+                    PeerMisbehaved::BadCertChainExtensions,
+                ));
+            }
+        }
+
+        Ok(ServerCertVerified::assertion())
+    }
+
+    fn verify_tls12_signature(
+        &self,
+        _message: &[u8],
+        _cert: &Certificate,
+        _dss: &DigitallySignedStruct,
+    ) -> Result<HandshakeSignatureValid, rustls::Error> {
+        unreachable!("`PROTOCOL_VERSIONS` only allows TLS 1.3")
+    }
+
+    fn verify_tls13_signature(
+        &self,
+        message: &[u8],
+        cert: &Certificate,
+        dss: &DigitallySignedStruct,
+    ) -> Result<HandshakeSignatureValid, rustls::Error> {
+        verify_tls13_signature(cert, dss.scheme, message, dss.signature())
+    }
+
+    fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
+        Self::verification_schemes()
+    }
+}
+
+/// libp2p requires the following of X.509 client certificate chains:
+///
+/// - Exactly one certificate must be presented. In particular, client
+///   authentication is mandatory in libp2p.
+/// - The certificate must be self-signed.
+/// - The certificate must have a valid libp2p extension that includes a
+///   signature of its public key.
+impl ClientCertVerifier for Libp2pCertificateVerifier {
+    fn offer_client_auth(&self) -> bool {
+        true
+    }
+
+    fn verify_client_cert(
+        &self,
+        end_entity: &Certificate,
+        intermediates: &[Certificate],
+        _now: rustls::pki_types::UnixTime,
+    ) -> Result<ClientCertVerified, rustls::Error> {
+        verify_presented_certs(end_entity, intermediates)?;
+
+        Ok(ClientCertVerified::assertion())
+    }
+
+    fn verify_tls12_signature(
+        &self,
+        _message: &[u8],
+        _cert: &Certificate,
+        _dss: &DigitallySignedStruct,
+    ) -> Result<HandshakeSignatureValid, rustls::Error> {
+        unreachable!("`PROTOCOL_VERSIONS` only allows TLS 1.3")
+    }
+
+    fn verify_tls13_signature(
+        &self,
+        message: &[u8],
+        cert: &Certificate,
+        dss: &DigitallySignedStruct,
+    ) -> Result<HandshakeSignatureValid, rustls::Error> {
+        verify_tls13_signature(cert, dss.scheme, message, dss.signature())
+    }
+
+    fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
+        Self::verification_schemes()
+    }
+
+    fn root_hint_subjects(&self) -> &[DistinguishedName] {
+        &[][..]
+    }
+}
+
+/// When receiving the certificate chain, an endpoint
+/// MUST check these conditions and abort the connection attempt if
+/// (a) the presented certificate is not yet valid, OR
+/// (b) if it is expired.
+/// Endpoints MUST abort the connection attempt if more than one certificate is received,
+/// or if the certificate’s self-signature is not valid.
+fn verify_presented_certs(
+    end_entity: &Certificate,
+    intermediates: &[Certificate],
+) -> Result<PublicKey, rustls::Error> {
+    if !intermediates.is_empty() {
+        return Err(rustls::Error::General(
+            "libp2p-tls requires exactly one certificate".into(),
+        ));
+    }
+
+    let cert = certificate::parse(end_entity)?;
+
+    Ok(cert.peer_id())
+}
+
+fn verify_tls13_signature(
+    cert: &Certificate,
+    signature_scheme: SignatureScheme,
+    message: &[u8],
+    signature: &[u8],
+) -> Result<HandshakeSignatureValid, rustls::Error> {
+    certificate::parse(cert)?.verify_signature(signature_scheme, message, signature)?;
+
+    Ok(HandshakeSignatureValid::assertion())
+}
+
+impl From<certificate::ParseError> for rustls::Error {
+    fn from(certificate::ParseError(e): certificate::ParseError) -> Self {
+        use webpki::Error::*;
+        match e {
+            BadDer => rustls::Error::InvalidCertificate(CertificateError::BadEncoding),
+            e => {
+                rustls::Error::InvalidCertificate(CertificateError::Other(OtherError(Arc::new(e))))
+            }
+        }
+    }
+}
+impl From<certificate::VerificationError> for rustls::Error {
+    fn from(certificate::VerificationError(e): certificate::VerificationError) -> Self {
+        use webpki::Error::*;
+        match e {
+            InvalidSignatureForPublicKey => {
+                rustls::Error::InvalidCertificate(CertificateError::BadSignature)
+            }
+            UnsupportedSignatureAlgorithm | UnsupportedSignatureAlgorithmForPublicKey => {
+                rustls::Error::InvalidCertificate(CertificateError::BadSignature)
+            }
+            e => {
+                rustls::Error::InvalidCertificate(CertificateError::Other(OtherError(Arc::new(e))))
+            }
+        }
+    }
+}
diff --git a/h3-iroh/Cargo.toml b/h3-iroh/Cargo.toml
index da5edf2..82d2a9d 100644
--- a/h3-iroh/Cargo.toml
+++ b/h3-iroh/Cargo.toml
@@ -15,7 +15,8 @@ http-body = { version = "1", optional = true }
 http-body-util = { version = "0.1", optional = true }
 hyper = { version = "1.5", optional = true }
 hyper-util = { version = "0.1", optional = true }
-iroh = "0.29"
+iroh = "0.30"
+iroh-base = { version = "0.30", features = ["ticket"] }
 tokio = { version = "1", features = ["io-util"], default-features = false}
 tokio-util = "0.7"
 tower = { version = "0.5", optional = true }
@@ -38,3 +39,7 @@ anyhow = "1"
 clap = { version = "4", features = ["derive"] }
 http = "1"
 tracing-subscriber = "0.3"
+
+[[example]]
+name = "server-axum"
+required-features = ["axum"]
diff --git a/h3-iroh/examples/client.rs b/h3-iroh/examples/client.rs
index 6c03009..f78564c 100644
--- a/h3-iroh/examples/client.rs
+++ b/h3-iroh/examples/client.rs
@@ -3,8 +3,8 @@ use std::str::FromStr;
 
 use anyhow::{bail, Context, Result};
 use clap::Parser;
-use iroh::ticket::NodeTicket;
 use iroh::NodeAddr;
+use iroh_base::ticket::NodeTicket;
 use tokio::io::AsyncWriteExt;
 use tracing::info;
 
diff --git a/h3-iroh/examples/server-axum.rs b/h3-iroh/examples/server-axum.rs
index 5ffdc37..d7d1bd8 100644
--- a/h3-iroh/examples/server-axum.rs
+++ b/h3-iroh/examples/server-axum.rs
@@ -6,8 +6,7 @@ use anyhow::Result;
 use axum::response::Html;
 use axum::routing::get;
 use axum::Router;
-use futures::StreamExt;
-use iroh::ticket::NodeTicket;
+use iroh_base::ticket::NodeTicket;
 use tracing::info;
 
 #[tokio::main]
@@ -23,8 +22,8 @@ async fn main() -> Result<()> {
     info!("accepting connections on node: {}", ep.node_id());
 
     // Wait for direct addresses and a RelayUrl before printing a NodeTicket.
-    ep.direct_addresses().next().await;
-    ep.watch_home_relay().next().await;
+    ep.direct_addresses().initialized().await?;
+    ep.home_relay().initialized().await?;
     let ticket = NodeTicket::new(ep.node_addr().await?);
     info!("node ticket: {ticket}");
     info!("run: cargo run --example client -- iroh+h3://{ticket}/");
diff --git a/h3-iroh/examples/server.rs b/h3-iroh/examples/server.rs
index 4f112e6..b4d5f74 100644
--- a/h3-iroh/examples/server.rs
+++ b/h3-iroh/examples/server.rs
@@ -8,13 +8,12 @@ use std::sync::Arc;
 use anyhow::{bail, Result};
 use bytes::{Bytes, BytesMut};
 use clap::Parser;
-use futures::StreamExt;
 use h3::error::ErrorLevel;
 use h3::quic::BidiStream;
 use h3::server::RequestStream;
 use http::{Request, StatusCode};
 use iroh::endpoint::{self, Incoming};
-use iroh::ticket::NodeTicket;
+use iroh_base::ticket::NodeTicket;
 use tokio::fs::File;
 use tokio::io::AsyncReadExt;
 use tracing::{debug, error, field, info, info_span, Instrument, Span};
@@ -55,8 +54,8 @@ async fn main() -> Result<()> {
     info!("accepting connections on node: {}", ep.node_id());
 
     // Wait for direct addresses and a RelayUrl before printing a NodeTicket.
-    ep.direct_addresses().next().await;
-    ep.watch_home_relay().next().await;
+    ep.direct_addresses().initialized().await?;
+    ep.home_relay().initialized().await?;
     let ticket = NodeTicket::new(ep.node_addr().await?);
     info!("node ticket: {ticket}");
     info!("run e.g.: cargo run --example client -- iroh+h3://{ticket}/Cargo.toml");
diff --git a/iroh-dag-sync/Cargo.toml b/iroh-dag-sync/Cargo.toml
index b13c5e6..7f69819 100644
--- a/iroh-dag-sync/Cargo.toml
+++ b/iroh-dag-sync/Cargo.toml
@@ -4,9 +4,10 @@ version = "0.1.0"
 edition = "2021"
 
 [dependencies]
-iroh-blobs = "0.29"
-iroh-gossip = "0.29"
-iroh = "0.29"
+iroh-blobs = "0.30"
+iroh-gossip = "0.30"
+iroh = "0.30"
+iroh-base = { version ="0.30", features = ["ticket"] }
 iroh-car = "0.5.0"
 redb = "2.1.1"
 clap = { version = "4.5.7", features = ["derive"] }
@@ -24,6 +25,7 @@ iroh-quinn = "0.12"
 bytes = "1.6.0"
 hex = "0.4.3"
 ron = "0.8.1"
+rand = "0.8.5"
 tracing = "0.1.40"
 serde_bytes = "0.11.14"
 ipld-core = "0.4.1"
diff --git a/iroh-dag-sync/src/main.rs b/iroh-dag-sync/src/main.rs
index 190178e..cdbdabc 100644
--- a/iroh-dag-sync/src/main.rs
+++ b/iroh-dag-sync/src/main.rs
@@ -6,8 +6,8 @@ use clap::Parser;
 use futures_lite::StreamExt;
 use ipld_core::codec::Links;
 use iroh::discovery::{dns::DnsDiscovery, pkarr::PkarrPublisher, ConcurrentDiscovery};
-use iroh::ticket::NodeTicket;
 use iroh::NodeAddr;
+use iroh_base::ticket::NodeTicket;
 use iroh_blobs::store::{Map, MapEntry};
 use iroh_blobs::{store::Store, BlobFormat};
 use iroh_car::CarReader;
@@ -19,7 +19,6 @@ use tables::{ReadOnlyTables, ReadableTables, Tables};
 use tokio::io::AsyncWriteExt;
 use tokio_util::task::LocalPoolHandle;
 use traversal::{get_traversal, Traversal};
-use util::wait_for_relay;
 
 mod args;
 mod protocol;
@@ -127,10 +126,13 @@ async fn main() -> anyhow::Result<()> {
         args::SubCommand::Node(args) => {
             let endpoint =
                 create_endpoint(args.net.iroh_ipv4_addr, args.net.iroh_ipv6_addr).await?;
-            wait_for_relay(&endpoint).await?;
+            endpoint.home_relay().initialized().await?;
             let addr = endpoint.node_addr().await?;
             println!("Node id:\n{}", addr.node_id);
-            println!("Listening on {:#?}", addr.info);
+            println!(
+                "Listening on {:#?}, {:#?}",
+                addr.relay_url, addr.direct_addresses
+            );
             println!("ticket:\n{}", NodeTicket::new(addr.clone()));
             while let Some(incoming) = endpoint.accept().await {
                 let mut connecting = incoming.accept()?;
diff --git a/iroh-dag-sync/src/util.rs b/iroh-dag-sync/src/util.rs
index ee681d2..4398354 100644
--- a/iroh-dag-sync/src/util.rs
+++ b/iroh-dag-sync/src/util.rs
@@ -1,13 +1,5 @@
 use anyhow::anyhow;
-use iroh::{key::SecretKey, Endpoint};
-
-// Wait for the endpoint to figure out its relay address.
-pub async fn wait_for_relay(endpoint: &Endpoint) -> anyhow::Result<()> {
-    while endpoint.home_relay().is_none() {
-        tokio::time::sleep(std::time::Duration::from_millis(50)).await;
-    }
-    Ok(())
-}
+use iroh::SecretKey;
 
 /// Get the secret key from a file or generate a new one.
 pub fn get_or_create_secret() -> anyhow::Result<SecretKey> {
@@ -19,7 +11,7 @@ pub fn get_or_create_secret() -> anyhow::Result<SecretKey> {
             Ok(secret)
         }
         Err(_) => {
-            let secret = SecretKey::generate();
+            let secret = SecretKey::generate(rand::thread_rng());
             std::fs::write(FILENAME, secret.to_bytes())?;
             Ok(secret)
         }
diff --git a/iroh-pkarr-naming-system/Cargo.toml b/iroh-pkarr-naming-system/Cargo.toml
index 100c30f..d01d41d 100644
--- a/iroh-pkarr-naming-system/Cargo.toml
+++ b/iroh-pkarr-naming-system/Cargo.toml
@@ -8,8 +8,8 @@ edition = "2021"
 [dependencies]
 anyhow = "1.0.79"
 derive_more = "0.99.17"
-iroh = "0.29"
-iroh-blobs = "0.29"
+iroh = "0.30"
+iroh-blobs = "0.30"
 pkarr = { version = "1.0.1", features = ["async", "dht"] }
 tokio = "1.35.1"
 tokio-util = "0.7.12"
diff --git a/iroh-pkarr-naming-system/src/lib.rs b/iroh-pkarr-naming-system/src/lib.rs
index 8c6369d..3ab24d3 100644
--- a/iroh-pkarr-naming-system/src/lib.rs
+++ b/iroh-pkarr-naming-system/src/lib.rs
@@ -6,7 +6,7 @@ use std::{
 };
 
 use anyhow::Context;
-use iroh::key::SecretKey;
+use iroh::SecretKey;
 use iroh_blobs::HashAndFormat;
 use pkarr::{
     dns::{
@@ -50,7 +50,7 @@ pub struct IPNS(Arc<Inner>);
 #[derive(Debug, Default)]
 struct Inner {
     pkarr: Arc<pkarr::PkarrClient>,
-    packets: Mutex<BTreeMap<iroh::key::PublicKey, (Record, AbortOnDropHandle<()>)>>,
+    packets: Mutex<BTreeMap<iroh::PublicKey, (Record, AbortOnDropHandle<()>)>>,
 }
 
 impl IPNS {
@@ -89,10 +89,7 @@ impl IPNS {
     }
 
     /// Resolve a record for a public key.
-    pub async fn resolve(
-        &self,
-        public_key: iroh::key::PublicKey,
-    ) -> anyhow::Result<Option<Record>> {
+    pub async fn resolve(&self, public_key: iroh::PublicKey) -> anyhow::Result<Option<Record>> {
         let public_key =
             pkarr::PublicKey::try_from(*public_key.as_bytes()).context("invalid public key")?;
         let packet = self.0.pkarr.resolve(public_key).await;
diff --git a/iroh-s3-bao-store/Cargo.toml b/iroh-s3-bao-store/Cargo.toml
index 0a7c6b2..75700b1 100644
--- a/iroh-s3-bao-store/Cargo.toml
+++ b/iroh-s3-bao-store/Cargo.toml
@@ -21,8 +21,8 @@ flume = "0.11.0"
 futures-lite = "2.3"
 hex = "0.4.3"
 indicatif = "0.17.7"
-iroh = "0.29"
-iroh-blobs = "0.29"
+iroh = "0.30"
+iroh-blobs = "0.30"
 iroh-io = { version = "0.6", features = ["x-http"] }
 num_cpus = "1.16.0"
 rand = "0.8.5"
diff --git a/iroh-s3-bao-store/src/main.rs b/iroh-s3-bao-store/src/main.rs
index a9d2fce..f39fb7d 100644
--- a/iroh-s3-bao-store/src/main.rs
+++ b/iroh-s3-bao-store/src/main.rs
@@ -3,10 +3,11 @@ use clap::{Parser, Subcommand};
 use indicatif::{
     HumanBytes, HumanDuration, MultiProgress, ProgressBar, ProgressDrawTarget, ProgressStyle,
 };
-use iroh::{key::SecretKey, ticket::BlobTicket, Endpoint, NodeAddr};
-use iroh_blobs::util::local_pool::LocalPool;
+use iroh::{Endpoint, NodeAddr, SecretKey};
 use iroh_blobs::{
     provider::{self, handle_connection, CustomEventSender, EventSender},
+    ticket::BlobTicket,
+    util::local_pool::LocalPool,
     BlobFormat,
 };
 use iroh_io::{AsyncSliceReaderExt, HttpAdapter};
@@ -127,7 +128,7 @@ fn get_or_create_secret(print: bool) -> anyhow::Result<SecretKey> {
     match std::env::var("IROH_SECRET") {
         Ok(secret) => SecretKey::from_str(&secret).context("invalid secret"),
         Err(_) => {
-            let key = SecretKey::generate();
+            let key = SecretKey::generate(rand::thread_rng());
             if print {
                 eprintln!("using secret key {}", key);
             }
@@ -244,9 +245,7 @@ async fn serve_db(
     // wait for the endpoint to be ready
     let endpoint = builder.bind().await?;
     // wait for the endpoint to figure out its address before making a ticket
-    while endpoint.home_relay().is_none() {
-        tokio::time::sleep(std::time::Duration::from_millis(100)).await;
-    }
+    endpoint.home_relay().initialized().await?;
     // make a ticket
     let addr = endpoint.node_addr().await?;
     on_addr(addr)?;