diff --git a/crates/arti-client/src/address.rs b/crates/arti-client/src/address.rs
index 569688fb019ffdd25ef018497afd591caa4a476a..4f5582144a84eaf754a8aef88e1500c7534c15b9 100644
--- a/crates/arti-client/src/address.rs
+++ b/crates/arti-client/src/address.rs
@@ -99,6 +99,16 @@ pub struct TorAddr {
 }
 
 impl TorAddr {
+    /// Construct a TorAddr from its consituent parts, rejecting it if the
+    /// port is zero.
+    fn new(host: Host, port: u16) -> Result<Self, TorAddrError> {
+        if port == 0 {
+            Err(TorAddrError::BadPort)
+        } else {
+            Ok(TorAddr { host, port })
+        }
+    }
+
     /// Construct a `TorAddr` from any object that implements
     /// [`IntoTorAddr`].
     pub fn from<A: IntoTorAddr>(addr: A) -> Result<Self, TorAddrError> {
@@ -165,7 +175,7 @@ impl std::fmt::Display for TorAddr {
 }
 
 /// An error created while making or using a [`TorAddr`].
-#[derive(Debug, Error, Clone)]
+#[derive(Debug, Error, Clone, Eq, PartialEq)]
 #[non_exhaustive]
 pub enum TorAddrError {
     /// Tried to parse a string that can never be interpreted as a valid host.
@@ -174,7 +184,7 @@ pub enum TorAddrError {
     /// Tried to parse a string as an `address:port`, but it had no port.
     #[error("No port found in string")]
     NoPort,
-    /// Tried to parse a port that wasn't a valid `u16`.
+    /// Tried to parse a port that wasn't a valid nonzero `u16`.
     #[error("Could not parse port")]
     BadPort,
 }
@@ -238,19 +248,12 @@ impl<A: IntoTorAddr + Clone> IntoTorAddr for &A {
 impl IntoTorAddr for &str {
     fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
         if let Ok(sa) = SocketAddr::from_str(self) {
-            Ok(TorAddr {
-                host: Host::Ip(sa.ip()),
-                port: sa.port(),
-            })
+            TorAddr::new(Host::Ip(sa.ip()), sa.port())
         } else {
             let (host, port) = self.rsplit_once(':').ok_or(TorAddrError::NoPort)?;
             let host = host.parse()?;
             let port = port.parse().map_err(|_| TorAddrError::BadPort)?;
-            if port == 0 {
-                Err(TorAddrError::BadPort)
-            } else {
-                Ok(TorAddr { host, port })
-            }
+            TorAddr::new(host, port)
         }
     }
 }
@@ -272,7 +275,7 @@ impl IntoTorAddr for (&str, u16) {
     fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
         let (host, port) = self;
         let host = host.parse()?;
-        Ok(TorAddr { host, port })
+        TorAddr::new(host, port)
     }
 }
 
@@ -292,30 +295,21 @@ impl<T: DangerouslyIntoTorAddr + Clone> DangerouslyIntoTorAddr for &T {
 impl DangerouslyIntoTorAddr for (IpAddr, u16) {
     fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
         let (addr, port) = self;
-        Ok(TorAddr {
-            host: Host::Ip(addr),
-            port,
-        })
+        TorAddr::new(Host::Ip(addr), port)
     }
 }
 
 impl DangerouslyIntoTorAddr for (Ipv4Addr, u16) {
     fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
         let (addr, port) = self;
-        Ok(TorAddr {
-            host: Host::Ip(addr.into()),
-            port,
-        })
+        TorAddr::new(Host::Ip(addr.into()), port)
     }
 }
 
 impl DangerouslyIntoTorAddr for (Ipv6Addr, u16) {
     fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
         let (addr, port) = self;
-        Ok(TorAddr {
-            host: Host::Ip(addr.into()),
-            port,
-        })
+        TorAddr::new(Host::Ip(addr.into()), port)
     }
 }
 
@@ -457,6 +451,18 @@ mod test {
         assert_eq!(sap("example.com:80"), ("example.com".to_owned(), 80));
     }
 
+    #[test]
+    fn bad_ports() {
+        assert_eq!(
+            TorAddr::from("www.example.com:squirrel"),
+            Err(TorAddrError::BadPort)
+        );
+        assert_eq!(
+            TorAddr::from("www.example.com:0"),
+            Err(TorAddrError::BadPort)
+        );
+    }
+
     #[test]
     fn convert_safe() {
         fn check<A: IntoTorAddr>(a: A, s: &str) {
diff --git a/doc/semver_status.md b/doc/semver_status.md
index a5ff4d2d1cb5624c0117b454a2813a3b0abd914e..f5333f257a2de3dbec1213adb9765e2c04c4c489 100644
--- a/doc/semver_status.md
+++ b/doc/semver_status.md
@@ -23,3 +23,6 @@ tor-proto: MODIFIED
   (New constructor for HsNtorClientInput)
 tor-netdoc: BREAKING
   (No more tap_key function in MicrodescBuilder)
+
+arti-client: MODIFIED
+  (PartialEq, Eq for TorAddrError)