diff --git a/veilid-core/src/routing_table/route_spec_store/route_set_spec_detail.rs b/veilid-core/src/routing_table/route_spec_store/route_set_spec_detail.rs
index fd6a39ae..2b3ec72e 100644
--- a/veilid-core/src/routing_table/route_spec_store/route_set_spec_detail.rs
+++ b/veilid-core/src/routing_table/route_spec_store/route_set_spec_detail.rs
@@ -90,6 +90,14 @@ impl RouteSetSpecDetail {
             Sequencing::EnsureOrdered => self.can_do_sequenced,
         }
     }
+    pub fn contains_nodes(&self, nodes: &[TypedKey]) -> bool {
+        for h in self.hop_node_refs {
+            if h.node_ids().contains_any(nodes) {
+                return true;
+            }
+        }
+        false
+    }
 
     /// Generate a key for the cache that can be used to uniquely identify this route's contents
     pub fn make_cache_key(&self) -> Vec<u8> {
diff --git a/veilid-core/src/routing_table/route_spec_store/route_spec_store.rs b/veilid-core/src/routing_table/route_spec_store/route_spec_store.rs
index 453afa34..bd9f0c3e 100644
--- a/veilid-core/src/routing_table/route_spec_store/route_spec_store.rs
+++ b/veilid-core/src/routing_table/route_spec_store/route_spec_store.rs
@@ -537,7 +537,7 @@ impl RouteSpecStore {
         last_hop_id: PublicKey,
         callback: F,
     ) -> Option<R> 
-    where F: FnOnce(&RouteSpecDetail) -> R, 
+    where F: FnOnce(&RouteSetSpecDetail, &RouteSpecDetail) -> R, 
     R: fmt::Debug,
     {
         let inner = &*self.inner.lock();
@@ -585,7 +585,7 @@ impl RouteSpecStore {
             }
         }
         // We got the correct signatures, return a key and response safety spec     
-        Some(callback(rsd))
+        Some(callback(rssd, rsd))
     }
 
     #[instrument(level = "trace", skip(self), ret, err)]
