From d05786ffb5523c334f10d16870c2e73674661a52 Mon Sep 17 00:00:00 2001
From: Dmitry Markin <dmitry@markin.tech>
Date: Tue, 21 May 2024 19:10:10 +0300
Subject: [PATCH] Replace `Multiaddr` & related types with substrate-specific
 types (#4198)

This PR introduces custom types / substrate wrappers for `Multiaddr`,
`multiaddr::Protocol`, `Multihash`, `ed25519::*` and supplementary types
like errors and iterators.

This is needed to unblock `libp2p` upgrade PR
https://github.com/paritytech/polkadot-sdk/pull/1631 after
https://github.com/paritytech/polkadot-sdk/pull/2944 was merged.
`libp2p` and `litep2p` currently depend on different versions of
`multiaddr` crate, and introduction of this "common ground" types is
needed to support independent version upgrades of `multiaddr` and
dependent crates in `libp2p` & `litep2p`.

While being just convenient to not tie versions of `libp2p` & `litep2p`
dependencies together, it's currently not even possible to keep `libp2p`
& `litep2p` dependencies updated to the same versions as `multiaddr` in
`libp2p` depends on `libp2p-identity` that we can't include as a
dependency of `litep2p`, which has it's own `PeerId` type. In the
future, to keep things updated on `litep2p` side, we will likely need to
fork `multiaddr` and make it use `litep2p` `PeerId` as a payload of
`/p2p/...` protocol.

With these changes, common code in substrate uses these custom types,
and `litep2p` & `libp2p` backends use corresponding libraries types.
---
 Cargo.lock                                    |  15 +-
 prdoc/pr_4198.prdoc                           |  31 +
 .../client/authority-discovery/src/error.rs   |   4 +-
 .../client/authority-discovery/src/worker.rs  |   6 +-
 .../src/worker/addr_cache.rs                  |   2 +-
 .../authority-discovery/src/worker/tests.rs   |  10 +-
 .../client/cli/src/params/node_key_params.rs  |  10 +-
 .../client/mixnet/src/sync_with_runtime.rs    |   7 +-
 substrate/client/network/src/config.rs        |  38 +-
 substrate/client/network/src/error.rs         |   2 +-
 substrate/client/network/src/lib.rs           |   6 +-
 .../client/network/src/litep2p/discovery.rs   |   7 +-
 substrate/client/network/src/litep2p/mod.rs   |  49 +-
 .../client/network/src/litep2p/service.rs     |  24 +-
 substrate/client/network/src/peer_store.rs    |   3 +-
 .../src/protocol/notifications/service/mod.rs |   2 +-
 .../protocol/notifications/service/tests.rs   |   2 +-
 .../client/network/src/protocol_controller.rs |   9 +-
 substrate/client/network/src/service.rs       |  83 +--
 .../client/network/src/service/traits.rs      |   4 +-
 .../client/network/sync/src/service/mock.rs   |   4 +-
 substrate/client/network/test/src/fuzz.rs     |   3 +-
 substrate/client/network/test/src/lib.rs      |   9 +-
 substrate/client/network/types/Cargo.toml     |   5 +
 substrate/client/network/types/src/ed25519.rs | 551 ++++++++++++++++++
 substrate/client/network/types/src/lib.rs     |   8 +-
 .../client/network/types/src/multiaddr.rs     | 251 ++++++++
 .../network/types/src/multiaddr/protocol.rs   | 138 +++++
 .../client/network/types/src/multihash.rs     | 192 ++++++
 substrate/client/network/types/src/peer_id.rs |   8 +-
 substrate/client/telemetry/src/endpoints.rs   |   2 +-
 substrate/client/telemetry/src/lib.rs         |   2 +-
 substrate/client/telemetry/src/node.rs        |   3 +-
 33 files changed, 1343 insertions(+), 147 deletions(-)
 create mode 100644 prdoc/pr_4198.prdoc
 create mode 100644 substrate/client/network/types/src/ed25519.rs
 create mode 100644 substrate/client/network/types/src/multiaddr.rs
 create mode 100644 substrate/client/network/types/src/multiaddr/protocol.rs
 create mode 100644 substrate/client/network/types/src/multihash.rs

diff --git a/Cargo.lock b/Cargo.lock
index 2a4b9b138bf..1f908bb49ed 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1896,7 +1896,7 @@ dependencies = [
  "bp-parachains",
  "bp-polkadot-core",
  "bp-runtime",
- "ed25519-dalek 2.1.0",
+ "ed25519-dalek 2.1.1",
  "finality-grandpa",
  "parity-scale-codec",
  "sp-application-crypto",
@@ -4961,9 +4961,9 @@ dependencies = [
 
 [[package]]
 name = "ed25519-dalek"
-version = "2.1.0"
+version = "2.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f628eaec48bfd21b865dc2950cfa014450c01d2fa2b69a86c2fd5844ec523c0"
+checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871"
 dependencies = [
  "curve25519-dalek 4.1.2",
  "ed25519 2.2.2",
@@ -7626,7 +7626,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "276bb57e7af15d8f100d3c11cbdd32c6752b7eef4ba7a18ecf464972c07abcce"
 dependencies = [
  "bs58 0.4.0",
- "ed25519-dalek 2.1.0",
+ "ed25519-dalek 2.1.1",
  "log",
  "multiaddr",
  "multihash 0.17.0",
@@ -17213,12 +17213,15 @@ name = "sc-network-types"
 version = "0.10.0"
 dependencies = [
  "bs58 0.5.0",
+ "ed25519-dalek 2.1.1",
  "libp2p-identity",
  "litep2p",
  "multiaddr",
  "multihash 0.17.0",
+ "quickcheck",
  "rand 0.8.5",
  "thiserror",
+ "zeroize",
 ]
 
 [[package]]
@@ -19497,7 +19500,7 @@ name = "sp-io"
 version = "30.0.0"
 dependencies = [
  "bytes",
- "ed25519-dalek 2.1.0",
+ "ed25519-dalek 2.1.1",
  "libsecp256k1",
  "log",
  "parity-scale-codec",
@@ -19823,7 +19826,7 @@ version = "10.0.0"
 dependencies = [
  "aes-gcm",
  "curve25519-dalek 4.1.2",
- "ed25519-dalek 2.1.0",
+ "ed25519-dalek 2.1.1",
  "hkdf",
  "parity-scale-codec",
  "rand 0.8.5",
diff --git a/prdoc/pr_4198.prdoc b/prdoc/pr_4198.prdoc
new file mode 100644
index 00000000000..cff95681260
--- /dev/null
+++ b/prdoc/pr_4198.prdoc
@@ -0,0 +1,31 @@
+# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
+# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json
+
+title: Replace `Multiaddr` & related types with substrate-specific types
+
+doc:
+  - audience: Node Dev
+    description: |
+      Introduce custom types / substrate wrappers for `Multiaddr`, `multiaddr::Protocol`,
+      `Multihash`, `ed25519::*` and supplementary types like errors and iterators.
+
+      Common code in substrate uses these custom types, while `libp2p` & `litep2p` network
+      backends use their corresponding libraries types.
+
+      This is needed to independently upgrade `libp2p` & `litep2p` dependencies.
+
+crates:
+  - name: sc-network-types
+    bump: minor
+  - name: sc-network
+    bump: minor
+  - name: sc-network-sync
+    bump: minor
+  - name: sc-authority-discovery
+    bump: minor
+  - name: sc-cli
+    bump: patch
+  - name: sc-mixnet
+    bump: patch
+  - name: sc-telemetry
+    bump: patch
diff --git a/substrate/client/authority-discovery/src/error.rs b/substrate/client/authority-discovery/src/error.rs
index 6f791237c2f..d2c567d77af 100644
--- a/substrate/client/authority-discovery/src/error.rs
+++ b/substrate/client/authority-discovery/src/error.rs
@@ -35,7 +35,7 @@ pub enum Error {
 	VerifyingDhtPayload,
 
 	#[error("Failed to hash the authority id to be used as a dht key.")]
-	HashingAuthorityId(#[from] sc_network::multiaddr::multihash::Error),
+	HashingAuthorityId(#[from] sc_network_types::multihash::Error),
 
 	#[error("Failed calling into the Substrate runtime: {0}")]
 	CallingRuntime(#[from] sp_blockchain::Error),
@@ -53,7 +53,7 @@ pub enum Error {
 	EncodingDecodingScale(#[from] codec::Error),
 
 	#[error("Failed to parse a libp2p multi address.")]
-	ParsingMultiaddress(#[from] sc_network::multiaddr::Error),
+	ParsingMultiaddress(#[from] sc_network::multiaddr::ParseError),
 
 	#[error("Failed to parse a libp2p key: {0}")]
 	ParsingLibp2pIdentity(String),
diff --git a/substrate/client/authority-discovery/src/worker.rs b/substrate/client/authority-discovery/src/worker.rs
index 53418d2d38c..d89083100aa 100644
--- a/substrate/client/authority-discovery/src/worker.rs
+++ b/substrate/client/authority-discovery/src/worker.rs
@@ -35,7 +35,6 @@ use addr_cache::AddrCache;
 use codec::{Decode, Encode};
 use ip_network::IpNetwork;
 use linked_hash_set::LinkedHashSet;
-use multihash::{Code, Multihash, MultihashDigest};
 
 use log::{debug, error, log_enabled};
 use prometheus_endpoint::{register, Counter, CounterVec, Gauge, Opts, U64};
@@ -46,7 +45,10 @@ use sc_network::{
 	event::DhtEvent, multiaddr, KademliaKey, Multiaddr, NetworkDHTProvider, NetworkSigner,
 	NetworkStateInfo,
 };
-use sc_network_types::PeerId;
+use sc_network_types::{
+	multihash::{Code, Multihash},
+	PeerId,
+};
 use sp_api::{ApiError, ProvideRuntimeApi};
 use sp_authority_discovery::{
 	AuthorityDiscoveryApi, AuthorityId, AuthorityPair, AuthoritySignature,
diff --git a/substrate/client/authority-discovery/src/worker/addr_cache.rs b/substrate/client/authority-discovery/src/worker/addr_cache.rs
index 6e3b3c8af20..77cdfbd4f15 100644
--- a/substrate/client/authority-discovery/src/worker/addr_cache.rs
+++ b/substrate/client/authority-discovery/src/worker/addr_cache.rs
@@ -176,8 +176,8 @@ fn addresses_to_peer_ids(addresses: &HashSet<Multiaddr>) -> HashSet<PeerId> {
 mod tests {
 	use super::*;
 
-	use multihash::{self, Multihash};
 	use quickcheck::{Arbitrary, Gen, QuickCheck, TestResult};
+	use sc_network_types::multihash::Multihash;
 
 	use sp_authority_discovery::{AuthorityId, AuthorityPair};
 	use sp_core::crypto::Pair;
diff --git a/substrate/client/authority-discovery/src/worker/tests.rs b/substrate/client/authority-discovery/src/worker/tests.rs
index caeac56c540..70107c89a85 100644
--- a/substrate/client/authority-discovery/src/worker/tests.rs
+++ b/substrate/client/authority-discovery/src/worker/tests.rs
@@ -29,11 +29,15 @@ use futures::{
 	sink::SinkExt,
 	task::LocalSpawn,
 };
-use libp2p::{core::multiaddr, identity::SigningError, kad::record::Key as KademliaKey, PeerId};
+use libp2p::{identity::SigningError, kad::record::Key as KademliaKey};
 use prometheus_endpoint::prometheus::default_registry;
 
 use sc_client_api::HeaderBackend;
 use sc_network::{service::signature::Keypair, Signature};
+use sc_network_types::{
+	multiaddr::{Multiaddr, Protocol},
+	PeerId,
+};
 use sp_api::{ApiRef, ProvideRuntimeApi};
 use sp_keystore::{testing::MemoryKeystore, Keystore};
 use sp_runtime::traits::{Block as BlockT, NumberFor, Zero};
@@ -168,7 +172,7 @@ impl NetworkSigner for TestNetwork {
 		let public_key = libp2p::identity::PublicKey::try_decode_protobuf(&public_key)
 			.map_err(|error| error.to_string())?;
 		let peer_id: PeerId = peer_id.into();
-		let remote: libp2p::PeerId = public_key.to_peer_id();
+		let remote: PeerId = public_key.to_peer_id().into();
 
 		Ok(peer_id == remote && public_key.verify(message, signature))
 	}
@@ -435,7 +439,7 @@ fn dont_stop_polling_dht_event_stream_after_bogus_event() {
 		let peer_id = PeerId::random();
 		let address: Multiaddr = "/ip6/2001:db8:0:0:0:0:0:1/tcp/30333".parse().unwrap();
 
-		address.with(multiaddr::Protocol::P2p(peer_id.into()))
+		address.with(Protocol::P2p(peer_id.into()))
 	};
 	let remote_key_store = MemoryKeystore::new();
 	let remote_public_key: AuthorityId = remote_key_store
diff --git a/substrate/client/cli/src/params/node_key_params.rs b/substrate/client/cli/src/params/node_key_params.rs
index 7058af19f1d..0e12c7a2a2d 100644
--- a/substrate/client/cli/src/params/node_key_params.rs
+++ b/substrate/client/cli/src/params/node_key_params.rs
@@ -17,7 +17,7 @@
 // along with this program. If not, see <https://www.gnu.org/licenses/>.
 
 use clap::Args;
-use sc_network::config::{identity::ed25519, NodeKeyConfig};
+use sc_network::config::{ed25519, NodeKeyConfig};
 use sc_service::Role;
 use sp_core::H256;
 use std::{path::PathBuf, str::FromStr};
@@ -148,7 +148,7 @@ fn parse_ed25519_secret(hex: &str) -> error::Result<sc_network::config::Ed25519S
 mod tests {
 	use super::*;
 	use clap::ValueEnum;
-	use libp2p_identity::ed25519;
+	use sc_network::config::ed25519;
 	use std::fs::{self, File};
 	use tempfile::TempDir;
 
@@ -194,11 +194,7 @@ mod tests {
 				.into_keypair()
 				.expect("Creates node key pair");
 
-			if let Ok(pair) = node_key.try_into_ed25519() {
-				if pair.secret().as_ref() != key.as_ref() {
-					panic!("Invalid key")
-				}
-			} else {
+			if node_key.secret().as_ref() != key.as_ref() {
 				panic!("Invalid key")
 			}
 		}
diff --git a/substrate/client/mixnet/src/sync_with_runtime.rs b/substrate/client/mixnet/src/sync_with_runtime.rs
index 46c2334ceb4..0071ce13b33 100644
--- a/substrate/client/mixnet/src/sync_with_runtime.rs
+++ b/substrate/client/mixnet/src/sync_with_runtime.rs
@@ -25,8 +25,10 @@ use mixnet::core::{
 	Mixnet, Mixnode as CoreMixnode, MixnodesErr as CoreMixnodesErr, RelSessionIndex,
 	SessionPhase as CoreSessionPhase, SessionStatus as CoreSessionStatus,
 };
-use multiaddr::{multiaddr, Multiaddr, Protocol};
-use sc_network_types::PeerId;
+use sc_network_types::{
+	multiaddr::{multiaddr, Multiaddr, Protocol},
+	PeerId,
+};
 use sp_api::{ApiError, ApiRef};
 use sp_mixnet::{
 	runtime_api::MixnetApi,
@@ -196,7 +198,6 @@ where
 #[cfg(test)]
 mod tests {
 	use super::*;
-	use multiaddr::multiaddr;
 
 	#[test]
 	fn fixup_empty_external_addresses() {
diff --git a/substrate/client/network/src/config.rs b/substrate/client/network/src/config.rs
index e6cc9de5694..100a1e9dfb3 100644
--- a/substrate/client/network/src/config.rs
+++ b/substrate/client/network/src/config.rs
@@ -35,12 +35,11 @@ pub use crate::{
 	types::ProtocolName,
 };
 
-pub use libp2p::{
-	build_multiaddr,
-	identity::{self, ed25519, Keypair},
-	multiaddr, Multiaddr,
+pub use sc_network_types::{build_multiaddr, ed25519};
+use sc_network_types::{
+	multiaddr::{self, Multiaddr},
+	PeerId,
 };
-use sc_network_types::PeerId;
 
 use crate::service::{ensure_addresses_consistent_with_transport, traits::NetworkBackend};
 use codec::Encode;
@@ -100,7 +99,7 @@ impl fmt::Debug for ProtocolId {
 /// # Example
 ///
 /// ```
-/// # use libp2p::{Multiaddr, PeerId};
+/// # use sc_network_types::{multiaddr::Multiaddr, PeerId};
 /// use sc_network::config::parse_str_addr;
 /// let (peer_id, addr) = parse_str_addr(
 /// 	"/ip4/198.51.100.19/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV"
@@ -131,7 +130,7 @@ pub fn parse_addr(mut addr: Multiaddr) -> Result<(PeerId, Multiaddr), ParseErr>
 /// # Example
 ///
 /// ```
-/// # use libp2p::{Multiaddr, PeerId};
+/// # use sc_network_types::{multiaddr::Multiaddr, PeerId};
 /// use sc_network::config::MultiaddrWithPeerId;
 /// let addr: MultiaddrWithPeerId =
 /// 	"/ip4/198.51.100.19/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV".parse().unwrap();
@@ -187,7 +186,7 @@ impl TryFrom<String> for MultiaddrWithPeerId {
 #[derive(Debug)]
 pub enum ParseErr {
 	/// Error while parsing the multiaddress.
-	MultiaddrParse(multiaddr::Error),
+	MultiaddrParse(multiaddr::ParseError),
 	/// Multihash of the peer ID is invalid.
 	InvalidPeerId,
 	/// The peer ID is missing from the address.
@@ -214,8 +213,8 @@ impl std::error::Error for ParseErr {
 	}
 }
 
-impl From<multiaddr::Error> for ParseErr {
-	fn from(err: multiaddr::Error) -> ParseErr {
+impl From<multiaddr::ParseError> for ParseErr {
+	fn from(err: multiaddr::ParseError) -> ParseErr {
 		Self::MultiaddrParse(err)
 	}
 }
@@ -343,10 +342,10 @@ impl NodeKeyConfig {
 	///
 	///  * If the secret is configured to be new, it is generated and the corresponding keypair is
 	///    returned.
-	pub fn into_keypair(self) -> io::Result<Keypair> {
+	pub fn into_keypair(self) -> io::Result<ed25519::Keypair> {
 		use NodeKeyConfig::*;
 		match self {
-			Ed25519(Secret::New) => Ok(Keypair::generate_ed25519()),
+			Ed25519(Secret::New) => Ok(ed25519::Keypair::generate()),
 
 			Ed25519(Secret::Input(k)) => Ok(ed25519::Keypair::from(k).into()),
 
@@ -365,8 +364,7 @@ impl NodeKeyConfig {
 				ed25519::SecretKey::generate,
 				|b| b.as_ref().to_vec(),
 			)
-			.map(ed25519::Keypair::from)
-			.map(Keypair::from),
+			.map(ed25519::Keypair::from),
 		}
 	}
 }
@@ -887,7 +885,7 @@ impl<B: BlockT + 'static, H: ExHashT, N: NetworkBackend<B, H>> FullNetworkConfig
 				.find(|o| o.peer_id != bootnode.peer_id)
 			{
 				Err(crate::error::Error::DuplicateBootnode {
-					address: bootnode.multiaddr.clone(),
+					address: bootnode.multiaddr.clone().into(),
 					first_id: bootnode.peer_id.into(),
 					second_id: other.peer_id.into(),
 				})
@@ -947,14 +945,8 @@ mod tests {
 		tempfile::Builder::new().prefix(prefix).tempdir().unwrap()
 	}
 
-	fn secret_bytes(kp: Keypair) -> Vec<u8> {
-		kp.try_into_ed25519()
-			.expect("ed25519 keypair")
-			.secret()
-			.as_ref()
-			.iter()
-			.cloned()
-			.collect()
+	fn secret_bytes(kp: ed25519::Keypair) -> Vec<u8> {
+		kp.secret().to_bytes().into()
 	}
 
 	#[test]
diff --git a/substrate/client/network/src/error.rs b/substrate/client/network/src/error.rs
index b776e3e1ad9..376b8461be4 100644
--- a/substrate/client/network/src/error.rs
+++ b/substrate/client/network/src/error.rs
@@ -20,7 +20,7 @@
 
 use crate::{config::TransportConfig, types::ProtocolName};
 
-use libp2p::{Multiaddr, PeerId};
+use sc_network_types::{multiaddr::Multiaddr, PeerId};
 
 use std::fmt;
 
diff --git a/substrate/client/network/src/lib.rs b/substrate/client/network/src/lib.rs
index 8f479825c8d..99a972f914e 100644
--- a/substrate/client/network/src/lib.rs
+++ b/substrate/client/network/src/lib.rs
@@ -272,6 +272,10 @@ pub use sc_network_common::{
 	role::{ObservedRole, Roles},
 	types::ReputationChange,
 };
+pub use sc_network_types::{
+	multiaddr::{self, Multiaddr},
+	PeerId,
+};
 pub use service::{
 	metrics::NotificationMetrics,
 	signature::Signature,
@@ -285,7 +289,7 @@ pub use service::{
 	DecodingError, Keypair, NetworkService, NetworkWorker, NotificationSender, OutboundFailure,
 	PublicKey,
 };
-pub use types::{multiaddr, Multiaddr, PeerId, ProtocolName};
+pub use types::ProtocolName;
 
 /// The maximum allowed number of established connections per peer.
 ///
diff --git a/substrate/client/network/src/litep2p/discovery.rs b/substrate/client/network/src/litep2p/discovery.rs
index 2120ea7c615..351380755db 100644
--- a/substrate/client/network/src/litep2p/discovery.rs
+++ b/substrate/client/network/src/litep2p/discovery.rs
@@ -20,9 +20,7 @@
 
 use crate::{
 	config::{NetworkConfiguration, ProtocolId},
-	multiaddr::Protocol,
 	peer_store::PeerStoreProvider,
-	Multiaddr,
 };
 
 use array_bytes::bytes2hex;
@@ -42,6 +40,7 @@ use litep2p::{
 		},
 		mdns::{Config as MdnsConfig, MdnsEvent},
 	},
+	types::multiaddr::{Multiaddr, Protocol},
 	PeerId, ProtocolName,
 };
 use parking_lot::RwLock;
@@ -227,7 +226,7 @@ impl Discovery {
 		let (identify_config, identify_event_stream) = IdentifyConfig::new(
 			"/substrate/1.0".to_string(),
 			Some(user_agent),
-			config.public_addresses.clone(),
+			config.public_addresses.clone().into_iter().map(Into::into).collect(),
 		);
 
 		let (mdns_config, mdns_event_stream) = match config.transport {
@@ -266,7 +265,7 @@ impl Discovery {
 				duration_to_next_find_query: Duration::from_secs(1),
 				address_confirmations: LruMap::new(ByLength::new(8)),
 				allow_non_global_addresses: config.allow_non_globals_in_dht,
-				public_addresses: config.public_addresses.iter().cloned().collect(),
+				public_addresses: config.public_addresses.iter().cloned().map(Into::into).collect(),
 				next_kad_query: Some(Delay::new(KADEMLIA_QUERY_INTERVAL)),
 				local_protocols: HashSet::from_iter([kademlia_protocol_name(
 					genesis_hash,
diff --git a/substrate/client/network/src/litep2p/mod.rs b/substrate/client/network/src/litep2p/mod.rs
index 1137c73b56d..67085a81a5c 100644
--- a/substrate/client/network/src/litep2p/mod.rs
+++ b/substrate/client/network/src/litep2p/mod.rs
@@ -38,7 +38,6 @@ use crate::{
 			request_response::{RequestResponseConfig, RequestResponseProtocol},
 		},
 	},
-	multiaddr::{Multiaddr, Protocol},
 	peer_store::PeerStoreProvider,
 	protocol,
 	service::{
@@ -54,7 +53,7 @@ use futures::StreamExt;
 use libp2p::kad::RecordKey;
 use litep2p::{
 	config::ConfigBuilder,
-	crypto::ed25519::{Keypair, SecretKey},
+	crypto::ed25519::Keypair,
 	executor::Executor,
 	protocol::{
 		libp2p::{bitswap::Config as BitswapConfig, kademlia::QueryId},
@@ -64,7 +63,10 @@ use litep2p::{
 		tcp::config::Config as TcpTransportConfig,
 		websocket::config::Config as WebSocketTransportConfig, Endpoint,
 	},
-	types::ConnectionId,
+	types::{
+		multiaddr::{Multiaddr, Protocol},
+		ConnectionId,
+	},
 	Error as Litep2pError, Litep2p, Litep2pEvent, ProtocolName as Litep2pProtocolName,
 };
 use parking_lot::RwLock;
@@ -81,7 +83,7 @@ use std::{
 	collections::{hash_map::Entry, HashMap, HashSet},
 	fs,
 	future::Future,
-	io, iter,
+	iter,
 	pin::Pin,
 	sync::{
 		atomic::{AtomicUsize, Ordering},
@@ -200,12 +202,12 @@ impl Litep2pNetworkBackend {
 					Protocol::Ip4(_),
 				) => match address.iter().find(|protocol| std::matches!(protocol, Protocol::P2p(_)))
 				{
-					Some(Protocol::P2p(multihash)) => PeerId::from_multihash(multihash)
+					Some(Protocol::P2p(multihash)) => PeerId::from_multihash(multihash.into())
 						.map_or(None, |peer| Some((peer, Some(address)))),
 					_ => None,
 				},
 				Some(Protocol::P2p(multihash)) =>
-					PeerId::from_multihash(multihash).map_or(None, |peer| Some((peer, None))),
+					PeerId::from_multihash(multihash.into()).map_or(None, |peer| Some((peer, None))),
 				_ => None,
 			})
 			.fold(HashMap::new(), |mut acc, (peer, maybe_address)| {
@@ -244,16 +246,9 @@ impl Litep2pNetworkBackend {
 impl Litep2pNetworkBackend {
 	/// Get `litep2p` keypair from `NodeKeyConfig`.
 	fn get_keypair(node_key: &NodeKeyConfig) -> Result<(Keypair, litep2p::PeerId), Error> {
-		let secret = libp2p::identity::Keypair::try_into_ed25519(node_key.clone().into_keypair()?)
-			.map_err(|error| {
-				log::error!(target: LOG_TARGET, "failed to convert to ed25519: {error:?}");
-				Error::Io(io::ErrorKind::InvalidInput.into())
-			})?
-			.secret();
-
-		let mut secret = secret.as_ref().iter().cloned().collect::<Vec<_>>();
-		let secret = SecretKey::from_bytes(&mut secret)
-			.map_err(|_| Error::Io(io::ErrorKind::InvalidInput.into()))?;
+		let secret: litep2p::crypto::ed25519::SecretKey =
+			node_key.clone().into_keypair()?.secret().into();
+
 		let local_identity = Keypair::from(secret);
 		let local_public = local_identity.public();
 		let local_peer_id = local_public.to_peer_id();
@@ -327,6 +322,8 @@ impl Litep2pNetworkBackend {
 			.listen_addresses
 			.iter()
 			.filter_map(|address| {
+				use sc_network_types::multiaddr::Protocol;
+
 				let mut iter = address.iter();
 
 				match iter.next() {
@@ -367,12 +364,12 @@ impl Litep2pNetworkBackend {
 
 		config_builder
 			.with_websocket(WebSocketTransportConfig {
-				listen_addresses: websocket.into_iter().flatten().collect(),
+				listen_addresses: websocket.into_iter().flatten().map(Into::into).collect(),
 				yamux_config: yamux_config.clone(),
 				..Default::default()
 			})
 			.with_tcp(TcpTransportConfig {
-				listen_addresses: tcp.into_iter().flatten().collect(),
+				listen_addresses: tcp.into_iter().flatten().map(Into::into).collect(),
 				yamux_config,
 				..Default::default()
 			})
@@ -522,6 +519,8 @@ impl<B: BlockT + 'static, H: ExHashT> NetworkBackend<B, H> for Litep2pNetworkBac
 		// collect known addresses
 		let known_addresses: HashMap<litep2p::PeerId, Vec<Multiaddr>> =
 			known_addresses.into_iter().fold(HashMap::new(), |mut acc, (peer, address)| {
+				use sc_network_types::multiaddr::Protocol;
+
 				let address = match address.iter().last() {
 					Some(Protocol::Ws(_) | Protocol::Wss(_) | Protocol::Tcp(_)) =>
 						address.with(Protocol::P2p(peer.into())),
@@ -529,7 +528,7 @@ impl<B: BlockT + 'static, H: ExHashT> NetworkBackend<B, H> for Litep2pNetworkBac
 					_ => return acc,
 				};
 
-				acc.entry(peer.into()).or_default().push(address);
+				acc.entry(peer.into()).or_default().push(address.into());
 				peer_store_handle.add_known_peer(peer);
 
 				acc
@@ -567,7 +566,7 @@ impl<B: BlockT + 'static, H: ExHashT> NetworkBackend<B, H> for Litep2pNetworkBac
 			Litep2p::new(config_builder.build()).map_err(|error| Error::Litep2p(error))?;
 
 		let external_addresses: Arc<RwLock<HashSet<Multiaddr>>> = Arc::new(RwLock::new(
-			HashSet::from_iter(network_config.public_addresses.iter().cloned()),
+			HashSet::from_iter(network_config.public_addresses.iter().cloned().map(Into::into)),
 		));
 		litep2p.listen_addresses().for_each(|address| {
 			log::debug!(target: LOG_TARGET, "listening on: {address}");
@@ -713,7 +712,7 @@ impl<B: BlockT + 'static, H: ExHashT> NetworkBackend<B, H> for Litep2pNetworkBac
 							protocol,
 							peers,
 						} => {
-							let peers = self.add_addresses(peers.into_iter());
+							let peers = self.add_addresses(peers.into_iter().map(Into::into));
 
 							match self.peerset_handles.get(&protocol) {
 								Some(handle) => {
@@ -722,9 +721,11 @@ impl<B: BlockT + 'static, H: ExHashT> NetworkBackend<B, H> for Litep2pNetworkBac
 								None => log::warn!(target: LOG_TARGET, "protocol {protocol} doens't exist"),
 							};
 						}
-						NetworkServiceCommand::AddKnownAddress { peer, mut address } => {
+						NetworkServiceCommand::AddKnownAddress { peer, address } => {
+							let mut address: Multiaddr = address.into();
+
 							if !address.iter().any(|protocol| std::matches!(protocol, Protocol::P2p(_))) {
-								address.push(Protocol::P2p(peer.into()));
+								address.push(Protocol::P2p(litep2p::PeerId::from(peer).into()));
 							}
 
 							if self.litep2p.add_known_address(peer.into(), iter::once(address.clone())) == 0usize {
@@ -735,7 +736,7 @@ impl<B: BlockT + 'static, H: ExHashT> NetworkBackend<B, H> for Litep2pNetworkBac
 							}
 						},
 						NetworkServiceCommand::SetReservedPeers { protocol, peers } => {
-							let peers = self.add_addresses(peers.into_iter());
+							let peers = self.add_addresses(peers.into_iter().map(Into::into));
 
 							match self.peerset_handles.get(&protocol) {
 								Some(handle) => {
diff --git a/substrate/client/network/src/litep2p/service.rs b/substrate/client/network/src/litep2p/service.rs
index 86f11aa6e14..09b869abdf5 100644
--- a/substrate/client/network/src/litep2p/service.rs
+++ b/substrate/client/network/src/litep2p/service.rs
@@ -24,7 +24,6 @@ use crate::{
 		notification::{config::ProtocolControlHandle, peerset::PeersetCommand},
 		request_response::OutboundRequest,
 	},
-	multiaddr::Protocol,
 	network_state::NetworkState,
 	peer_store::PeerStoreProvider,
 	service::out_events,
@@ -35,15 +34,18 @@ use crate::{
 
 use codec::DecodeAll;
 use futures::{channel::oneshot, stream::BoxStream};
-use libp2p::{identity::SigningError, kad::record::Key as KademliaKey, Multiaddr};
-use litep2p::crypto::ed25519::Keypair;
+use libp2p::{identity::SigningError, kad::record::Key as KademliaKey};
+use litep2p::{crypto::ed25519::Keypair, types::multiaddr::Multiaddr as LiteP2pMultiaddr};
 use parking_lot::RwLock;
 
 use sc_network_common::{
 	role::{ObservedRole, Roles},
 	types::ReputationChange,
 };
-use sc_network_types::PeerId;
+use sc_network_types::{
+	multiaddr::{Multiaddr, Protocol},
+	PeerId,
+};
 use sc_utils::mpsc::TracingUnboundedSender;
 
 use std::{
@@ -165,10 +167,10 @@ pub struct Litep2pNetworkService {
 	request_response_protocols: HashMap<ProtocolName, TracingUnboundedSender<OutboundRequest>>,
 
 	/// Listen addresses.
-	listen_addresses: Arc<RwLock<HashSet<Multiaddr>>>,
+	listen_addresses: Arc<RwLock<HashSet<LiteP2pMultiaddr>>>,
 
 	/// External addresses.
-	external_addresses: Arc<RwLock<HashSet<Multiaddr>>>,
+	external_addresses: Arc<RwLock<HashSet<LiteP2pMultiaddr>>>,
 }
 
 impl Litep2pNetworkService {
@@ -181,8 +183,8 @@ impl Litep2pNetworkService {
 		peerset_handles: HashMap<ProtocolName, ProtocolControlHandle>,
 		block_announce_protocol: ProtocolName,
 		request_response_protocols: HashMap<ProtocolName, TracingUnboundedSender<OutboundRequest>>,
-		listen_addresses: Arc<RwLock<HashSet<Multiaddr>>>,
-		external_addresses: Arc<RwLock<HashSet<Multiaddr>>>,
+		listen_addresses: Arc<RwLock<HashSet<LiteP2pMultiaddr>>>,
+		external_addresses: Arc<RwLock<HashSet<LiteP2pMultiaddr>>>,
 	) -> Self {
 		Self {
 			local_peer_id,
@@ -322,7 +324,7 @@ impl NetworkPeers for Litep2pNetworkService {
 	fn add_reserved_peer(&self, peer: MultiaddrWithPeerId) -> Result<(), String> {
 		let _ = self.cmd_tx.unbounded_send(NetworkServiceCommand::AddPeersToReservedSet {
 			protocol: self.block_announce_protocol.clone(),
-			peers: HashSet::from_iter([peer.concat()]),
+			peers: HashSet::from_iter([peer.concat().into()]),
 		});
 
 		Ok(())
@@ -415,11 +417,11 @@ impl NetworkEventStream for Litep2pNetworkService {
 
 impl NetworkStateInfo for Litep2pNetworkService {
 	fn external_addresses(&self) -> Vec<Multiaddr> {
-		self.external_addresses.read().iter().cloned().collect()
+		self.external_addresses.read().iter().cloned().map(Into::into).collect()
 	}
 
 	fn listen_addresses(&self) -> Vec<Multiaddr> {
-		self.listen_addresses.read().iter().cloned().collect()
+		self.listen_addresses.read().iter().cloned().map(Into::into).collect()
 	}
 
 	fn local_peer_id(&self) -> PeerId {
diff --git a/substrate/client/network/src/peer_store.rs b/substrate/client/network/src/peer_store.rs
index a4c739f1448..987405500dc 100644
--- a/substrate/client/network/src/peer_store.rs
+++ b/substrate/client/network/src/peer_store.rs
@@ -19,8 +19,9 @@
 //! [`PeerStore`] manages peer reputations and provides connection candidates to
 //! [`crate::protocol_controller::ProtocolController`].
 
-use crate::{service::traits::PeerStore as PeerStoreT, PeerId};
+use crate::service::traits::PeerStore as PeerStoreT;
 
+use libp2p::PeerId;
 use log::trace;
 use parking_lot::Mutex;
 use partial_sort::PartialSort;
diff --git a/substrate/client/network/src/protocol/notifications/service/mod.rs b/substrate/client/network/src/protocol/notifications/service/mod.rs
index 15d289d170e..4f6d32ae3b3 100644
--- a/substrate/client/network/src/protocol/notifications/service/mod.rs
+++ b/substrate/client/network/src/protocol/notifications/service/mod.rs
@@ -28,13 +28,13 @@ use crate::{
 		},
 	},
 	types::ProtocolName,
-	PeerId,
 };
 
 use futures::{
 	stream::{FuturesUnordered, Stream},
 	StreamExt,
 };
+use libp2p::PeerId;
 use parking_lot::Mutex;
 use tokio::sync::{mpsc, oneshot};
 use tokio_stream::wrappers::ReceiverStream;
diff --git a/substrate/client/network/src/protocol/notifications/service/tests.rs b/substrate/client/network/src/protocol/notifications/service/tests.rs
index f0157f6d28d..32ccb3348ad 100644
--- a/substrate/client/network/src/protocol/notifications/service/tests.rs
+++ b/substrate/client/network/src/protocol/notifications/service/tests.rs
@@ -200,7 +200,7 @@ async fn send_async_notification_to_non_existent_peer() {
 	if let Err(error::Error::PeerDoesntExist(peer_id)) =
 		notif.send_async_notification(&peer.into(), vec![1, 3, 3, 7]).await
 	{
-		assert_eq!(peer, peer_id);
+		assert_eq!(peer, peer_id.into());
 	} else {
 		panic!("invalid error received from `send_async_notification()`");
 	}
diff --git a/substrate/client/network/src/protocol_controller.rs b/substrate/client/network/src/protocol_controller.rs
index 2c3e6744e32..da51a7a4f9f 100644
--- a/substrate/client/network/src/protocol_controller.rs
+++ b/substrate/client/network/src/protocol_controller.rs
@@ -41,12 +41,10 @@
 //! Even though this does not guarantee that `ProtocolController` and `Notifications` have the same
 //! view of the peers' states at any given moment, the eventual consistency is maintained.
 
-use crate::{
-	peer_store::{PeerStoreProvider, ProtocolHandle as ProtocolHandleT},
-	PeerId,
-};
+use crate::peer_store::{PeerStoreProvider, ProtocolHandle as ProtocolHandleT};
 
 use futures::{channel::oneshot, future::Either, FutureExt, StreamExt};
+use libp2p::PeerId;
 use log::{debug, error, trace, warn};
 use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender};
 use sp_arithmetic::traits::SaturatedConversion;
@@ -860,8 +858,9 @@ mod tests {
 	use super::*;
 	use crate::{
 		peer_store::{PeerStoreProvider, ProtocolHandle as ProtocolHandleT},
-		PeerId, ReputationChange,
+		ReputationChange,
 	};
+	use libp2p::PeerId;
 	use sc_network_common::role::ObservedRole;
 	use sc_utils::mpsc::{tracing_unbounded, TryRecvError};
 	use std::collections::HashSet;
diff --git a/substrate/client/network/src/service.rs b/substrate/client/network/src/service.rs
index 807c5b5a80a..1aaa63191a8 100644
--- a/substrate/client/network/src/service.rs
+++ b/substrate/client/network/src/service.rs
@@ -55,24 +55,26 @@ use crate::{
 	},
 	transport,
 	types::ProtocolName,
-	Multiaddr, NotificationService, PeerId, ReputationChange,
+	NotificationService, ReputationChange,
 };
 
 use codec::DecodeAll;
 use either::Either;
 use futures::{channel::oneshot, prelude::*};
+use libp2p::identity::ed25519;
 #[allow(deprecated)]
 use libp2p::{
 	connection_limits::Exceeded,
 	core::{upgrade, ConnectedPoint, Endpoint},
 	identify::Info as IdentifyInfo,
 	kad::record::Key as KademliaKey,
-	multiaddr,
+	multiaddr::{self, Multiaddr},
 	ping::Failure as PingFailure,
 	swarm::{
 		AddressScore, ConnectionError, ConnectionId, ConnectionLimits, DialError, Executor,
 		ListenError, NetworkBehaviour, Swarm, SwarmBuilder, SwarmEvent, THandlerErr,
 	},
+	PeerId,
 };
 use log::{debug, error, info, trace, warn};
 use metrics::{Histogram, MetricSources, Metrics};
@@ -269,6 +271,15 @@ where
 		let local_public = local_identity.public();
 		let local_peer_id = local_public.to_peer_id();
 
+		// Convert to libp2p types.
+		let local_identity: ed25519::Keypair = local_identity.into();
+		let local_public: ed25519::PublicKey = local_public.into();
+		let local_peer_id: PeerId = local_peer_id.into();
+		let listen_addresses: Vec<Multiaddr> =
+			network_config.listen_addresses.iter().cloned().map(Into::into).collect();
+		let public_addresses: Vec<Multiaddr> =
+			network_config.public_addresses.iter().cloned().map(Into::into).collect();
+
 		network_config.boot_nodes = network_config
 			.boot_nodes
 			.into_iter()
@@ -370,7 +381,7 @@ where
 			};
 
 			transport::build_transport(
-				local_identity.clone(),
+				local_identity.clone().into(),
 				config_mem,
 				network_config.yamux_window_size,
 				yamux_maximum_buffer_size,
@@ -462,7 +473,7 @@ where
 				.find(|o| o.peer_id != bootnode.peer_id)
 			{
 				Err(Error::DuplicateBootnode {
-					address: bootnode.multiaddr.clone(),
+					address: bootnode.multiaddr.clone().into(),
 					first_id: bootnode.peer_id.into(),
 					second_id: other.peer_id.into(),
 				})
@@ -478,7 +489,7 @@ where
 			boot_node_ids
 				.entry(bootnode.peer_id.into())
 				.or_default()
-				.push(bootnode.multiaddr.clone());
+				.push(bootnode.multiaddr.clone().into());
 		}
 
 		let boot_node_ids = Arc::new(boot_node_ids);
@@ -502,11 +513,11 @@ where
 				format!("{} ({})", network_config.client_version, network_config.node_name);
 
 			let discovery_config = {
-				let mut config = DiscoveryConfig::new(local_public.to_peer_id());
+				let mut config = DiscoveryConfig::new(local_peer_id);
 				config.with_permanent_addresses(
 					known_addresses
 						.iter()
-						.map(|(peer, address)| (peer.into(), address.clone()))
+						.map(|(peer, address)| (peer.into(), address.clone().into()))
 						.collect::<Vec<_>>(),
 				);
 				config.discovery_limit(u64::from(network_config.default_peers_set.out_peers) + 15);
@@ -544,7 +555,7 @@ where
 				let result = Behaviour::new(
 					protocol,
 					user_agent,
-					local_public,
+					local_public.into(),
 					discovery_config,
 					request_response_protocols,
 					Arc::clone(&peer_store_handle),
@@ -604,14 +615,14 @@ where
 		};
 
 		// Listen on multiaddresses.
-		for addr in &network_config.listen_addresses {
+		for addr in &listen_addresses {
 			if let Err(err) = Swarm::<Behaviour<B>>::listen_on(&mut swarm, addr.clone()) {
 				warn!(target: "sub-libp2p", "Can't listen on {} because: {:?}", addr, err)
 			}
 		}
 
 		// Add external addresses.
-		for addr in &network_config.public_addresses {
+		for addr in &public_addresses {
 			Swarm::<Behaviour<B>>::add_external_address(
 				&mut swarm,
 				addr.clone(),
@@ -619,15 +630,15 @@ where
 			);
 		}
 
-		let listen_addresses = Arc::new(Mutex::new(HashSet::new()));
+		let listen_addresses_set = Arc::new(Mutex::new(HashSet::new()));
 
 		let service = Arc::new(NetworkService {
 			bandwidth,
 			external_addresses,
-			listen_addresses: listen_addresses.clone(),
+			listen_addresses: listen_addresses_set.clone(),
 			num_connected: num_connected.clone(),
 			local_peer_id,
-			local_identity,
+			local_identity: local_identity.into(),
 			to_worker,
 			notification_protocol_ids,
 			protocol_handles,
@@ -638,7 +649,7 @@ where
 		});
 
 		Ok(NetworkWorker {
-			listen_addresses,
+			listen_addresses: listen_addresses_set,
 			num_connected,
 			network_service: swarm,
 			service,
@@ -880,13 +891,13 @@ where
 	H: ExHashT,
 {
 	/// Returns the local external addresses.
-	fn external_addresses(&self) -> Vec<Multiaddr> {
-		self.external_addresses.lock().iter().cloned().collect()
+	fn external_addresses(&self) -> Vec<sc_network_types::multiaddr::Multiaddr> {
+		self.external_addresses.lock().iter().cloned().map(Into::into).collect()
 	}
 
 	/// Returns the listener addresses (without trailing `/p2p/` with our `PeerId`).
-	fn listen_addresses(&self) -> Vec<Multiaddr> {
-		self.listen_addresses.lock().iter().cloned().collect()
+	fn listen_addresses(&self) -> Vec<sc_network_types::multiaddr::Multiaddr> {
+		self.listen_addresses.lock().iter().cloned().map(Into::into).collect()
 	}
 
 	/// Returns the local Peer ID.
@@ -998,10 +1009,14 @@ where
 		self.sync_protocol_handle.set_reserved_only(reserved_only);
 	}
 
-	fn add_known_address(&self, peer_id: sc_network_types::PeerId, addr: Multiaddr) {
+	fn add_known_address(
+		&self,
+		peer_id: sc_network_types::PeerId,
+		addr: sc_network_types::multiaddr::Multiaddr,
+	) {
 		let _ = self
 			.to_worker
-			.unbounded_send(ServiceToWorkerMsg::AddKnownAddress(peer_id.into(), addr));
+			.unbounded_send(ServiceToWorkerMsg::AddKnownAddress(peer_id.into(), addr.into()));
 	}
 
 	fn report_peer(&self, peer_id: sc_network_types::PeerId, cost_benefit: ReputationChange) {
@@ -1034,7 +1049,7 @@ where
 
 		let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::AddKnownAddress(
 			peer.peer_id.into(),
-			peer.multiaddr,
+			peer.multiaddr.into(),
 		));
 		self.sync_protocol_handle.add_reserved_peer(peer.peer_id.into());
 
@@ -1048,16 +1063,16 @@ where
 	fn set_reserved_peers(
 		&self,
 		protocol: ProtocolName,
-		peers: HashSet<Multiaddr>,
+		peers: HashSet<sc_network_types::multiaddr::Multiaddr>,
 	) -> Result<(), String> {
 		let Some(set_id) = self.notification_protocol_ids.get(&protocol) else {
 			return Err(format!("Cannot set reserved peers for unknown protocol: {}", protocol))
 		};
 
+		let peers: HashSet<Multiaddr> = peers.into_iter().map(Into::into).collect();
 		let peers_addrs = self.split_multiaddr_and_peer_id(peers)?;
 
-		let mut peers: HashSet<sc_network_types::PeerId> =
-			HashSet::with_capacity(peers_addrs.len());
+		let mut peers: HashSet<PeerId> = HashSet::with_capacity(peers_addrs.len());
 
 		for (peer_id, addr) in peers_addrs.into_iter() {
 			// Make sure the local peer ID is never added to the PSM.
@@ -1074,8 +1089,7 @@ where
 			}
 		}
 
-		self.protocol_handles[usize::from(*set_id)]
-			.set_reserved_peers(peers.iter().map(|peer| (*peer).into()).collect());
+		self.protocol_handles[usize::from(*set_id)].set_reserved_peers(peers);
 
 		Ok(())
 	}
@@ -1083,7 +1097,7 @@ where
 	fn add_peers_to_reserved_set(
 		&self,
 		protocol: ProtocolName,
-		peers: HashSet<Multiaddr>,
+		peers: HashSet<sc_network_types::multiaddr::Multiaddr>,
 	) -> Result<(), String> {
 		let Some(set_id) = self.notification_protocol_ids.get(&protocol) else {
 			return Err(format!(
@@ -1092,6 +1106,7 @@ where
 			))
 		};
 
+		let peers: HashSet<Multiaddr> = peers.into_iter().map(Into::into).collect();
 		let peers = self.split_multiaddr_and_peer_id(peers)?;
 
 		for (peer_id, addr) in peers.into_iter() {
@@ -1723,8 +1738,8 @@ where
 					{
 						if let DialError::WrongPeerId { obtained, endpoint } = &error {
 							if let ConnectedPoint::Dialer { address, role_override: _ } = endpoint {
-								let address_without_peer_id = parse_addr(address.clone())
-									.map_or_else(|_| address.clone(), |r| r.1);
+								let address_without_peer_id = parse_addr(address.clone().into())
+									.map_or_else(|_| address.clone(), |r| r.1.into());
 
 								// Only report for address of boot node that was added at startup of
 								// the node and not for any address that the node learned of the
@@ -1860,14 +1875,14 @@ where
 }
 
 pub(crate) fn ensure_addresses_consistent_with_transport<'a>(
-	addresses: impl Iterator<Item = &'a Multiaddr>,
+	addresses: impl Iterator<Item = &'a sc_network_types::multiaddr::Multiaddr>,
 	transport: &TransportConfig,
 ) -> Result<(), Error> {
+	use sc_network_types::multiaddr::Protocol;
+
 	if matches!(transport, TransportConfig::MemoryOnly) {
 		let addresses: Vec<_> = addresses
-			.filter(|x| {
-				x.iter().any(|y| !matches!(y, libp2p::core::multiaddr::Protocol::Memory(_)))
-			})
+			.filter(|x| x.iter().any(|y| !matches!(y, Protocol::Memory(_))))
 			.cloned()
 			.collect();
 
@@ -1879,7 +1894,7 @@ pub(crate) fn ensure_addresses_consistent_with_transport<'a>(
 		}
 	} else {
 		let addresses: Vec<_> = addresses
-			.filter(|x| x.iter().any(|y| matches!(y, libp2p::core::multiaddr::Protocol::Memory(_))))
+			.filter(|x| x.iter().any(|y| matches!(y, Protocol::Memory(_))))
 			.cloned()
 			.collect();
 
diff --git a/substrate/client/network/src/service/traits.rs b/substrate/client/network/src/service/traits.rs
index 9bbaeb1026f..d1ea9a2ed56 100644
--- a/substrate/client/network/src/service/traits.rs
+++ b/substrate/client/network/src/service/traits.rs
@@ -28,7 +28,7 @@ use crate::{
 	request_responses::{IfDisconnected, RequestFailure},
 	service::{metrics::NotificationMetrics, signature::Signature, PeerStoreProvider},
 	types::ProtocolName,
-	Multiaddr, ReputationChange,
+	ReputationChange,
 };
 
 use futures::{channel::oneshot, Stream};
@@ -36,7 +36,7 @@ use prometheus_endpoint::Registry;
 
 use sc_client_api::BlockBackend;
 use sc_network_common::{role::ObservedRole, ExHashT};
-use sc_network_types::PeerId;
+use sc_network_types::{multiaddr::Multiaddr, PeerId};
 use sp_runtime::traits::Block as BlockT;
 
 use std::{collections::HashSet, fmt::Debug, future::Future, pin::Pin, sync::Arc, time::Duration};
diff --git a/substrate/client/network/sync/src/service/mock.rs b/substrate/client/network/sync/src/service/mock.rs
index 2e7e12af53d..141edc7c884 100644
--- a/substrate/client/network/sync/src/service/mock.rs
+++ b/substrate/client/network/sync/src/service/mock.rs
@@ -23,10 +23,10 @@ use sc_network::{
 	config::MultiaddrWithPeerId,
 	request_responses::{IfDisconnected, RequestFailure},
 	types::ProtocolName,
-	Multiaddr, NetworkPeers, NetworkRequest, NetworkSyncForkRequest, ReputationChange,
+	NetworkPeers, NetworkRequest, NetworkSyncForkRequest, ReputationChange,
 };
 use sc_network_common::role::ObservedRole;
-use sc_network_types::PeerId;
+use sc_network_types::{multiaddr::Multiaddr, PeerId};
 use sp_runtime::traits::{Block as BlockT, NumberFor};
 
 use std::collections::HashSet;
diff --git a/substrate/client/network/test/src/fuzz.rs b/substrate/client/network/test/src/fuzz.rs
index 69d08d47d26..b0cd6dcf999 100644
--- a/substrate/client/network/test/src/fuzz.rs
+++ b/substrate/client/network/test/src/fuzz.rs
@@ -20,6 +20,7 @@
 //! and `PeerStore` to discover possible inconsistencies in peer management.
 
 use futures::prelude::*;
+use libp2p::PeerId;
 use rand::{
 	distributions::{Distribution, Uniform, WeightedIndex},
 	seq::IteratorRandom,
@@ -27,7 +28,7 @@ use rand::{
 use sc_network::{
 	peer_store::{PeerStore, PeerStoreProvider},
 	protocol_controller::{IncomingIndex, Message, ProtoSetConfig, ProtocolController, SetId},
-	PeerId, ReputationChange,
+	ReputationChange,
 };
 use sc_utils::mpsc::tracing_unbounded;
 use std::{
diff --git a/substrate/client/network/test/src/lib.rs b/substrate/client/network/test/src/lib.rs
index 48a4b3d6e6e..8a8f9608051 100644
--- a/substrate/client/network/test/src/lib.rs
+++ b/substrate/client/network/test/src/lib.rs
@@ -35,7 +35,7 @@ use std::{
 };
 
 use futures::{channel::oneshot, future::BoxFuture, pin_mut, prelude::*};
-use libp2p::{build_multiaddr, PeerId};
+use libp2p::PeerId;
 use log::trace;
 use parking_lot::Mutex;
 use sc_block_builder::{BlockBuilder, BlockBuilderBuilder};
@@ -57,8 +57,8 @@ use sc_network::{
 	peer_store::PeerStore,
 	request_responses::ProtocolConfig as RequestResponseConfig,
 	types::ProtocolName,
-	Multiaddr, NetworkBlock, NetworkService, NetworkStateInfo, NetworkSyncForkRequest,
-	NetworkWorker, NotificationMetrics, NotificationService,
+	NetworkBlock, NetworkService, NetworkStateInfo, NetworkSyncForkRequest, NetworkWorker,
+	NotificationMetrics, NotificationService,
 };
 use sc_network_common::role::Roles;
 use sc_network_light::light_client_requests::handler::LightClientRequestHandler;
@@ -71,6 +71,7 @@ use sc_network_sync::{
 	},
 	warp_request_handler,
 };
+use sc_network_types::{build_multiaddr, multiaddr::Multiaddr};
 use sc_service::client::Client;
 use sp_blockchain::{
 	Backend as BlockchainBackend, HeaderBackend, Info as BlockchainInfo, Result as ClientResult,
@@ -985,7 +986,7 @@ pub trait TestNetFactory: Default + Sized + Send {
 			for peer in peers.iter_mut() {
 				peer.network.add_known_address(
 					network.service().local_peer_id().into(),
-					listen_addr.clone(),
+					listen_addr.clone().into(),
 				);
 			}
 
diff --git a/substrate/client/network/types/Cargo.toml b/substrate/client/network/types/Cargo.toml
index 8815ccdca3c..f9d9330a439 100644
--- a/substrate/client/network/types/Cargo.toml
+++ b/substrate/client/network/types/Cargo.toml
@@ -11,9 +11,14 @@ documentation = "https://docs.rs/sc-network-types"
 
 [dependencies]
 bs58 = "0.5.0"
+ed25519-dalek = "2.1"
 libp2p-identity = { version = "0.1.3", features = ["ed25519", "peerid"] }
 litep2p = { git = "https://github.com/paritytech/litep2p", rev = "e03a6023882db111beeb24d8c0ceaac0721d3f0f" }
 multiaddr = "0.17.0"
 multihash = { version = "0.17.0", default-features = false, features = ["identity", "multihash-impl", "sha2", "std"] }
 rand = "0.8.5"
 thiserror = "1.0.48"
+zeroize = { version = "1.7.0", default-features = false }
+
+[dev-dependencies]
+quickcheck = "1.0.3"
diff --git a/substrate/client/network/types/src/ed25519.rs b/substrate/client/network/types/src/ed25519.rs
new file mode 100644
index 00000000000..e85f405b130
--- /dev/null
+++ b/substrate/client/network/types/src/ed25519.rs
@@ -0,0 +1,551 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+//! Ed25519 keys.
+
+use crate::PeerId;
+use core::{cmp, fmt, hash};
+use ed25519_dalek::{self as ed25519, Signer as _, Verifier as _};
+use libp2p_identity::ed25519 as libp2p_ed25519;
+use litep2p::crypto::ed25519 as litep2p_ed25519;
+use zeroize::Zeroize;
+
+/// An Ed25519 keypair.
+#[derive(Clone)]
+pub struct Keypair(ed25519::SigningKey);
+
+impl Keypair {
+	/// Generate a new random Ed25519 keypair.
+	pub fn generate() -> Keypair {
+		Keypair::from(SecretKey::generate())
+	}
+
+	/// Convert the keypair into a byte array by concatenating the bytes
+	/// of the secret scalar and the compressed public point,
+	/// an informal standard for encoding Ed25519 keypairs.
+	pub fn to_bytes(&self) -> [u8; 64] {
+		self.0.to_keypair_bytes()
+	}
+
+	/// Try to parse a keypair from the [binary format](https://datatracker.ietf.org/doc/html/rfc8032#section-5.1.5)
+	/// produced by [`Keypair::to_bytes`], zeroing the input on success.
+	///
+	/// Note that this binary format is the same as `ed25519_dalek`'s and `ed25519_zebra`'s.
+	pub fn try_from_bytes(kp: &mut [u8]) -> Result<Keypair, DecodingError> {
+		let bytes = <[u8; 64]>::try_from(&*kp)
+			.map_err(|e| DecodingError::KeypairParseError(Box::new(e)))?;
+
+		ed25519::SigningKey::from_keypair_bytes(&bytes)
+			.map(|k| {
+				kp.zeroize();
+				Keypair(k)
+			})
+			.map_err(|e| DecodingError::KeypairParseError(Box::new(e)))
+	}
+
+	/// Sign a message using the private key of this keypair.
+	pub fn sign(&self, msg: &[u8]) -> Vec<u8> {
+		self.0.sign(msg).to_bytes().to_vec()
+	}
+
+	/// Get the public key of this keypair.
+	pub fn public(&self) -> PublicKey {
+		PublicKey(self.0.verifying_key())
+	}
+
+	/// Get the secret key of this keypair.
+	pub fn secret(&self) -> SecretKey {
+		SecretKey(self.0.to_bytes())
+	}
+}
+
+impl fmt::Debug for Keypair {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		f.debug_struct("Keypair").field("public", &self.0.verifying_key()).finish()
+	}
+}
+
+impl From<litep2p_ed25519::Keypair> for Keypair {
+	fn from(kp: litep2p_ed25519::Keypair) -> Self {
+		Self::try_from_bytes(&mut kp.encode())
+			.expect("ed25519_dalek in substrate & litep2p to use the same format")
+	}
+}
+
+impl From<Keypair> for litep2p_ed25519::Keypair {
+	fn from(kp: Keypair) -> Self {
+		Self::decode(&mut kp.to_bytes())
+			.expect("ed25519_dalek in substrate & litep2p to use the same format")
+	}
+}
+
+impl From<libp2p_ed25519::Keypair> for Keypair {
+	fn from(kp: libp2p_ed25519::Keypair) -> Self {
+		Self::try_from_bytes(&mut kp.to_bytes())
+			.expect("ed25519_dalek in substrate & libp2p to use the same format")
+	}
+}
+
+impl From<Keypair> for libp2p_ed25519::Keypair {
+	fn from(kp: Keypair) -> Self {
+		Self::try_from_bytes(&mut kp.to_bytes())
+			.expect("ed25519_dalek in substrate & libp2p to use the same format")
+	}
+}
+
+/// Demote an Ed25519 keypair to a secret key.
+impl From<Keypair> for SecretKey {
+	fn from(kp: Keypair) -> SecretKey {
+		SecretKey(kp.0.to_bytes())
+	}
+}
+
+/// Promote an Ed25519 secret key into a keypair.
+impl From<SecretKey> for Keypair {
+	fn from(sk: SecretKey) -> Keypair {
+		let signing = ed25519::SigningKey::from_bytes(&sk.0);
+		Keypair(signing)
+	}
+}
+
+/// An Ed25519 public key.
+#[derive(Eq, Clone)]
+pub struct PublicKey(ed25519::VerifyingKey);
+
+impl fmt::Debug for PublicKey {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		f.write_str("PublicKey(compressed): ")?;
+		for byte in self.0.as_bytes() {
+			write!(f, "{byte:x}")?;
+		}
+		Ok(())
+	}
+}
+
+impl cmp::PartialEq for PublicKey {
+	fn eq(&self, other: &Self) -> bool {
+		self.0.as_bytes().eq(other.0.as_bytes())
+	}
+}
+
+impl hash::Hash for PublicKey {
+	fn hash<H: hash::Hasher>(&self, state: &mut H) {
+		self.0.as_bytes().hash(state);
+	}
+}
+
+impl cmp::PartialOrd for PublicKey {
+	fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
+		Some(self.cmp(other))
+	}
+}
+
+impl cmp::Ord for PublicKey {
+	fn cmp(&self, other: &Self) -> cmp::Ordering {
+		self.0.as_bytes().cmp(other.0.as_bytes())
+	}
+}
+
+impl PublicKey {
+	/// Verify the Ed25519 signature on a message using the public key.
+	pub fn verify(&self, msg: &[u8], sig: &[u8]) -> bool {
+		ed25519::Signature::try_from(sig).and_then(|s| self.0.verify(msg, &s)).is_ok()
+	}
+
+	/// Convert the public key to a byte array in compressed form, i.e.
+	/// where one coordinate is represented by a single bit.
+	pub fn to_bytes(&self) -> [u8; 32] {
+		self.0.to_bytes()
+	}
+
+	/// Try to parse a public key from a byte array containing the actual key as produced by
+	/// `to_bytes`.
+	pub fn try_from_bytes(k: &[u8]) -> Result<PublicKey, DecodingError> {
+		let k =
+			<[u8; 32]>::try_from(k).map_err(|e| DecodingError::PublicKeyParseError(Box::new(e)))?;
+		ed25519::VerifyingKey::from_bytes(&k)
+			.map_err(|e| DecodingError::PublicKeyParseError(Box::new(e)))
+			.map(PublicKey)
+	}
+
+	/// Convert public key to `PeerId`.
+	pub fn to_peer_id(&self) -> PeerId {
+		litep2p::PeerId::from(litep2p::crypto::PublicKey::Ed25519(self.clone().into())).into()
+	}
+}
+
+impl From<litep2p_ed25519::PublicKey> for PublicKey {
+	fn from(k: litep2p_ed25519::PublicKey) -> Self {
+		Self::try_from_bytes(&k.encode())
+			.expect("ed25519_dalek in substrate & litep2p to use the same format")
+	}
+}
+
+impl From<PublicKey> for litep2p_ed25519::PublicKey {
+	fn from(k: PublicKey) -> Self {
+		Self::decode(&k.to_bytes())
+			.expect("ed25519_dalek in substrate & litep2p to use the same format")
+	}
+}
+
+impl From<libp2p_ed25519::PublicKey> for PublicKey {
+	fn from(k: libp2p_ed25519::PublicKey) -> Self {
+		Self::try_from_bytes(&k.to_bytes())
+			.expect("ed25519_dalek in substrate & libp2p to use the same format")
+	}
+}
+
+impl From<PublicKey> for libp2p_ed25519::PublicKey {
+	fn from(k: PublicKey) -> Self {
+		Self::try_from_bytes(&k.to_bytes())
+			.expect("ed25519_dalek in substrate & libp2p to use the same format")
+	}
+}
+
+/// An Ed25519 secret key.
+#[derive(Clone)]
+pub struct SecretKey(ed25519::SecretKey);
+
+/// View the bytes of the secret key.
+impl AsRef<[u8]> for SecretKey {
+	fn as_ref(&self) -> &[u8] {
+		&self.0[..]
+	}
+}
+
+impl fmt::Debug for SecretKey {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		write!(f, "SecretKey")
+	}
+}
+
+impl SecretKey {
+	/// Generate a new Ed25519 secret key.
+	pub fn generate() -> SecretKey {
+		let signing = ed25519::SigningKey::generate(&mut rand::rngs::OsRng);
+		SecretKey(signing.to_bytes())
+	}
+
+	/// Try to parse an Ed25519 secret key from a byte slice
+	/// containing the actual key, zeroing the input on success.
+	/// If the bytes do not constitute a valid Ed25519 secret key, an error is
+	/// returned.
+	pub fn try_from_bytes(mut sk_bytes: impl AsMut<[u8]>) -> Result<SecretKey, DecodingError> {
+		let sk_bytes = sk_bytes.as_mut();
+		let secret = <[u8; 32]>::try_from(&*sk_bytes)
+			.map_err(|e| DecodingError::SecretKeyParseError(Box::new(e)))?;
+		sk_bytes.zeroize();
+		Ok(SecretKey(secret))
+	}
+
+	pub fn to_bytes(&self) -> [u8; 32] {
+		self.0
+	}
+}
+
+impl Drop for SecretKey {
+	fn drop(&mut self) {
+		self.0.zeroize();
+	}
+}
+
+impl From<litep2p_ed25519::SecretKey> for SecretKey {
+	fn from(sk: litep2p_ed25519::SecretKey) -> Self {
+		Self::try_from_bytes(&mut sk.to_bytes()).expect("Ed25519 key to be 32 bytes length")
+	}
+}
+
+impl From<SecretKey> for litep2p_ed25519::SecretKey {
+	fn from(sk: SecretKey) -> Self {
+		Self::from_bytes(&mut sk.to_bytes())
+			.expect("litep2p `SecretKey` to accept 32 bytes as Ed25519 key")
+	}
+}
+
+impl From<libp2p_ed25519::SecretKey> for SecretKey {
+	fn from(sk: libp2p_ed25519::SecretKey) -> Self {
+		Self::try_from_bytes(&mut sk.as_ref().to_owned())
+			.expect("Ed25519 key to be 32 bytes length")
+	}
+}
+
+impl From<SecretKey> for libp2p_ed25519::SecretKey {
+	fn from(sk: SecretKey) -> Self {
+		Self::try_from_bytes(&mut sk.to_bytes())
+			.expect("libp2p `SecretKey` to accept 32 bytes as Ed25519 key")
+	}
+}
+
+/// Error when decoding `ed25519`-related types.
+#[derive(Debug, thiserror::Error)]
+pub enum DecodingError {
+	#[error("failed to parse Ed25519 keypair: {0}")]
+	KeypairParseError(Box<dyn std::error::Error + Send + Sync>),
+	#[error("failed to parse Ed25519 secret key: {0}")]
+	SecretKeyParseError(Box<dyn std::error::Error + Send + Sync>),
+	#[error("failed to parse Ed25519 public key: {0}")]
+	PublicKeyParseError(Box<dyn std::error::Error + Send + Sync>),
+}
+
+#[cfg(test)]
+mod tests {
+	use super::*;
+	use quickcheck::*;
+
+	fn eq_keypairs(kp1: &Keypair, kp2: &Keypair) -> bool {
+		kp1.public() == kp2.public() && kp1.0.to_bytes() == kp2.0.to_bytes()
+	}
+
+	#[test]
+	fn ed25519_keypair_encode_decode() {
+		fn prop() -> bool {
+			let kp1 = Keypair::generate();
+			let mut kp1_enc = kp1.to_bytes();
+			let kp2 = Keypair::try_from_bytes(&mut kp1_enc).unwrap();
+			eq_keypairs(&kp1, &kp2) && kp1_enc.iter().all(|b| *b == 0)
+		}
+		QuickCheck::new().tests(10).quickcheck(prop as fn() -> _);
+	}
+
+	#[test]
+	fn ed25519_keypair_from_secret() {
+		fn prop() -> bool {
+			let kp1 = Keypair::generate();
+			let mut sk = kp1.0.to_bytes();
+			let kp2 = Keypair::from(SecretKey::try_from_bytes(&mut sk).unwrap());
+			eq_keypairs(&kp1, &kp2) && sk == [0u8; 32]
+		}
+		QuickCheck::new().tests(10).quickcheck(prop as fn() -> _);
+	}
+
+	#[test]
+	fn ed25519_signature() {
+		let kp = Keypair::generate();
+		let pk = kp.public();
+
+		let msg = "hello world".as_bytes();
+		let sig = kp.sign(msg);
+		assert!(pk.verify(msg, &sig));
+
+		let mut invalid_sig = sig.clone();
+		invalid_sig[3..6].copy_from_slice(&[10, 23, 42]);
+		assert!(!pk.verify(msg, &invalid_sig));
+
+		let invalid_msg = "h3ll0 w0rld".as_bytes();
+		assert!(!pk.verify(invalid_msg, &sig));
+	}
+
+	#[test]
+	fn substrate_kp_to_libs() {
+		let kp = Keypair::generate();
+		let kp_bytes = kp.to_bytes();
+		let kp1: libp2p_ed25519::Keypair = kp.clone().into();
+		let kp2: litep2p_ed25519::Keypair = kp.clone().into();
+		let kp3 = libp2p_ed25519::Keypair::try_from_bytes(&mut kp_bytes.clone()).unwrap();
+		let kp4 = litep2p_ed25519::Keypair::decode(&mut kp_bytes.clone()).unwrap();
+
+		assert_eq!(kp_bytes, kp1.to_bytes());
+		assert_eq!(kp_bytes, kp2.encode());
+
+		let msg = "hello world".as_bytes();
+		let sig = kp.sign(msg);
+		let sig1 = kp1.sign(msg);
+		let sig2 = kp2.sign(msg);
+		let sig3 = kp3.sign(msg);
+		let sig4 = kp4.sign(msg);
+
+		assert_eq!(sig, sig1);
+		assert_eq!(sig, sig2);
+		assert_eq!(sig, sig3);
+		assert_eq!(sig, sig4);
+
+		let pk1 = kp1.public();
+		let pk2 = kp2.public();
+		let pk3 = kp3.public();
+		let pk4 = kp4.public();
+
+		assert!(pk1.verify(msg, &sig));
+		assert!(pk2.verify(msg, &sig));
+		assert!(pk3.verify(msg, &sig));
+		assert!(pk4.verify(msg, &sig));
+	}
+
+	#[test]
+	fn litep2p_kp_to_substrate_kp() {
+		let kp = litep2p_ed25519::Keypair::generate();
+		let kp1: Keypair = kp.clone().into();
+		let kp2 = Keypair::try_from_bytes(&mut kp.encode()).unwrap();
+
+		assert_eq!(kp.encode(), kp1.to_bytes());
+
+		let msg = "hello world".as_bytes();
+		let sig = kp.sign(msg);
+		let sig1 = kp1.sign(msg);
+		let sig2 = kp2.sign(msg);
+
+		assert_eq!(sig, sig1);
+		assert_eq!(sig, sig2);
+
+		let pk1 = kp1.public();
+		let pk2 = kp2.public();
+
+		assert!(pk1.verify(msg, &sig));
+		assert!(pk2.verify(msg, &sig));
+	}
+
+	#[test]
+	fn libp2p_kp_to_substrate_kp() {
+		let kp = libp2p_ed25519::Keypair::generate();
+		let kp1: Keypair = kp.clone().into();
+		let kp2 = Keypair::try_from_bytes(&mut kp.to_bytes()).unwrap();
+
+		assert_eq!(kp.to_bytes(), kp1.to_bytes());
+
+		let msg = "hello world".as_bytes();
+		let sig = kp.sign(msg);
+		let sig1 = kp1.sign(msg);
+		let sig2 = kp2.sign(msg);
+
+		assert_eq!(sig, sig1);
+		assert_eq!(sig, sig2);
+
+		let pk1 = kp1.public();
+		let pk2 = kp2.public();
+
+		assert!(pk1.verify(msg, &sig));
+		assert!(pk2.verify(msg, &sig));
+	}
+
+	#[test]
+	fn substrate_pk_to_libs() {
+		let kp = Keypair::generate();
+		let pk = kp.public();
+		let pk_bytes = pk.to_bytes();
+		let pk1: libp2p_ed25519::PublicKey = pk.clone().into();
+		let pk2: litep2p_ed25519::PublicKey = pk.clone().into();
+		let pk3 = libp2p_ed25519::PublicKey::try_from_bytes(&pk_bytes).unwrap();
+		let pk4 = litep2p_ed25519::PublicKey::decode(&pk_bytes).unwrap();
+
+		assert_eq!(pk_bytes, pk1.to_bytes());
+		assert_eq!(pk_bytes, pk2.encode());
+
+		let msg = "hello world".as_bytes();
+		let sig = kp.sign(msg);
+
+		assert!(pk.verify(msg, &sig));
+		assert!(pk1.verify(msg, &sig));
+		assert!(pk2.verify(msg, &sig));
+		assert!(pk3.verify(msg, &sig));
+		assert!(pk4.verify(msg, &sig));
+	}
+
+	#[test]
+	fn litep2p_pk_to_substrate_pk() {
+		let kp = litep2p_ed25519::Keypair::generate();
+		let pk = kp.public();
+		let pk_bytes = pk.clone().encode();
+		let pk1: PublicKey = pk.clone().into();
+		let pk2 = PublicKey::try_from_bytes(&pk_bytes).unwrap();
+
+		assert_eq!(pk_bytes, pk1.to_bytes());
+
+		let msg = "hello world".as_bytes();
+		let sig = kp.sign(msg);
+
+		assert!(pk.verify(msg, &sig));
+		assert!(pk1.verify(msg, &sig));
+		assert!(pk2.verify(msg, &sig));
+	}
+
+	#[test]
+	fn libp2p_pk_to_substrate_pk() {
+		let kp = libp2p_ed25519::Keypair::generate();
+		let pk = kp.public();
+		let pk_bytes = pk.clone().to_bytes();
+		let pk1: PublicKey = pk.clone().into();
+		let pk2 = PublicKey::try_from_bytes(&pk_bytes).unwrap();
+
+		assert_eq!(pk_bytes, pk1.to_bytes());
+
+		let msg = "hello world".as_bytes();
+		let sig = kp.sign(msg);
+
+		assert!(pk.verify(msg, &sig));
+		assert!(pk1.verify(msg, &sig));
+		assert!(pk2.verify(msg, &sig));
+	}
+
+	#[test]
+	fn substrate_sk_to_libs() {
+		let sk = SecretKey::generate();
+		let sk_bytes = sk.to_bytes();
+		let sk1: libp2p_ed25519::SecretKey = sk.clone().into();
+		let sk2: litep2p_ed25519::SecretKey = sk.clone().into();
+		let sk3 = libp2p_ed25519::SecretKey::try_from_bytes(&mut sk_bytes.clone()).unwrap();
+		let sk4 = litep2p_ed25519::SecretKey::from_bytes(&mut sk_bytes.clone()).unwrap();
+
+		let kp: Keypair = sk.into();
+		let kp1: libp2p_ed25519::Keypair = sk1.into();
+		let kp2: litep2p_ed25519::Keypair = sk2.into();
+		let kp3: libp2p_ed25519::Keypair = sk3.into();
+		let kp4: litep2p_ed25519::Keypair = sk4.into();
+
+		let msg = "hello world".as_bytes();
+		let sig = kp.sign(msg);
+
+		assert_eq!(sig, kp1.sign(msg));
+		assert_eq!(sig, kp2.sign(msg));
+		assert_eq!(sig, kp3.sign(msg));
+		assert_eq!(sig, kp4.sign(msg));
+	}
+
+	#[test]
+	fn litep2p_sk_to_substrate_sk() {
+		let sk = litep2p_ed25519::SecretKey::generate();
+		let sk1: SecretKey = sk.clone().into();
+		let sk2 = SecretKey::try_from_bytes(&mut sk.to_bytes()).unwrap();
+
+		let kp: litep2p_ed25519::Keypair = sk.into();
+		let kp1: Keypair = sk1.into();
+		let kp2: Keypair = sk2.into();
+
+		let msg = "hello world".as_bytes();
+		let sig = kp.sign(msg);
+
+		assert_eq!(sig, kp1.sign(msg));
+		assert_eq!(sig, kp2.sign(msg));
+	}
+
+	#[test]
+	fn libp2p_sk_to_substrate_sk() {
+		let sk = libp2p_ed25519::SecretKey::generate();
+		let sk_bytes = sk.as_ref().to_owned();
+		let sk1: SecretKey = sk.clone().into();
+		let sk2 = SecretKey::try_from_bytes(sk_bytes).unwrap();
+
+		let kp: libp2p_ed25519::Keypair = sk.into();
+		let kp1: Keypair = sk1.into();
+		let kp2: Keypair = sk2.into();
+
+		let msg = "hello world".as_bytes();
+		let sig = kp.sign(msg);
+
+		assert_eq!(sig, kp1.sign(msg));
+		assert_eq!(sig, kp2.sign(msg));
+	}
+}
diff --git a/substrate/client/network/types/src/lib.rs b/substrate/client/network/types/src/lib.rs
index 9a126c48c7e..5684e38ab2e 100644
--- a/substrate/client/network/types/src/lib.rs
+++ b/substrate/client/network/types/src/lib.rs
@@ -13,6 +13,12 @@
 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 // GNU General Public License for more details.
 
-mod peer_id;
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+pub mod ed25519;
+pub mod multiaddr;
+pub mod multihash;
 
+mod peer_id;
 pub use peer_id::PeerId;
diff --git a/substrate/client/network/types/src/multiaddr.rs b/substrate/client/network/types/src/multiaddr.rs
new file mode 100644
index 00000000000..312bef9baab
--- /dev/null
+++ b/substrate/client/network/types/src/multiaddr.rs
@@ -0,0 +1,251 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+use litep2p::types::multiaddr::{
+	Error as LiteP2pError, Iter as LiteP2pIter, Multiaddr as LiteP2pMultiaddr,
+	Protocol as LiteP2pProtocol,
+};
+use std::{
+	fmt::{self, Debug, Display},
+	str::FromStr,
+};
+
+mod protocol;
+pub use protocol::Protocol;
+
+// Re-export the macro under shorter name under `multiaddr`.
+pub use crate::build_multiaddr as multiaddr;
+
+/// [`Multiaddr`] type used in Substrate. Converted to libp2p's `Multiaddr`
+/// or litep2p's `Multiaddr` when passed to the corresponding network backend.
+
+#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
+pub struct Multiaddr {
+	multiaddr: LiteP2pMultiaddr,
+}
+
+impl Multiaddr {
+	/// Create a new, empty multiaddress.
+	pub fn empty() -> Self {
+		Self { multiaddr: LiteP2pMultiaddr::empty() }
+	}
+
+	/// Adds an address component to the end of this multiaddr.
+	pub fn push(&mut self, p: Protocol<'_>) {
+		self.multiaddr.push(p.into())
+	}
+
+	/// Pops the last `Protocol` of this multiaddr, or `None` if the multiaddr is empty.
+	pub fn pop<'a>(&mut self) -> Option<Protocol<'a>> {
+		self.multiaddr.pop().map(Into::into)
+	}
+
+	/// Like [`Multiaddr::push`] but consumes `self`.
+	pub fn with(self, p: Protocol<'_>) -> Self {
+		self.multiaddr.with(p.into()).into()
+	}
+
+	/// Returns the components of this multiaddress.
+	pub fn iter(&self) -> Iter<'_> {
+		self.multiaddr.iter().into()
+	}
+
+	/// Return a copy of this [`Multiaddr`]'s byte representation.
+	pub fn to_vec(&self) -> Vec<u8> {
+		self.multiaddr.to_vec()
+	}
+}
+
+impl Display for Multiaddr {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		Display::fmt(&self.multiaddr, f)
+	}
+}
+
+/// Remove an extra layer of nestedness by deferring to the wrapped value's [`Debug`].
+impl Debug for Multiaddr {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		Debug::fmt(&self.multiaddr, f)
+	}
+}
+
+impl AsRef<[u8]> for Multiaddr {
+	fn as_ref(&self) -> &[u8] {
+		self.multiaddr.as_ref()
+	}
+}
+
+impl From<LiteP2pMultiaddr> for Multiaddr {
+	fn from(multiaddr: LiteP2pMultiaddr) -> Self {
+		Self { multiaddr }
+	}
+}
+
+impl From<Multiaddr> for LiteP2pMultiaddr {
+	fn from(multiaddr: Multiaddr) -> Self {
+		multiaddr.multiaddr
+	}
+}
+
+impl TryFrom<Vec<u8>> for Multiaddr {
+	type Error = ParseError;
+
+	fn try_from(v: Vec<u8>) -> Result<Self, ParseError> {
+		let multiaddr = LiteP2pMultiaddr::try_from(v)?;
+		Ok(Self { multiaddr })
+	}
+}
+
+/// Error when parsing a [`Multiaddr`] from string.
+#[derive(Debug, thiserror::Error)]
+pub enum ParseError {
+	/// Less data provided than indicated by length.
+	#[error("less data than indicated by length")]
+	DataLessThanLen,
+	/// Invalid multiaddress.
+	#[error("invalid multiaddress")]
+	InvalidMultiaddr,
+	/// Invalid protocol specification.
+	#[error("invalid protocol string")]
+	InvalidProtocolString,
+	/// Unknown protocol string identifier.
+	#[error("unknown protocol '{0}'")]
+	UnknownProtocolString(String),
+	/// Unknown protocol numeric id.
+	#[error("unknown protocol id {0}")]
+	UnknownProtocolId(u32),
+	/// Failed to decode unsigned varint.
+	#[error("failed to decode unsigned varint: {0}")]
+	InvalidUvar(Box<dyn std::error::Error + Send + Sync>),
+	/// Other error emitted when parsing into the wrapped type.
+	#[error("multiaddr parsing error: {0}")]
+	ParsingError(Box<dyn std::error::Error + Send + Sync>),
+}
+
+impl From<LiteP2pError> for ParseError {
+	fn from(error: LiteP2pError) -> Self {
+		match error {
+			LiteP2pError::DataLessThanLen => ParseError::DataLessThanLen,
+			LiteP2pError::InvalidMultiaddr => ParseError::InvalidMultiaddr,
+			LiteP2pError::InvalidProtocolString => ParseError::InvalidProtocolString,
+			LiteP2pError::UnknownProtocolString(s) => ParseError::UnknownProtocolString(s),
+			LiteP2pError::UnknownProtocolId(n) => ParseError::UnknownProtocolId(n),
+			LiteP2pError::InvalidUvar(e) => ParseError::InvalidUvar(Box::new(e)),
+			LiteP2pError::ParsingError(e) => ParseError::ParsingError(e),
+			error => ParseError::ParsingError(Box::new(error)),
+		}
+	}
+}
+
+impl FromStr for Multiaddr {
+	type Err = ParseError;
+
+	fn from_str(s: &str) -> Result<Self, Self::Err> {
+		let multiaddr = LiteP2pMultiaddr::from_str(s)?;
+		Ok(Self { multiaddr })
+	}
+}
+
+impl TryFrom<String> for Multiaddr {
+	type Error = ParseError;
+
+	fn try_from(s: String) -> Result<Multiaddr, Self::Error> {
+		Self::from_str(&s)
+	}
+}
+
+impl<'a> TryFrom<&'a str> for Multiaddr {
+	type Error = ParseError;
+
+	fn try_from(s: &'a str) -> Result<Multiaddr, Self::Error> {
+		Self::from_str(s)
+	}
+}
+
+/// Iterator over `Multiaddr` [`Protocol`]s.
+pub struct Iter<'a>(LiteP2pIter<'a>);
+
+impl<'a> Iterator for Iter<'a> {
+	type Item = Protocol<'a>;
+
+	fn next(&mut self) -> Option<Self::Item> {
+		self.0.next().map(Into::into)
+	}
+}
+
+impl<'a> From<LiteP2pIter<'a>> for Iter<'a> {
+	fn from(iter: LiteP2pIter<'a>) -> Self {
+		Self(iter)
+	}
+}
+
+impl<'a> IntoIterator for &'a Multiaddr {
+	type Item = Protocol<'a>;
+	type IntoIter = Iter<'a>;
+
+	fn into_iter(self) -> Iter<'a> {
+		self.multiaddr.into_iter().into()
+	}
+}
+
+impl<'a> FromIterator<Protocol<'a>> for Multiaddr {
+	fn from_iter<T>(iter: T) -> Self
+	where
+		T: IntoIterator<Item = Protocol<'a>>,
+	{
+		LiteP2pMultiaddr::from_iter(iter.into_iter().map(Into::into)).into()
+	}
+}
+
+impl<'a> From<Protocol<'a>> for Multiaddr {
+	fn from(p: Protocol<'a>) -> Multiaddr {
+		let protocol: LiteP2pProtocol = p.into();
+		let multiaddr: LiteP2pMultiaddr = protocol.into();
+		multiaddr.into()
+	}
+}
+
+/// Easy way for a user to create a `Multiaddr`.
+///
+/// Example:
+///
+/// ```rust
+/// use sc_network_types::build_multiaddr;
+/// let addr = build_multiaddr!(Ip4([127, 0, 0, 1]), Tcp(10500u16));
+/// ```
+///
+/// Each element passed to `multiaddr!` should be a variant of the `Protocol` enum. The
+/// optional parameter is turned into the proper type with the `Into` trait.
+///
+/// For example, `Ip4([127, 0, 0, 1])` works because `Ipv4Addr` implements `From<[u8; 4]>`.
+#[macro_export]
+macro_rules! build_multiaddr {
+    ($($comp:ident $(($param:expr))*),+) => {
+        {
+            use std::iter;
+            let elem = iter::empty::<$crate::multiaddr::Protocol>();
+            $(
+                let elem = {
+                    let cmp = $crate::multiaddr::Protocol::$comp $(( $param.into() ))*;
+                    elem.chain(iter::once(cmp))
+                };
+            )+
+            elem.collect::<$crate::multiaddr::Multiaddr>()
+        }
+    }
+}
diff --git a/substrate/client/network/types/src/multiaddr/protocol.rs b/substrate/client/network/types/src/multiaddr/protocol.rs
new file mode 100644
index 00000000000..800d08fe36b
--- /dev/null
+++ b/substrate/client/network/types/src/multiaddr/protocol.rs
@@ -0,0 +1,138 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+use crate::multihash::Multihash;
+use litep2p::types::multiaddr::Protocol as LiteP2pProtocol;
+use std::{
+	borrow::Cow,
+	net::{Ipv4Addr, Ipv6Addr},
+};
+
+/// [`Protocol`] describes all possible multiaddress protocols.
+#[derive(PartialEq, Eq, Clone, Debug)]
+pub enum Protocol<'a> {
+	Dccp(u16),
+	Dns(Cow<'a, str>),
+	Dns4(Cow<'a, str>),
+	Dns6(Cow<'a, str>),
+	Dnsaddr(Cow<'a, str>),
+	Http,
+	Https,
+	Ip4(Ipv4Addr),
+	Ip6(Ipv6Addr),
+	P2pWebRtcDirect,
+	P2pWebRtcStar,
+	WebRTC,
+	Certhash(Multihash),
+	P2pWebSocketStar,
+	/// Contains the "port" to contact. Similar to TCP or UDP, 0 means "assign me a port".
+	Memory(u64),
+	Onion(Cow<'a, [u8; 10]>, u16),
+	Onion3(Cow<'a, [u8; 35]>, u16),
+	P2p(Multihash),
+	P2pCircuit,
+	Quic,
+	QuicV1,
+	Sctp(u16),
+	Tcp(u16),
+	Tls,
+	Noise,
+	Udp(u16),
+	Udt,
+	Unix(Cow<'a, str>),
+	Utp,
+	Ws(Cow<'a, str>),
+	Wss(Cow<'a, str>),
+}
+
+impl<'a> From<LiteP2pProtocol<'a>> for Protocol<'a> {
+	fn from(protocol: LiteP2pProtocol<'a>) -> Self {
+		match protocol {
+			LiteP2pProtocol::Dccp(port) => Protocol::Dccp(port),
+			LiteP2pProtocol::Dns(str) => Protocol::Dns(str),
+			LiteP2pProtocol::Dns4(str) => Protocol::Dns4(str),
+			LiteP2pProtocol::Dns6(str) => Protocol::Dns6(str),
+			LiteP2pProtocol::Dnsaddr(str) => Protocol::Dnsaddr(str),
+			LiteP2pProtocol::Http => Protocol::Http,
+			LiteP2pProtocol::Https => Protocol::Https,
+			LiteP2pProtocol::Ip4(ipv4_addr) => Protocol::Ip4(ipv4_addr),
+			LiteP2pProtocol::Ip6(ipv6_addr) => Protocol::Ip6(ipv6_addr),
+			LiteP2pProtocol::P2pWebRtcDirect => Protocol::P2pWebRtcDirect,
+			LiteP2pProtocol::P2pWebRtcStar => Protocol::P2pWebRtcStar,
+			LiteP2pProtocol::WebRTC => Protocol::WebRTC,
+			LiteP2pProtocol::Certhash(multihash) => Protocol::Certhash(multihash.into()),
+			LiteP2pProtocol::P2pWebSocketStar => Protocol::P2pWebSocketStar,
+			LiteP2pProtocol::Memory(port) => Protocol::Memory(port),
+			LiteP2pProtocol::Onion(str, port) => Protocol::Onion(str, port),
+			LiteP2pProtocol::Onion3(addr) =>
+				Protocol::Onion3(Cow::Owned(*addr.hash()), addr.port()),
+			LiteP2pProtocol::P2p(multihash) => Protocol::P2p(multihash.into()),
+			LiteP2pProtocol::P2pCircuit => Protocol::P2pCircuit,
+			LiteP2pProtocol::Quic => Protocol::Quic,
+			LiteP2pProtocol::QuicV1 => Protocol::QuicV1,
+			LiteP2pProtocol::Sctp(port) => Protocol::Sctp(port),
+			LiteP2pProtocol::Tcp(port) => Protocol::Tcp(port),
+			LiteP2pProtocol::Tls => Protocol::Tls,
+			LiteP2pProtocol::Noise => Protocol::Noise,
+			LiteP2pProtocol::Udp(port) => Protocol::Udp(port),
+			LiteP2pProtocol::Udt => Protocol::Udt,
+			LiteP2pProtocol::Unix(str) => Protocol::Unix(str),
+			LiteP2pProtocol::Utp => Protocol::Utp,
+			LiteP2pProtocol::Ws(str) => Protocol::Ws(str),
+			LiteP2pProtocol::Wss(str) => Protocol::Wss(str),
+		}
+	}
+}
+
+impl<'a> From<Protocol<'a>> for LiteP2pProtocol<'a> {
+	fn from(protocol: Protocol<'a>) -> Self {
+		match protocol {
+			Protocol::Dccp(port) => LiteP2pProtocol::Dccp(port),
+			Protocol::Dns(str) => LiteP2pProtocol::Dns(str),
+			Protocol::Dns4(str) => LiteP2pProtocol::Dns4(str),
+			Protocol::Dns6(str) => LiteP2pProtocol::Dns6(str),
+			Protocol::Dnsaddr(str) => LiteP2pProtocol::Dnsaddr(str),
+			Protocol::Http => LiteP2pProtocol::Http,
+			Protocol::Https => LiteP2pProtocol::Https,
+			Protocol::Ip4(ipv4_addr) => LiteP2pProtocol::Ip4(ipv4_addr),
+			Protocol::Ip6(ipv6_addr) => LiteP2pProtocol::Ip6(ipv6_addr),
+			Protocol::P2pWebRtcDirect => LiteP2pProtocol::P2pWebRtcDirect,
+			Protocol::P2pWebRtcStar => LiteP2pProtocol::P2pWebRtcStar,
+			Protocol::WebRTC => LiteP2pProtocol::WebRTC,
+			Protocol::Certhash(multihash) => LiteP2pProtocol::Certhash(multihash.into()),
+			Protocol::P2pWebSocketStar => LiteP2pProtocol::P2pWebSocketStar,
+			Protocol::Memory(port) => LiteP2pProtocol::Memory(port),
+			Protocol::Onion(str, port) => LiteP2pProtocol::Onion(str, port),
+			Protocol::Onion3(str, port) => LiteP2pProtocol::Onion3((str.into_owned(), port).into()),
+			Protocol::P2p(multihash) => LiteP2pProtocol::P2p(multihash.into()),
+			Protocol::P2pCircuit => LiteP2pProtocol::P2pCircuit,
+			Protocol::Quic => LiteP2pProtocol::Quic,
+			Protocol::QuicV1 => LiteP2pProtocol::QuicV1,
+			Protocol::Sctp(port) => LiteP2pProtocol::Sctp(port),
+			Protocol::Tcp(port) => LiteP2pProtocol::Tcp(port),
+			Protocol::Tls => LiteP2pProtocol::Tls,
+			Protocol::Noise => LiteP2pProtocol::Noise,
+			Protocol::Udp(port) => LiteP2pProtocol::Udp(port),
+			Protocol::Udt => LiteP2pProtocol::Udt,
+			Protocol::Unix(str) => LiteP2pProtocol::Unix(str),
+			Protocol::Utp => LiteP2pProtocol::Utp,
+			Protocol::Ws(str) => LiteP2pProtocol::Ws(str),
+			Protocol::Wss(str) => LiteP2pProtocol::Wss(str),
+		}
+	}
+}
diff --git a/substrate/client/network/types/src/multihash.rs b/substrate/client/network/types/src/multihash.rs
new file mode 100644
index 00000000000..91f5b6353a7
--- /dev/null
+++ b/substrate/client/network/types/src/multihash.rs
@@ -0,0 +1,192 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+//! [`Multihash`] implemenattion used by substrate. Currently it's a wrapper over
+//! multihash used by litep2p, but it can be switched to other implementation if needed.
+
+use litep2p::types::multihash::{
+	Code as LiteP2pCode, Error as LiteP2pError, Multihash as LiteP2pMultihash, MultihashDigest as _,
+};
+use std::fmt::{self, Debug};
+
+/// Default [`Multihash`] implementations. Only hashes used by substrate are defined.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum Code {
+	/// Identity hasher.
+	Identity,
+	/// SHA-256 (32-byte hash size).
+	Sha2_256,
+}
+
+impl Code {
+	/// Calculate digest using this [`Code`]'s hashing algorithm.
+	pub fn digest(&self, input: &[u8]) -> Multihash {
+		LiteP2pCode::from(*self).digest(input).into()
+	}
+}
+
+/// Error generated when converting to [`Code`].
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+	/// Invalid multihash size.
+	#[error("invalid multihash size '{0}'")]
+	InvalidSize(u64),
+	/// The multihash code is not supported.
+	#[error("unsupported multihash code '{0:x}'")]
+	UnsupportedCode(u64),
+	/// Catch-all for other errors emitted when converting `u64` code to enum or parsing multihash
+	/// from bytes. Never generated as of multihash-0.17.0.
+	#[error("other error: {0}")]
+	Other(Box<dyn std::error::Error + Send + Sync>),
+}
+
+impl From<LiteP2pError> for Error {
+	fn from(error: LiteP2pError) -> Self {
+		match error {
+			LiteP2pError::InvalidSize(s) => Self::InvalidSize(s),
+			LiteP2pError::UnsupportedCode(c) => Self::UnsupportedCode(c),
+			e => Self::Other(Box::new(e)),
+		}
+	}
+}
+
+impl From<Code> for LiteP2pCode {
+	fn from(code: Code) -> Self {
+		match code {
+			Code::Identity => LiteP2pCode::Identity,
+			Code::Sha2_256 => LiteP2pCode::Sha2_256,
+		}
+	}
+}
+
+impl TryFrom<LiteP2pCode> for Code {
+	type Error = Error;
+
+	fn try_from(code: LiteP2pCode) -> Result<Self, Self::Error> {
+		match code {
+			LiteP2pCode::Identity => Ok(Code::Identity),
+			LiteP2pCode::Sha2_256 => Ok(Code::Sha2_256),
+			_ => Err(Error::UnsupportedCode(code.into())),
+		}
+	}
+}
+
+impl TryFrom<u64> for Code {
+	type Error = Error;
+
+	fn try_from(code: u64) -> Result<Self, Self::Error> {
+		match LiteP2pCode::try_from(code) {
+			Ok(code) => code.try_into(),
+			Err(e) => Err(e.into()),
+		}
+	}
+}
+
+impl From<Code> for u64 {
+	fn from(code: Code) -> Self {
+		LiteP2pCode::from(code).into()
+	}
+}
+
+#[derive(Clone, Copy, Hash, PartialEq, Eq, Ord, PartialOrd)]
+pub struct Multihash {
+	multihash: LiteP2pMultihash,
+}
+
+impl Multihash {
+	/// Multihash code.
+	pub fn code(&self) -> u64 {
+		self.multihash.code()
+	}
+
+	/// Multihash digest.
+	pub fn digest(&self) -> &[u8] {
+		self.multihash.digest()
+	}
+
+	/// Wraps the digest in a multihash.
+	pub fn wrap(code: u64, input_digest: &[u8]) -> Result<Self, Error> {
+		LiteP2pMultihash::wrap(code, input_digest).map(Into::into).map_err(Into::into)
+	}
+
+	/// Parses a multihash from bytes.
+	///
+	/// You need to make sure the passed in bytes have the length of 64.
+	pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
+		LiteP2pMultihash::from_bytes(bytes).map(Into::into).map_err(Into::into)
+	}
+
+	/// Returns the bytes of a multihash.
+	pub fn to_bytes(&self) -> Vec<u8> {
+		self.multihash.to_bytes()
+	}
+}
+
+/// Remove extra layer of nestedness by deferring to the wrapped value's [`Debug`].
+impl Debug for Multihash {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		Debug::fmt(&self.multihash, f)
+	}
+}
+
+impl From<LiteP2pMultihash> for Multihash {
+	fn from(multihash: LiteP2pMultihash) -> Self {
+		Multihash { multihash }
+	}
+}
+
+impl From<Multihash> for LiteP2pMultihash {
+	fn from(multihash: Multihash) -> Self {
+		multihash.multihash
+	}
+}
+
+// TODO: uncomment this after upgrading `multihash` crate to v0.19.1.
+//
+// impl From<multihash::Multihash<64>> for Multihash {
+// 	fn from(generic: multihash::MultihashGeneric<64>) -> Self {
+// 		LiteP2pMultihash::wrap(generic.code(), generic.digest())
+// 			.expect("both have size 64; qed")
+// 			.into()
+// 	}
+// }
+//
+// impl From<Multihash> for multihash::Multihash<64> {
+// 	fn from(multihash: Multihash) -> Self {
+// 		multihash::Multihash::<64>::wrap(multihash.code(), multihash.digest())
+// 			.expect("both have size 64; qed")
+// 	}
+// }
+
+#[cfg(test)]
+mod tests {
+	use super::*;
+
+	#[test]
+	fn code_from_u64() {
+		assert_eq!(Code::try_from(0x00).unwrap(), Code::Identity);
+		assert_eq!(Code::try_from(0x12).unwrap(), Code::Sha2_256);
+		assert!(matches!(Code::try_from(0x01).unwrap_err(), Error::UnsupportedCode(0x01)));
+	}
+
+	#[test]
+	fn code_into_u64() {
+		assert_eq!(u64::from(Code::Identity), 0x00);
+		assert_eq!(u64::from(Code::Sha2_256), 0x12);
+	}
+}
diff --git a/substrate/client/network/types/src/peer_id.rs b/substrate/client/network/types/src/peer_id.rs
index 14ac4a1e9aa..076be0a66c7 100644
--- a/substrate/client/network/types/src/peer_id.rs
+++ b/substrate/client/network/types/src/peer_id.rs
@@ -16,8 +16,10 @@
 // You should have received a copy of the GNU General Public License
 // along with this program. If not, see <https://www.gnu.org/licenses/>.
 
-use multiaddr::{Multiaddr, Protocol};
-use multihash::{Code, Error, Multihash};
+use crate::{
+	multiaddr::{Multiaddr, Protocol},
+	multihash::{Code, Error, Multihash},
+};
 use rand::Rng;
 
 use std::{fmt, hash::Hash, str::FromStr};
@@ -185,7 +187,7 @@ pub enum ParseError {
 	#[error("unsupported multihash code '{0}'")]
 	UnsupportedCode(u64),
 	#[error("invalid multihash")]
-	InvalidMultihash(#[from] multihash::Error),
+	InvalidMultihash(#[from] crate::multihash::Error),
 }
 
 impl FromStr for PeerId {
diff --git a/substrate/client/telemetry/src/endpoints.rs b/substrate/client/telemetry/src/endpoints.rs
index c7a60726a56..c49b114152a 100644
--- a/substrate/client/telemetry/src/endpoints.rs
+++ b/substrate/client/telemetry/src/endpoints.rs
@@ -16,7 +16,7 @@
 // You should have received a copy of the GNU General Public License
 // along with this program. If not, see <https://www.gnu.org/licenses/>.
 
-use sc_network::{multiaddr, Multiaddr};
+use libp2p::multiaddr::{self, Multiaddr};
 use serde::{Deserialize, Deserializer, Serialize};
 
 /// List of telemetry servers we want to talk to. Contains the URL of the server, and the
diff --git a/substrate/client/telemetry/src/lib.rs b/substrate/client/telemetry/src/lib.rs
index f8a201e7611..7e3a4ee8639 100644
--- a/substrate/client/telemetry/src/lib.rs
+++ b/substrate/client/telemetry/src/lib.rs
@@ -37,9 +37,9 @@
 #![warn(missing_docs)]
 
 use futures::{channel::mpsc, prelude::*};
+use libp2p::Multiaddr;
 use log::{error, warn};
 use parking_lot::Mutex;
-use sc_network::Multiaddr;
 use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender};
 use serde::Serialize;
 use std::{
diff --git a/substrate/client/telemetry/src/node.rs b/substrate/client/telemetry/src/node.rs
index 9b2443799d3..0bbdbfb622e 100644
--- a/substrate/client/telemetry/src/node.rs
+++ b/substrate/client/telemetry/src/node.rs
@@ -18,9 +18,8 @@
 
 use crate::TelemetryPayload;
 use futures::{channel::mpsc, prelude::*};
-use libp2p::core::transport::Transport;
+use libp2p::{core::transport::Transport, Multiaddr};
 use rand::Rng as _;
-use sc_network::Multiaddr;
 use std::{
 	fmt, mem,
 	pin::Pin,
-- 
GitLab