@@ -593,15 +593,22 @@ impl RouteSpecStore {
         // Make loopback route to test with
         let dest = {
 
-            // Match the private route's hop length for safety route length
-            let hop_count = {
+            // Get the best private route for this id
+            let (key, hop_count) = {
                 let inner = &mut *self.inner.lock();
                 let Some(rssd) = inner.content.get_detail(&private_route_id) else {
                     bail!("route id not allocated");
                 };
-                rssd.hop_count()
+                let Some(key) = rssd.get_best_route_set_key() else {
+                    bail!("no best key to test allocated route");
+                };
+                // Match the private route's hop length for safety route length
+                let hop_count = rssd.hop_count(); 
+                (key, hop_count)
             };
 
+            // Get the private route to send to
+            let private_route = self.assemble_private_route(&key, None)?;
             // Always test routes with safety routes that are more likely to succeed
             let stability = Stability::Reliable;
             // Routes can test with whatever sequencing they were allocated with
@@ -639,6 +646,12 @@ impl RouteSpecStore {
         
         // Make private route test
         let dest = {
+            
+            // Get the route to test
+            let Some(private_route) = self.best_remote_private_route(&private_route_id) else {
+                bail!("no best key to test remote route");
+            };
+
             // Get a safety route that is good enough
             let safety_spec = SafetySpec {
                 preferred_route: None,
@@ -722,7 +735,7 @@ impl RouteSpecStore {
         stability: Stability,
         sequencing: Sequencing,
         directions: DirectionSet,
-        avoid_node_ids: &[PublicKey],
+        avoid_nodes: &[TypedKey],
     ) -> Option<PublicKey> {
         let cur_ts = get_aligned_timestamp();
 
@@ -740,7 +753,7 @@ impl RouteSpecStore {
             {
                 let mut avoid = false;
                 for h in &detail.1.hops {
-                    if avoid_node_ids.contains(h) {
+                    if avoid_nodes.contains(h) {
                         avoid = true;
                         break;
                     }
@@ -1054,7 +1067,7 @@ impl RouteSpecStore {
         Ok(Some(compiled_route))
     }
 
-    /// Get a route that matches a particular safety spec
+    /// Get an allocated route that matches a particular safety spec
     #[instrument(level = "trace", skip(self, inner, rti), ret, err)]
     fn get_route_for_safety_spec_inner(
         &self,
@@ -1076,12 +1089,12 @@ impl RouteSpecStore {
 
         // See if the preferred route is here
         if let Some(preferred_route) = safety_spec.preferred_route {
-            if let Some(preferred_rsd) = inner.content.details.get(&preferred_route) {
+            if let Some(preferred_rssd) = inner.content.get_detail(&preferred_route) {
                 // Only use the preferred route if it has the desire crypto kind
-                if preferred_rsd.crypto_kind == crypto_kind {    
-                    // Only use the preferred route if it doesn't end with the avoid nodes
-                    if !avoid_nodes.contains(&TypedKey::new(crypto_kind, preferred_rsd.hops.last().cloned().unwrap())) {
-                        return Ok(Some(preferred_route));
+                if let Some(preferred_key) = preferred_rssd.get_route_set_keys().get(crypto_kind) {
+                    // Only use the preferred route if it doesn't contain the avoid nodes
+                    if !preferred_rssd.contains_nodes(avoid_nodes) {
+                        return Ok(Some(preferred_key.key));
                     }
                 }
             }
@@ -1095,7 +1108,7 @@ impl RouteSpecStore {
             safety_spec.stability,
             safety_spec.sequencing,
             direction,
-            avoid_node_ids,
+            avoid_nodes,
         ) {
             // Found a route to use
             sr_pubkey
@@ -1105,11 +1118,12 @@ impl RouteSpecStore {
                 .allocate_route_inner(
                     inner,
                     rti,
+                    crypto_kind, ???
                     safety_spec.stability,
                     safety_spec.sequencing,
                     safety_spec.hop_count,
                     direction,
-                    avoid_node_ids,
+                    avoid_nodes,
                 )
                 .map_err(RPCError::internal)?
             {
diff --git a/veilid-core/src/rpc_processor/rpc_route.rs b/veilid-core/src/rpc_processor/rpc_route.rs
index 5c7c1d0c..3d00c8e1 100644
--- a/veilid-core/src/rpc_processor/rpc_route.rs
+++ b/veilid-core/src/rpc_processor/rpc_route.rs
@@ -194,7 +194,7 @@ impl RPCProcessor {
                 sender_id,
                 |rsd| {
                     (
-                        rsd.get_secret_key(),
+                        rsd.secret_key,
                         SafetySpec {
                             preferred_route: Some(pr_pubkey),
                             hop_count: rsd.hop_count(),
@@ -243,7 +243,7 @@ impl RPCProcessor {
 
         // If the private route public key is our node id, then this was sent via safety route to our node directly
         // so there will be no signatures to validate
-        if pr_pubkey == self.routing_table.node_id() {
+        if self.routing_table.node_ids().contains(&pr_pubkey) {
             // The private route was a stub
             self.process_safety_routed_operation(detail, routed_operation, remote_sr_pubkey)
         } else {
@@ -314,13 +314,20 @@ impl RPCProcessor {
     /// Decrypt route hop data and sign routed operation
     pub(crate) fn decrypt_private_route_hop_data(&self, route_hop_data: &RouteHopData, pr_pubkey: &TypedKey, route_operation: &mut RoutedOperation) -> Result<NetworkResult<RouteHop>, RPCError>
     {
+        // Get crypto kind
+        let crypto_kind = pr_pubkey.kind;
+        let Some(vcrypto) = self.crypto.get(crypto_kind) else {
+            return Ok(NetworkResult::invalid_message(
+                "private route hop data crypto is not supported",
+            ));
+        };
+
         // Decrypt the blob with DEC(nonce, DH(the PR's public key, this hop's secret)
-        let node_id_secret = self.routing_table.node_id_secret();
-        let dh_secret = self
-            .crypto
-            .cached_dh(&pr_pubkey, &node_id_secret)
+        let node_id_secret = self.routing_table.node_id_secret(crypto_kind);
+        let dh_secret = vcrypto
+            .cached_dh(&pr_pubkey.key, &node_id_secret)
             .map_err(RPCError::protocol)?;
-        let dec_blob_data = match Crypto::decrypt_aead(
+        let dec_blob_data = match vcrypto.decrypt_aead(
             &route_hop_data.blob,
             &route_hop_data.nonce,
             &dh_secret,
@@ -338,15 +345,15 @@ impl RPCProcessor {
             let rh_reader = dec_blob_reader
                 .get_root::<veilid_capnp::route_hop::Reader>()
                 .map_err(RPCError::protocol)?;
-            decode_route_hop(&rh_reader)?
+            decode_route_hop(&rh_reader, self.crypto.clone())?
         };
 
         // Sign the operation if this is not our last hop
         // as the last hop is already signed by the envelope
         if route_hop.next_hop.is_some() {
-            let node_id = self.routing_table.node_id();
-            let node_id_secret = self.routing_table.node_id_secret();
-            let sig = sign(&node_id, &node_id_secret, &route_operation.data)
+            let node_id = self.routing_table.node_id(crypto_kind);
+            let node_id_secret = self.routing_table.node_id_secret(crypto_kind);
+            let sig = vcrypto.sign(&node_id.key, &node_id_secret, &route_operation.data)
                 .map_err(RPCError::internal)?;
             route_operation.signatures.push(sig);
         }
@@ -378,25 +385,24 @@ impl RPCProcessor {
             _ => panic!("not a statement"),
         };
 
-        // Process routed operation version
-        // xxx switch this to a Crypto trait factory method per issue#140
-        if route.operation.version != MAX_CRYPTO_VERSION {
+        // Get crypto kind
+        let crypto_kind = route.safety_route.crypto_kind();
+        let Some(vcrypto) = self.crypto.get(crypto_kind) else {
             return Ok(NetworkResult::invalid_message(
-                "routes operation crypto is not valid version",
+                "routed operation crypto is not supported",
             ));
-        }
+        };
 
         // See what kind of safety route we have going on here
         match route.safety_route.hops {
             // There is a safety route hop
             SafetyRouteHops::Data(ref route_hop_data) => {
                 // Decrypt the blob with DEC(nonce, DH(the SR's public key, this hop's secret)
-                let node_id_secret = self.routing_table.node_id_secret();
-                let dh_secret = self
-                    .crypto
-                    .cached_dh(&route.safety_route.public_key, &node_id_secret)
+                let node_id_secret = self.routing_table.node_id_secret(crypto_kind);
+                let dh_secret = vcrypto
+                    .cached_dh(&route.safety_route.public_key.key, &node_id_secret)
                     .map_err(RPCError::protocol)?;
-                let mut dec_blob_data = Crypto::decrypt_aead(&route_hop_data.blob, &route_hop_data.nonce, &dh_secret, None)
+                let mut dec_blob_data = vcrypto.decrypt_aead(&route_hop_data.blob, &route_hop_data.nonce, &dh_secret, None)
                     .map_err(RPCError::protocol)?;
 
                 // See if this is last hop in safety route, if so, we're decoding a PrivateRoute not a RouteHop
@@ -413,7 +419,7 @@ impl RPCProcessor {
                         let pr_reader = dec_blob_reader
                             .get_root::<veilid_capnp::private_route::Reader>()
                             .map_err(RPCError::protocol)?;
-                        decode_private_route(&pr_reader)?
+                        decode_private_route(&pr_reader, self.crypto.clone())?
                     };
 
                     // Switching from full safety route to private route first hop
@@ -429,7 +435,7 @@ impl RPCProcessor {
                         let rh_reader = dec_blob_reader
                             .get_root::<veilid_capnp::route_hop::Reader>()
                             .map_err(RPCError::protocol)?;
-                        decode_route_hop(&rh_reader)?
+                        decode_route_hop(&rh_reader, self.crypto.clone())?
                     };
 
                     // Continue the full safety route with another hop