Repository: tangle-network/gadget (language: rust)

Issue:
Gadget SDK - Keystore Module
### Overview

Every gadget would definitely need access to some sort of key management, like signing transactions and signing messages over network.

A Keystore should be exposed as a trait with default implementations that could be utilized as required, so we split the keystore into a frontend and a backend, with a trait that sits in between.

Possible Backends:
1. in Memory Keystore (useful for testing)
2. File-based Keystore
3. AWS KMS based Keystore
4. GCP CKM based Keystore
…etc.

## Keystore backend trait design

```rs
pub trait KeystoreBackend {
	/// Generate a new sr25519 key pair with an optional seed.
	///
	/// Returns an `sr25519::Public` key of the generated key pair or an `Err` if
	/// something failed during key generation.
	fn sr25519_generate_new(
		&self,
		seed: Option<&str>,
	) -> Result<sr25519::Public, Error>;
	/// Generate an sr25519 signature for a given message.
	///
	/// Receives an [`sr25519::Public`] key to be able to map
	/// them to a private key that exists in the keystore.
	///
	/// Returns an [`sr25519::Signature`] or `None` in case the given 
	/// `public` doesn't exist in the keystore.
	/// An `Err` will be returned if generating the signature itself failed.
	fn sr25519_sign(
		&self,
		public: &sr25519::Public,
		msg: &[u8],
	) -> Result<Option<sr25519::Signature>, Error>;
	/// Generate a new ed25519 key pair with an optional seed.
	///
	/// Returns an `ed25519::Public` key of the generated key pair or an `Err` if
	/// something failed during key generation.
	fn ed25519_generate_new(
		&self,
		seed: Option<&str>,
	) -> Result<ed25519::Public, Error>;
	/// Generate an ed25519 signature for a given message.
	///
	/// Receives an [`ed25519::Public`] key to be able to map
	/// it to a private key that exists in the keystore.
	///
	/// Returns an [`ed25519::Signature`] or `None` in case the given 
	/// `public` doesn't exist in the keystore.
	/// An `Err` will be returned if generating the signature itself failed.
	fn ed25519_sign(
		&self,
		public: &ed25519::Public,
		msg: &[u8],
	) -> Result<Option<ed25519::Signature>, Error>;
	/// Generate a new ecdsa key pair with an optional seed.
	///
	/// Returns an `ecdsa::Public` key of the generated key pair or an `Err` if
	/// something failed during key generation.
	fn ecdsa_generate_new(
		&self,
		seed: Option<&str>,
	) -> Result<ecdsa::Public, Error>;
	/// Generate an ecdsa signature for a given message.
	///
	/// Receives an [`ecdsa::Public`] key to be able to map
	/// it to a private key that exists in the keystore.
	///
	/// Returns an [`ecdsa::Signature`] or `None` in case the given 
	/// `public` doesn't exist in the keystore.
	/// An `Err` will be returned if generating the signature itself failed.
	fn ecdsa_sign(
		&self,
		public: &ecdsa::Public,
		msg: &[u8],
	) -> Result<Option<ecdsa::Signature>, Error>;
	/// Generate a new bls381 key pair with an optional seed.
	///
	/// Returns an `bls381::Public` key of the generated key pair or an `Err` if
	/// something failed during key generation.
	fn bls381_generate_new(
		&self,
		seed: Option<&str>,
	) -> Result<bls381::Public, Error>;
	/// Generate an bls381 signature for a given message.
	///
	/// Receives an [`bls381::Public`] key to be able to map
	/// it to a private key that exists in the keystore.
	///
	/// Returns an [`bls381::Signature`] or `None` in case the given 
	/// `public` doesn't exist in the keystore.
	/// An `Err` will be returned if generating the signature itself failed.
	fn bls381_sign(
		&self,
		public: &bls381::Public,
		msg: &[u8],
	) -> Result<Option<bls381::Signature>, Error>;
	/// Checks if the private key for the given public key exist.
	///
	/// Returns `true` if the private key could be found.
	fn has_key(&self, public: &[u8]) -> bool;
	/// Returns the Keypair for the given [`sr25519::Public`] if it does exist, otherwise returns `None`.
    /// An `Err` will be returned if finding the key operation itself failed.
	fn expose_sr25519_pair(&self, public: &sr25519::Public) -> Result<Option<sr25519::Pair>, Error>;
	/// Returns the Keypair for the given [`ecdsa::Public`] if it does exist, otherwise returns `None`.
    /// An `Err` will be returned if finding the key operation itself failed.
	fn expose_ecdsa_pair(&self, public: &ecdsa::Public) -> Result<Option<ecdsa::Pair>, Error>;
	/// Returns the Keypair for the given [`ed25519::Public`] if it does exist, otherwise returns `None`.
    /// An `Err` will be returned if finding the key operation itself failed.
	fn expose_ed25519_pair(&self, public: &ed25519::Public) -> Result<Option<ed25519::Pair>, Error>;
	/// Returns the Keypair for the given [`bls381::Public`] if it does exist, otherwise returns `None`.
    /// An `Err` will be returned if finding the key operation itself failed.
	fn expose_bls381_pair(&self, public: &bls381::Public) -> Result<Option<bls381::Pair>, Error>;
}
```

### Constraints

1. This new Keystore should not directly depend on sp-* or sc-* crates, instead we could have a Substrate Keystore backend behind a feature flag.
2. For each backend implemented, we should keep it behind a feature flag (the mem backend is enabled by default).



Discussion / hints:

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index b32419175..bfa200561 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2234,6 +2234,16 @@ dependencies = [
  "dirs-sys-next",
 ]
 
+[[package]]
+name = "dirs-next"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
+dependencies = [
+ "cfg-if",
+ "dirs-sys-next",
+]
+
 [[package]]
 name = "dirs-sys"
 version = "0.4.1"
@@ -3513,6 +3523,29 @@ dependencies = [
  "wasmtimer",
 ]
 
+[[package]]
+name = "gadget-sdk"
+version = "0.1.0"
+dependencies = [
+ "ed25519-zebra 4.0.3",
+ "hex",
+ "hyper 0.14.28",
+ "k256",
+ "log",
+ "parking_lot 0.12.2",
+ "prometheus",
+ "rand",
+ "rand_core 0.6.4",
+ "schnorrkel",
+ "slog",
+ "slog-term",
+ "thiserror",
+ "tokio",
+ "tracing 0.1.40",
+ "tracing-log 0.2.0",
+ "w3f-bls",
+]
+
 [[package]]
 name = "generic-array"
 version = "0.12.4"
@@ -9814,6 +9847,25 @@ dependencies = [
  "subtle 2.5.0",
 ]
 
+[[package]]
+name = "slog"
+version = "2.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8347046d4ebd943127157b94d63abb990fcf729dc4e9978927fdf4ac3c998d06"
+
+[[package]]
+name = "slog-term"
+version = "2.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6e022d0b998abfe5c3782c1f03551a596269450ccd677ea51c56f8b214610e8"
+dependencies = [
+ "is-terminal",
+ "slog",
+ "term",
+ "thread_local",
+ "time",
+]
+
 [[package]]
 name = "smallvec"
 version = "1.13.2"
@@ -11554,6 +11606,17 @@ dependencies = [
  "windows-sys 0.52.0",
 ]
 
+[[package]]
+name = "term"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f"
+dependencies = [
+ "dirs-next",
+ "rustversion",
+ "winapi",
+]
+
 [[package]]
 name = "termcolor"
 version = "1.4.1"
@@ -13638,9 +13701,9 @@ dependencies = [
 
 [[package]]
 name = "zeroize"
-version = "1.7.0"
+version = "1.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
+checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
 dependencies = [
  "zeroize_derive",
 ]
diff --git a/Cargo.toml b/Cargo.toml
index 4a2d6f303..48980ec0a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,8 +8,16 @@ members = [
     "protocol-macros",
     "shell-manager",
     "shell-sdk",
+    "sdk",
 ]
 
+[workspace.package]
+authors = ["Webb Technologies Inc."]
+edition = "2021"
+license = "Unlicense"
+homepage = "https://tangle.tools"
+repository = "https://github.com/webb-tools/gadget"
+
 [workspace.dependencies]
 gadget-core = { path = "./core" }
 gadget-common = { path = "./common", default-features = false }
@@ -178,4 +186,4 @@ sp-wasm-interface = { git = "https://github.com/paritytech/polkadot-sdk", branch
 wasmer = { version = "4.2.6", default-features = false }
 
 [profile.dev.package.backtrace]
-opt-level = 3
\ No newline at end of file
+opt-level = 3
diff --git a/common/src/gadget/mod.rs b/common/src/gadget/mod.rs
index be971722b..6aa6044a2 100644
--- a/common/src/gadget/mod.rs
+++ b/common/src/gadget/mod.rs
@@ -16,7 +16,6 @@ use std::sync::Arc;
 use std::time::Duration;
 
 pub mod message;
-pub mod metrics;
 pub mod network;
 pub mod work_manager;
 
diff --git a/flake.lock b/flake.lock
index 5c3974be1..7e0f71abc 100644
--- a/flake.lock
+++ b/flake.lock
@@ -5,11 +5,11 @@
         "systems": "systems"
       },
       "locked": {
-        "lastModified": 1709126324,
-        "narHash": "sha256-q6EQdSeUZOG26WelxqkmR7kArjgWCdw5sfJVHPH/7j8=",
+        "lastModified": 1710146030,
+        "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
         "owner": "numtide",
         "repo": "flake-utils",
-        "rev": "d465f4819400de7c8d874d50b982301f28a84605",
+        "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
         "type": "github"
       },
       "original": {
@@ -20,11 +20,11 @@
     },
     "nixpkgs": {
       "locked": {
-        "lastModified": 1709780214,
-        "narHash": "sha256-p4iDKdveHMhfGAlpxmkCtfQO3WRzmlD11aIcThwPqhk=",
+        "lastModified": 1716358718,
+        "narHash": "sha256-NQbegJb2ZZnAqp2EJhWwTf6DrZXSpA6xZCEq+RGV1r0=",
         "owner": "NixOS",
         "repo": "nixpkgs",
-        "rev": "f945939fd679284d736112d3d5410eb867f3b31c",
+        "rev": "3f316d2a50699a78afe5e77ca486ad553169061e",
         "type": "github"
       },
       "original": {
@@ -51,11 +51,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1709604635,
-        "narHash": "sha256-le4fwmWmjGRYWwkho0Gr7mnnZndOOe4XGbLw68OvF40=",
+        "lastModified": 1716430594,
+        "narHash": "sha256-vdVzaGD5p+KG7XHepIeX5rUPmdzEcF2w6rhqfr0SNkI=",
         "owner": "oxalica",
         "repo": "rust-overlay",
-        "rev": "e86c0fb5d3a22a5f30d7f64ecad88643fe26449d",
+        "rev": "ee0db3aeebafeaada2b98d076de6d314b4c8682e",
         "type": "github"
       },
       "original": {
diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml
new file mode 100644
index 000000000..861367c30
--- /dev/null
+++ b/sdk/Cargo.toml
@@ -0,0 +1,68 @@
+[package]
+name = "gadget-sdk"
+version = "0.1.0"
+authors.workspace = true
+edition.workspace = true
+homepage.workspace = true
+repository.workspace = true
+
+
+[dependencies]
+thiserror = { workspace = true }
+parking_lot = { workspace = true }
+rand_core = { version = "0.6.4", default-features = false }
+rand = { workspace = true, optional = true }
+hex = { workspace = true, default-features = false }
+
+# Keystore deps
+k256 = { version = "0.13", default-features = false, features = ["arithmetic", "ecdsa"], optional = true  }
+ed25519-zebra = { version = "4", default-features = false, features = [], optional = true }
+schnorrkel = { version = "0.11.4", features = ["preaudit_deprecated"], default-features = false, optional = true }
+w3f-bls = { version = "0.1.3", default-features = false, optional = true }
+
+# Metrics deps
+hyper = { version = "0.14.16", default-features = false, features = ["http1", "server", "tcp"] }
+log = { workspace = true, default-features = true }
+prometheus = { version = "0.13.0", default-features = false }
+tokio = { version = "1.22.0", features = ["rt-multi-thread", "parking_lot"] }
+
+# Logging deps
+tracing = "0.1.40"
+tracing-log = "0.2.0"
+slog = "2.7.0"
+
+[dev-dependencies]
+hyper = { version = "0.14.16", default-features = false, features = ["client"] }
+slog-term = "2.9.1"
+
+[features]
+default = ["full", "std"]
+std = [
+  "alloc",
+  "rand",
+  "getrandom",
+  "hex/std",
+]
+alloc = ["rand_core/alloc", "hex/alloc"]
+full = [
+  "keystore",
+]
+
+# Randomness
+getrandom = ["rand_core/getrandom"]
+
+# Keystore features
+keystore = [
+  "keystore-ecdsa",
+  "keystore-sr25519",
+  "keystore-ed25519",
+  "keystore-bls381",
+  "keystore-fs",
+]
+keystore-ecdsa = ["k256"]
+keystore-sr25519 = ["schnorrkel"]
+keystore-ed25519 = ["ed25519-zebra"]
+keystore-bls381 = ["w3f-bls"]
+# Keystore backends
+# Requires: std
+keystore-fs = []
diff --git a/sdk/src/keystore/backend/fs.rs b/sdk/src/keystore/backend/fs.rs
new file mode 100644
index 000000000..8da44147e
--- /dev/null
+++ b/sdk/src/keystore/backend/fs.rs
@@ -0,0 +1,270 @@
+//! Filesystem-based keystore backend.
+
+use std::{fs, io::Write, path::PathBuf};
+
+use crate::keystore::{bls381, ecdsa, ed25519, sr25519, Backend, Error};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[repr(u16)]
+enum KeyType {
+    Sr25519 = 0x00,
+    Ed25519 = 0x01,
+    Ecdsa = 0x02,
+    Bls381 = 0x03,
+}
+
+/// The filesystem keystore backend. It stores keys in files, where each file
+/// is named after the public key and contains the private key.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct FilesystemKeystore {
+    /// The root directory where the keys are stored.
+    root: PathBuf,
+}
+
+impl FilesystemKeystore {
+    /// Open the store at the given path.
+    ///
+    /// # Errors
+    /// An error will be returned if the root directory cannot be created.
+    pub fn open<T: Into<PathBuf>>(path: T) -> Result<Self, Error> {
+        let root = path.into();
+        fs::create_dir_all(&root)?;
+
+        Ok(Self { root })
+    }
+
+    /// Write the given `data` to `file`.
+    fn write_to_file(file: PathBuf, data: &[u8]) -> Result<(), Error> {
+        let mut file = fs::File::create(file)?;
+
+        #[cfg(target_family = "unix")]
+        {
+            use std::os::unix::fs::PermissionsExt;
+            file.set_permissions(fs::Permissions::from_mode(0o600))?;
+        }
+
+        file.write_all(data)?;
+        file.flush()?;
+        Ok(())
+    }
+
+    /// Get the file path for the given public key and key type.
+    fn key_file_path(&self, public: &[u8], key_type: KeyType) -> PathBuf {
+        let key_type = key_type as u16;
+        let public_key = hex::encode(public);
+        let key_type = hex::encode(key_type.to_be_bytes());
+        let file_name = format!("{key_type}{public_key}");
+        self.root.join(file_name)
+    }
+
+    /// Get the key phrase for a given public key and key type.
+    fn secret_by_type(&self, public: &[u8], key_type: KeyType) -> Result<Option<Vec<u8>>, Error> {
+        let path = self.key_file_path(public, key_type);
+        if path.exists() {
+            let content = fs::read(path)?;
+            Ok(Some(content))
+        } else {
+            Ok(None)
+        }
+    }
+
+    /// Iterate over all public keys of a given key type.
+    /// skipping any invalid files.
+    fn iter_keys(&self, key_type: KeyType) -> Box<dyn Iterator<Item = Vec<u8>>> {
+        let key_type = key_type as u16;
+        let key_type = key_type.to_be_bytes();
+        let prefix = hex::encode(key_type);
+        let res = fs::read_dir(&self.root);
+        match res {
+            Ok(r) => Box::new(r.filter_map(move |entry| {
+                let entry = entry.ok()?;
+                let file_name = entry.file_name().into_string().ok()?;
+                if file_name.starts_with(&prefix) {
+                    let public = file_name.strip_prefix(&prefix)?;
+                    hex::decode(public).ok()
+                } else {
+                    None
+                }
+            })),
+            Err(_) => Box::new(core::iter::empty()),
+        }
+    }
+}
+
+impl Backend for FilesystemKeystore {
+    #[cfg(feature = "keystore-sr25519")]
+    fn sr25519_generate_new(&self, seed: Option<&[u8]>) -> Result<sr25519::Public, Error> {
+        let secret = sr25519::generate_with_optional_seed(seed)?;
+        let public = secret.to_public();
+        let path = self.key_file_path(public.as_ref(), KeyType::Sr25519);
+        Self::write_to_file(path, &secret.to_bytes())?;
+        Ok(public)
+    }
+
+    #[cfg(feature = "keystore-sr25519")]
+    fn sr25519_sign(
+        &self,
+        public: &sr25519::Public,
+        msg: &[u8],
+    ) -> Result<Option<sr25519::Signature>, Error> {
+        let secret_bytes = self.secret_by_type(public.as_ref(), KeyType::Sr25519)?;
+        if let Some(buf) = secret_bytes {
+            let secret = sr25519::secret_from_bytes(&buf)?;
+            Ok(Some(sr25519::sign(&secret, msg)?))
+        } else {
+            Ok(None)
+        }
+    }
+
+    #[cfg(feature = "keystore-ed25519")]
+    fn ed25519_generate_new(&self, seed: Option<&[u8]>) -> Result<ed25519::Public, Error> {
+        let secret = ed25519::generate_with_optional_seed(seed)?;
+        let public = ed25519::to_public(&secret);
+        let path = self.key_file_path(public.as_ref(), KeyType::Ed25519);
+        Self::write_to_file(path, secret.as_ref())?;
+        Ok(public)
+    }
+
+    #[cfg(feature = "keystore-ed25519")]
+    fn ed25519_sign(
+        &self,
+        public: &ed25519::Public,
+        msg: &[u8],
+    ) -> Result<Option<ed25519::Signature>, Error> {
+        let secret_bytes = self.secret_by_type(public.as_ref(), KeyType::Ed25519)?;
+        if let Some(buf) = secret_bytes {
+            let secret = ed25519::secret_from_bytes(&buf)?;
+            Ok(Some(ed25519::sign(&secret, msg)))
+        } else {
+            Ok(None)
+        }
+    }
+
+    #[cfg(feature = "keystore-ecdsa")]
+    fn ecdsa_generate_new(&self, seed: Option<&[u8]>) -> Result<ecdsa::Public, Error> {
+        let secret = ecdsa::generate_with_optional_seed(seed)?;
+        let public = secret.public_key();
+        let path = self.key_file_path(&public.to_sec1_bytes(), KeyType::Ecdsa);
+        Self::write_to_file(path, &secret.to_bytes()[..])?;
+        Ok(public)
+    }
+
+    #[cfg(feature = "keystore-ecdsa")]
+    fn ecdsa_sign(
+        &self,
+        public: &ecdsa::Public,
+        msg: &[u8],
+    ) -> Result<Option<ecdsa::Signature>, Error> {
+        let secret_bytes = self.secret_by_type(&public.to_sec1_bytes(), KeyType::Ecdsa)?;
+        if let Some(buf) = secret_bytes {
+            let secret = ecdsa::secret_from_bytes(&buf)?;
+            Ok(Some(ecdsa::sign(&secret, msg)))
+        } else {
+            Ok(None)
+        }
+    }
+
+    #[cfg(feature = "keystore-bls381")]
+    fn bls381_generate_new(&self, seed: Option<&[u8]>) -> Result<bls381::Public, Error> {
+        use w3f_bls::SerializableToBytes;
+
+        let secret = bls381::generate_with_optional_seed(seed);
+        let public = bls381::to_public(&secret);
+        let path = self.key_file_path(&public.to_bytes(), KeyType::Bls381);
+        Self::write_to_file(path, &secret.to_bytes())?;
+        Ok(public)
+    }
+
+    #[cfg(feature = "keystore-bls381")]
+    fn bls381_sign(
+        &self,
+        public: &bls381::Public,
+        msg: &[u8],
+    ) -> Result<Option<bls381::Signature>, Error> {
+        use w3f_bls::SerializableToBytes;
+
+        let secret_bytes = self.secret_by_type(&public.to_bytes(), KeyType::Bls381)?;
+        if let Some(buf) = secret_bytes {
+            let mut secret = bls381::secret_from_bytes(&buf);
+            Ok(Some(bls381::sign(&mut secret, msg)))
+        } else {
+            Ok(None)
+        }
+    }
+
+    #[cfg(feature = "keystore-sr25519")]
+    fn expose_sr25519_secret(
+        &self,
+        public: &sr25519::Public,
+    ) -> Result<Option<sr25519::Secret>, Error> {
+        let secret_bytes = self.secret_by_type(public.as_ref(), KeyType::Sr25519)?;
+        if let Some(buf) = secret_bytes {
+            Ok(Some(sr25519::secret_from_bytes(&buf)?))
+        } else {
+            Ok(None)
+        }
+    }
+
+    #[cfg(feature = "keystore-ecdsa")]
+    fn expose_ecdsa_secret(&self, public: &ecdsa::Public) -> Result<Option<ecdsa::Secret>, Error> {
+        let secret_bytes = self.secret_by_type(&public.to_sec1_bytes(), KeyType::Ecdsa)?;
+        if let Some(buf) = secret_bytes {
+            Ok(Some(ecdsa::secret_from_bytes(&buf)?))
+        } else {
+            Ok(None)
+        }
+    }
+
+    #[cfg(feature = "keystore-ed25519")]
+    fn expose_ed25519_secret(
+        &self,
+        public: &ed25519::Public,
+    ) -> Result<Option<ed25519::Secret>, Error> {
+        let secret_bytes = self.secret_by_type(public.as_ref(), KeyType::Ed25519)?;
+        if let Some(buf) = secret_bytes {
+            Ok(Some(ed25519::secret_from_bytes(&buf)?))
+        } else {
+            Ok(None)
+        }
+    }
+
+    #[cfg(feature = "keystore-bls381")]
+    fn expose_bls381_secret(
+        &self,
+        public: &bls381::Public,
+    ) -> Result<Option<bls381::Secret>, Error> {
+        use w3f_bls::SerializableToBytes;
+
+        let secret_bytes = self.secret_by_type(&public.to_bytes(), KeyType::Bls381)?;
+        if let Some(buf) = secret_bytes {
+            Ok(Some(bls381::secret_from_bytes(&buf)))
+        } else {
+            Ok(None)
+        }
+    }
+
+    #[cfg(feature = "keystore-sr25519")]
+    fn iter_sr25519(&self) -> impl Iterator<Item = sr25519::Public> {
+        self.iter_keys(KeyType::Sr25519)
+            .flat_map(|b| sr25519::Public::from_bytes(&b))
+    }
+
+    #[cfg(feature = "keystore-ecdsa")]
+    fn iter_ecdsa(&self) -> impl Iterator<Item = ecdsa::Public> {
+        self.iter_keys(KeyType::Ecdsa)
+            .flat_map(|b| ecdsa::Public::from_sec1_bytes(&b))
+    }
+    #[cfg(feature = "keystore-ed25519")]
+    fn iter_ed25519(&self) -> impl Iterator<Item = ed25519::Public> {
+        self.iter_keys(KeyType::Ed25519)
+            .flat_map(|b| ed25519::Public::try_from(b.as_slice()))
+    }
+
+    #[cfg(feature = "keystore-bls381")]
+    fn iter_bls381(&self) -> impl Iterator<Item = bls381::Public> {
+        use w3f_bls::SerializableToBytes;
+
+        self.iter_keys(KeyType::Bls381)
+            .flat_map(|b| bls381::Public::from_bytes(&b))
+    }
+}
diff --git a/sdk/src/keystore/backend/mem.rs b/sdk/src/keystore/backend/mem.rs
new file mode 100644
index 000000000..3eb2770c4
--- /dev/null
+++ b/sdk/src/keystore/backend/mem.rs
@@ -0,0 +1,274 @@
+//! In-Memory Keystore Backend that supports different cryptographic key operations such as key generation, signing, and public key retrieval.
+
+#[cfg(not(feature = "std"))]
+use alloc::{collections::BTreeMap, sync::Arc};
+#[cfg(feature = "std")]
+use std::{collections::BTreeMap, sync::Arc};
+#[cfg(feature = "keystore-bls381")]
+use w3f_bls::SerializableToBytes;
+
+use parking_lot::RwLock;
+
+use crate::keystore::{bls381, ecdsa, ed25519, sr25519, Backend};
+
+/// The type alias for the In Memory `KeyMap`.
+type KeyMap<P, S> = Arc<RwLock<BTreeMap<P, S>>>;
+
+#[cfg(feature = "keystore-ed25519")]
+#[derive(Debug, Clone)]
+struct Ed25519PublicWrapper(ed25519::Public);
+
+#[cfg(feature = "keystore-ed25519")]
+impl PartialEq for Ed25519PublicWrapper {
+    fn eq(&self, other: &Self) -> bool {
+        self.0.as_ref() == other.0.as_ref()
+    }
+}
+
+#[cfg(feature = "keystore-ed25519")]
+impl Eq for Ed25519PublicWrapper {}
+
+#[cfg(feature = "keystore-ed25519")]
+impl Ord for Ed25519PublicWrapper {
+    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
+        self.0.as_ref().cmp(other.0.as_ref())
+    }
+}
+
+#[cfg(feature = "keystore-ed25519")]
+impl PartialOrd for Ed25519PublicWrapper {
+    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+#[cfg(feature = "keystore-bls381")]
+#[derive(Clone, PartialEq, Eq)]
+struct Bls381PublicWrapper(bls381::Public);
+
+#[cfg(feature = "keystore-bls381")]
+impl PartialOrd for Bls381PublicWrapper {
+    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+#[cfg(feature = "keystore-bls381")]
+impl Ord for Bls381PublicWrapper {
+    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
+        self.0.to_bytes().cmp(&other.0.to_bytes())
+    }
+}
+
+/// The In-Memory Keystore Backend. It stores everything in memory
+/// and does not persist anything, once dropped all the keys are lost, so use with caution.
+/// This is useful for testing and development purposes.
+/// It implements the [`crate::keystore::Backend`] trait.
+///
+/// Note: Cloning this backend is cheap, as it uses [`Arc`] and [`RwLock`] internally.
+#[derive(Default, Clone)]
+pub struct InMemoryKeystore {
+    sr25519: KeyMap<sr25519::Public, sr25519::Secret>,
+    ecdsa: KeyMap<ecdsa::Public, ecdsa::Secret>,
+    ed25519: KeyMap<Ed25519PublicWrapper, ed25519::Secret>,
+    bls381: KeyMap<Bls381PublicWrapper, bls381::Secret>,
+}
+
+impl core::fmt::Debug for InMemoryKeystore {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        f.debug_struct("InMemoryKeystore")
+            .field("sr25519", &"<hidden>")
+            .field("ecdsa", &"<hidden>")
+            .field("ed25519", &"<hidden>")
+            .field("bls381", &"<hidden>")
+            .finish()
+    }
+}
+
+impl InMemoryKeystore {
+    /// Create a new In-Memory Keystore Backend.
+    #[must_use]
+    pub fn new() -> Self {
+        Self::default()
+    }
+}
+
+impl Backend for InMemoryKeystore {
+    #[cfg(feature = "keystore-sr25519")]
+    fn sr25519_generate_new(
+        &self,
+        seed: Option<&[u8]>,
+    ) -> Result<sr25519::Public, crate::keystore::Error> {
+        let secret = sr25519::generate_with_optional_seed(seed)?;
+        let public = secret.to_public();
+        let old = self.sr25519.write().insert(public, secret);
+        assert!(old.is_none(), "generated key already exists");
+        Ok(public)
+    }
+
+    #[cfg(feature = "keystore-sr25519")]
+    fn sr25519_sign(
+        &self,
+        public: &sr25519::Public,
+        msg: &[u8],
+    ) -> Result<Option<sr25519::Signature>, crate::keystore::Error> {
+        let lock = self.sr25519.read();
+        let secret = lock.get(public);
+        if let Some(secret) = secret {
+            Ok(Some(sr25519::sign(secret, msg)?))
+        } else {
+            Ok(None)
+        }
+    }
+
+    #[cfg(feature = "keystore-ed25519")]
+    fn ed25519_generate_new(
+        &self,
+        seed: Option<&[u8]>,
+    ) -> Result<ed25519::Public, crate::keystore::Error> {
+        let secret = ed25519::generate_with_optional_seed(seed)?;
+        let public = ed25519::to_public(&secret);
+        let old = self
+            .ed25519
+            .write()
+            .insert(Ed25519PublicWrapper(public), secret);
+        assert!(old.is_none(), "generated key already exists");
+        Ok(public)
+    }
+
+    #[cfg(feature = "keystore-ed25519")]
+    fn ed25519_sign(
+        &self,
+        public: &ed25519::Public,
+        msg: &[u8],
+    ) -> Result<Option<ed25519::Signature>, crate::keystore::Error> {
+        let lock = self.ed25519.read();
+        let secret = lock.get(&Ed25519PublicWrapper(*public));
+        if let Some(secret) = secret {
+            Ok(Some(ed25519::sign(secret, msg)))
+        } else {
+            Ok(None)
+        }
+    }
+
+    #[cfg(feature = "keystore-ecdsa")]
+    fn ecdsa_generate_new(
+        &self,
+        seed: Option<&[u8]>,
+    ) -> Result<ecdsa::Public, crate::keystore::Error> {
+        let secret = ecdsa::generate_with_optional_seed(seed)?;
+        let public = secret.public_key();
+        let old = self.ecdsa.write().insert(public, secret);
+        assert!(old.is_none(), "generated key already exists");
+        Ok(public)
+    }
+
+    #[cfg(feature = "keystore-ecdsa")]
+    fn ecdsa_sign(
+        &self,
+        public: &ecdsa::Public,
+        msg: &[u8],
+    ) -> Result<Option<ecdsa::Signature>, crate::keystore::Error> {
+        let lock = self.ecdsa.read();
+        let secret = lock.get(public);
+        if let Some(secret) = secret {
+            Ok(Some(ecdsa::sign(secret, msg)))
+        } else {
+            Ok(None)
+        }
+    }
+
+    #[cfg(feature = "keystore-bls381")]
+    fn bls381_generate_new(
+        &self,
+        seed: Option<&[u8]>,
+    ) -> Result<bls381::Public, crate::keystore::Error> {
+        let secret = bls381::generate_with_optional_seed(seed);
+        let public = bls381::to_public(&secret);
+        let old = self
+            .bls381
+            .write()
+            .insert(Bls381PublicWrapper(public), secret);
+        assert!(old.is_none(), "generated key already exists");
+        Ok(public)
+    }
+
+    #[cfg(feature = "keystore-bls381")]
+    fn bls381_sign(
+        &self,
+        public: &bls381::Public,
+        msg: &[u8],
+    ) -> Result<Option<bls381::Signature>, crate::keystore::Error> {
+        let mut lock = self.bls381.write();
+        let secret = lock.get_mut(&Bls381PublicWrapper(*public));
+        if let Some(secret) = secret {
+            Ok(Some(bls381::sign(secret, msg)))
+        } else {
+            Ok(None)
+        }
+    }
+
+    #[cfg(feature = "keystore-sr25519")]
+    fn expose_sr25519_secret(
+        &self,
+        public: &sr25519::Public,
+    ) -> Result<Option<sr25519::Secret>, crate::keystore::Error> {
+        let lock = self.sr25519.read();
+        Ok(lock.get(public).cloned())
+    }
+
+    #[cfg(feature = "keystore-ecdsa")]
+    fn expose_ecdsa_secret(
+        &self,
+        public: &ecdsa::Public,
+    ) -> Result<Option<ecdsa::Secret>, crate::keystore::Error> {
+        let lock = self.ecdsa.read();
+        Ok(lock.get(public).cloned())
+    }
+
+    #[cfg(feature = "keystore-ed25519")]
+    fn expose_ed25519_secret(
+        &self,
+        public: &ed25519::Public,
+    ) -> Result<Option<ed25519::Secret>, crate::keystore::Error> {
+        let lock = self.ed25519.read();
+        Ok(lock.get(&Ed25519PublicWrapper(*public)).copied())
+    }
+
+    #[cfg(feature = "keystore-bls381")]
+    fn expose_bls381_secret(
+        &self,
+        public: &bls381::Public,
+    ) -> Result<Option<bls381::Secret>, crate::keystore::Error> {
+        let lock = self.bls381.read();
+        Ok(lock.get(&Bls381PublicWrapper(*public)).cloned())
+    }
+
+    #[cfg(feature = "keystore-sr25519")]
+    fn iter_sr25519(&self) -> impl Iterator<Item = sr25519::Public> {
+        let lock = self.sr25519.read();
+        let iter = lock.keys().copied().collect::<Vec<_>>();
+        iter.into_iter()
+    }
+
+    #[cfg(feature = "keystore-ecdsa")]
+    fn iter_ecdsa(&self) -> impl Iterator<Item = ecdsa::Public> {
+        let lock = self.ecdsa.read();
+        let iter = lock.keys().copied().collect::<Vec<_>>();
+        iter.into_iter()
+    }
+
+    #[cfg(feature = "keystore-ed25519")]
+    fn iter_ed25519(&self) -> impl Iterator<Item = ed25519::Public> {
+        let lock = self.ed25519.read();
+        let iter = lock.keys().map(|k| k.0).collect::<Vec<_>>();
+        iter.into_iter()
+    }
+
+    #[cfg(feature = "keystore-bls381")]
+    fn iter_bls381(&self) -> impl Iterator<Item = bls381::Public> {
+        let lock = self.bls381.read();
+        let iter = lock.keys().map(|k| k.0).collect::<Vec<_>>();
+        iter.into_iter()
+    }
+}
diff --git a/sdk/src/keystore/backend/mod.rs b/sdk/src/keystore/backend/mod.rs
new file mode 100644
index 000000000..922fe810f
--- /dev/null
+++ b/sdk/src/keystore/backend/mod.rs
@@ -0,0 +1,8 @@
+//! Keystore backend implementations.
+
+/// In-Memory Keystore Backend
+pub mod mem;
+
+/// Filesystem Keystore Backend
+#[cfg(all(feature = "keystore-fs", feature = "std"))]
+pub mod fs;
diff --git a/sdk/src/keystore/bls381.rs b/sdk/src/keystore/bls381.rs
new file mode 100644
index 000000000..381ffea87
--- /dev/null
+++ b/sdk/src/keystore/bls381.rs
@@ -0,0 +1,32 @@
+//! BLS12-381 keys and signatures.
+
+use w3f_bls::TinyBLS381;
+
+use crate::random;
+
+/// BLS12-381 public key.
+pub type Public = w3f_bls::PublicKey<TinyBLS381>;
+/// BLS12-381 secret key.
+pub type Secret = w3f_bls::SecretKey<TinyBLS381>;
+/// BLS12-381 signature.
+pub type Signature = w3f_bls::Signature<TinyBLS381>;
+
+pub fn generate_with_optional_seed(seed: Option<&[u8]>) -> Secret {
+    match seed {
+        Some(seed) => Secret::from_seed(seed),
+        None => Secret::generate(&mut random::getrandom_or_panic()),
+    }
+}
+
+pub fn sign(secret: &mut Secret, msg: &[u8]) -> Signature {
+    let message = w3f_bls::Message::from(msg);
+    secret.sign(&message, &mut random::getrandom_or_panic())
+}
+
+pub fn to_public(secret: &Secret) -> Public {
+    secret.clone().into_public()
+}
+
+pub fn secret_from_bytes(bytes: &[u8]) -> Secret {
+    Secret::from_seed(bytes)
+}
diff --git a/sdk/src/keystore/ecdsa.rs b/sdk/src/keystore/ecdsa.rs
new file mode 100644
index 000000000..7c89dd4af
--- /dev/null
+++ b/sdk/src/keystore/ecdsa.rs
@@ -0,0 +1,27 @@
+//! ECDSA Support using [`k256`] in pure Rust.
+
+use k256::ecdsa::signature::SignerMut;
+
+pub use k256::ecdsa::Signature;
+pub use k256::PublicKey as Public;
+pub use k256::SecretKey as Secret;
+
+use crate::random;
+
+pub fn generate_with_optional_seed(seed: Option<&[u8]>) -> k256::elliptic_curve::Result<Secret> {
+    if let Some(seed) = seed {
+        Secret::from_bytes(seed.into())
+    } else {
+        let mut rng = random::getrandom_or_panic();
+        Ok(Secret::random(&mut rng))
+    }
+}
+
+pub fn sign(secret: &Secret, msg: &[u8]) -> Signature {
+    let mut keypair = k256::ecdsa::SigningKey::from(secret);
+    keypair.sign(msg)
+}
+
+pub fn secret_from_bytes(bytes: &[u8]) -> k256::elliptic_curve::Result<Secret> {
+    Secret::from_bytes(bytes.into())
+}
diff --git a/sdk/src/keystore/ed25519.rs b/sdk/src/keystore/ed25519.rs
new file mode 100644
index 000000000..508df366a
--- /dev/null
+++ b/sdk/src/keystore/ed25519.rs
@@ -0,0 +1,26 @@
+//! Ed25519 Support using [`ed25519-zebra`] in pure Rust.
+
+pub use ed25519_zebra::Signature;
+pub use ed25519_zebra::SigningKey as Secret;
+pub use ed25519_zebra::VerificationKey as Public;
+
+use crate::random;
+
+pub fn generate_with_optional_seed(seed: Option<&[u8]>) -> Result<Secret, ed25519_zebra::Error> {
+    match seed {
+        Some(seed) => Secret::try_from(seed).map_err(|_| ed25519_zebra::Error::InvalidSliceLength),
+        None => Ok(Secret::new(&mut random::getrandom_or_panic())),
+    }
+}
+
+pub fn sign(secret: &Secret, msg: &[u8]) -> Signature {
+    secret.sign(msg)
+}
+
+pub fn to_public(secret: &Secret) -> Public {
+    Public::from(secret)
+}
+
+pub fn secret_from_bytes(bytes: &[u8]) -> Result<Secret, ed25519_zebra::Error> {
+    Secret::try_from(bytes).map_err(|_| ed25519_zebra::Error::InvalidSliceLength)
+}
diff --git a/sdk/src/keystore/error.rs b/sdk/src/keystore/error.rs
new file mode 100644
index 000000000..03d083078
--- /dev/null
+++ b/sdk/src/keystore/error.rs
@@ -0,0 +1,34 @@
+//! Keystore Errors
+
+/// Different errors that can occur in the [`crate::keystore`] module
+#[derive(Debug, thiserror::Error)]
+#[non_exhaustive]
+pub enum Error {
+    /// An I/O error occurred
+    #[error(transparent)]
+    Io(#[from] std::io::Error),
+    /// An error occurred during sr25519 module operation
+    #[cfg(feature = "keystore-sr25519")]
+    #[error("sr25519: {0}")]
+    Sr25519(schnorrkel::errors::SignatureError),
+    /// An error occurred during ecdsa module operation
+    #[cfg(feature = "keystore-ecdsa")]
+    #[error("ecdsa: {0}")]
+    Ecdsa(#[from] k256::elliptic_curve::Error),
+    /// An error occurred during ed25519 module operation
+    #[cfg(feature = "keystore-ed25519")]
+    #[error("ed25519: {0}")]
+    Ed25519(ed25519_zebra::Error),
+}
+
+impl From<ed25519_zebra::Error> for Error {
+    fn from(v: ed25519_zebra::Error) -> Self {
+        Self::Ed25519(v)
+    }
+}
+
+impl From<schnorrkel::errors::SignatureError> for Error {
+    fn from(v: schnorrkel::errors::SignatureError) -> Self {
+        Self::Sr25519(v)
+    }
+}
diff --git a/sdk/src/keystore/mod.rs b/sdk/src/keystore/mod.rs
new file mode 100644
index 000000000..7e878c5c7
--- /dev/null
+++ b/sdk/src/keystore/mod.rs
@@ -0,0 +1,183 @@
+//! Keystore Module for Gadget SDK
+//!
+//! Keystore is a module that provides an abstraction for a secure storage for private keys
+//! and other sensitive data. It does this by relaying the storage to the underlying backend
+//!
+//! Currently, we support different backends:
+//!
+//! - `memory`: A simple in-memory storage (enabled by default)
+//! - `file`: A storage that saves the keys to a file (`keystore-fs` feature)
+//! - `kms`: A storage that uses a AWS Key Management System (`keystore-kms` feature)*
+//! - `substrate`: Uses Substrate's Framework keystore (`keystore-substrate` feature)*
+//! and a lot more to be added in the future.
+//!
+//! Note: The `kms` and `substrate` backends are not yet implemented.
+//!
+//! In Addition, the module provides a `Keystore` trait that can be implemented by any backend
+//! and this trait offers the following methods:
+//! - `sr25519` methods, to generate, import and export sr25519 keys
+//! - `ed25519` methods, to generate, import and export ed25519 keys
+//! - `ecdsa` methods, to generate, import and export ecdsa keys
+//! - `bls381` methods, to generate, import and export bls381 keys
+//!
+//! with each module is feature gated to allow only the necessary dependencies to be included.
+
+/// Backend modules
+pub mod backend;
+/// BLS381 Support
+#[cfg(feature = "keystore-bls381")]
+mod bls381;
+/// ECDSA Support
+#[cfg(feature = "keystore-ecdsa")]
+mod ecdsa;
+/// Ed25519 Support
+#[cfg(feature = "keystore-ed25519")]
+mod ed25519;
+/// Keystore errors module
+mod error;
+/// Schnorrkel Support
+#[cfg(feature = "keystore-sr25519")]
+mod sr25519;
+
+pub use error::Error;
+
+/// The Keystore [`Backend`] trait defines the necessary functions that a keystore backend
+/// must implement to support various cryptographic key operations such as key generation,
+/// signing, and public key retrieval.
+pub trait Backend {
+    /// Generate a new sr25519 key pair with an optional seed.
+    ///
+    /// Returns an [`sr25519::Public`] key of the generated key pair or an `Err` if
+    /// something failed during key generation.
+    ///
+    /// # Errors
+    /// An `Err` will be returned if generating the key pair operation itself failed.
+    #[cfg(feature = "keystore-sr25519")]
+    fn sr25519_generate_new(&self, seed: Option<&[u8]>) -> Result<sr25519::Public, Error>;
+    /// Generate an sr25519 signature for a given message.
+    ///
+    /// Receives an [`sr25519::Public`] key to be able to map
+    /// them to a private key that exists in the keystore.
+    ///
+    /// Returns an [`sr25519::Signature`] or `None` in case the given
+    /// `public` doesn't exist in the keystore.
+    /// # Errors
+    /// An `Err` will be returned if generating the signature itself failed.
+    #[cfg(feature = "keystore-sr25519")]
+    fn sr25519_sign(
+        &self,
+        public: &sr25519::Public,
+        msg: &[u8],
+    ) -> Result<Option<sr25519::Signature>, Error>;
+    /// Generate a new ed25519 key pair with an optional seed.
+    ///
+    /// Returns an [`ed25519::Public`] key of the generated key pair or an `Err` if
+    /// something failed during key generation.
+    ///
+    /// # Errors
+    /// An `Err` will be returned if generating the key pair operation itself failed.
+    #[cfg(feature = "keystore-ed25519")]
+    fn ed25519_generate_new(&self, seed: Option<&[u8]>) -> Result<ed25519::Public, Error>;
+    /// Generate an ed25519 signature for a given message.
+    ///
+    /// Receives an [`ed25519::Public`] key to be able to map
+    /// it to a private key that exists in the keystore.
+    ///
+    /// Returns an [`ed25519::Signature`] or `None` in case the given
+    /// `public` doesn't exist in the keystore.
+    /// # Errors
+    /// An `Err` will be returned if generating the signature itself failed.
+    #[cfg(feature = "keystore-ed25519")]
+    fn ed25519_sign(
+        &self,
+        public: &ed25519::Public,
+        msg: &[u8],
+    ) -> Result<Option<ed25519::Signature>, Error>;
+    /// Generate a new ecdsa key pair with an optional seed.
+    ///
+    /// Returns an `ecdsa::Public` key of the generated key pair or an `Err` if
+    /// something failed during key generation.
+    /// # Errors
+    /// An `Err` will be returned if generating the key pair operation itself failed.
+    #[cfg(feature = "keystore-ecdsa")]
+    fn ecdsa_generate_new(&self, seed: Option<&[u8]>) -> Result<ecdsa::Public, Error>;
+    /// Generate an ecdsa signature for a given message.
+    ///
+    /// Receives an [`ecdsa::Public`] key to be able to map
+    /// it to a private key that exists in the keystore.
+    ///
+    /// Returns an [`ecdsa::Signature`] or `None` in case the given
+    /// `public` doesn't exist in the keystore.
+    /// # Errors
+    /// An `Err` will be returned if generating the signature itself failed.
+    #[cfg(feature = "keystore-ecdsa")]
+    fn ecdsa_sign(
+        &self,
+        public: &ecdsa::Public,
+        msg: &[u8],
+    ) -> Result<Option<ecdsa::Signature>, Error>;
+    /// Generate a new bls381 key pair with an optional seed.
+    ///
+    /// Returns an [`bls381::Public`] key of the generated key pair or an `Err` if
+    /// something failed during key generation.
+    /// # Errors
+    /// An `Err` will be returned if generating the key pair operation itself failed.
+    #[cfg(feature = "keystore-bls381")]
+    fn bls381_generate_new(&self, seed: Option<&[u8]>) -> Result<bls381::Public, Error>;
+    /// Generate an bls381 signature for a given message.
+    ///
+    /// Receives an [`bls381::Public`] key to be able to map
+    /// it to a private key that exists in the keystore.
+    ///
+    /// Returns an [`bls381::Signature`] or `None` in case the given
+    /// `public` doesn't exist in the keystore.
+    /// # Errors
+    /// An `Err` will be returned if generating the signature itself failed.
+    #[cfg(feature = "keystore-bls381")]
+    fn bls381_sign(
+        &self,
+        public: &bls381::Public,
+        msg: &[u8],
+    ) -> Result<Option<bls381::Signature>, Error>;
+    /// Returns the Keypair for the given [`sr25519::Public`] if it does exist, otherwise returns `None`.
+    /// # Errors
+    /// An `Err` will be returned if finding the key operation itself failed.
+    #[cfg(feature = "keystore-sr25519")]
+    fn expose_sr25519_secret(
+        &self,
+        public: &sr25519::Public,
+    ) -> Result<Option<sr25519::Secret>, Error>;
+    /// Returns the [`ecdsa::Secret`] for the given [`ecdsa::Public`] if it does exist, otherwise returns `None`.
+    /// # Errors
+    /// An `Err` will be returned if finding the key operation itself failed.
+    #[cfg(feature = "keystore-ecdsa")]
+    fn expose_ecdsa_secret(&self, public: &ecdsa::Public) -> Result<Option<ecdsa::Secret>, Error>;
+    /// Returns the [`ed25519::Secret`] for the given [`ed25519::Public`] if it does exist, otherwise returns `None`.
+    /// # Errors
+    /// An `Err` will be returned if finding the key operation itself failed.
+    #[cfg(feature = "keystore-ed25519")]
+    fn expose_ed25519_secret(
+        &self,
+        public: &ed25519::Public,
+    ) -> Result<Option<ed25519::Secret>, Error>;
+    /// Returns the [`bls381::Secret`] for the given [`bls381::Public`] if it does exist, otherwise returns `None`.
+    /// # Errors
+    /// An `Err` will be returned if finding the key operation itself failed.
+    #[cfg(feature = "keystore-bls381")]
+    fn expose_bls381_secret(
+        &self,
+        public: &bls381::Public,
+    ) -> Result<Option<bls381::Secret>, Error>;
+    /// Returns an iterator over all [`sr25519::Public`] keys that exist in the keystore.
+    #[cfg(feature = "keystore-sr25519")]
+    fn iter_sr25519(&self) -> impl Iterator<Item = sr25519::Public>;
+    /// Returns an iterator over all [`bls381::Public`] keys that exist in the keystore.
+    #[cfg(feature = "keystore-ecdsa")]
+    fn iter_ecdsa(&self) -> impl Iterator<Item = ecdsa::Public>;
+    /// Returns an iterator over all [`ed25519::Public`] keys that exist in the keystore.
+    #[cfg(feature = "keystore-ed25519")]
+    fn iter_ed25519(&self) -> impl Iterator<Item = ed25519::Public>;
+    /// Returns an iterator over all [`bls381::Public`] keys that exist in the keystore.
+    #[cfg(feature = "keystore-bls381")]
+    fn iter_bls381(&self) -> impl Iterator<Item = bls381::Public>;
+}
diff --git a/sdk/src/keystore/sr25519.rs b/sdk/src/keystore/sr25519.rs
new file mode 100644
index 000000000..f4b8ffe5e
--- /dev/null
+++ b/sdk/src/keystore/sr25519.rs
@@ -0,0 +1,26 @@
+//! Schnorrkel keypair implementation.
+
+pub use schnorrkel::PublicKey as Public;
+pub use schnorrkel::SecretKey as Secret;
+pub use schnorrkel::Signature;
+
+/// The context used for signing.
+const SIGNING_CTX: &[u8] = b"substrate";
+
+pub fn generate_with_optional_seed(
+    seed: Option<&[u8]>,
+) -> Result<Secret, schnorrkel::SignatureError> {
+    match seed {
+        Some(seed) => Secret::from_bytes(seed),
+        None => Ok(Secret::generate()),
+    }
+}
+
+pub fn sign(secret: &Secret, msg: &[u8]) -> Result<Signature, schnorrkel::SignatureError> {
+    let public = secret.to_public();
+    secret.sign_simple_doublecheck(SIGNING_CTX, msg, &public)
+}
+
+pub fn secret_from_bytes(bytes: &[u8]) -> Result<Secret, schnorrkel::SignatureError> {
+    Secret::from_bytes(bytes)
+}
diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs
new file mode 100644
index 000000000..6b8915bd5
--- /dev/null
+++ b/sdk/src/lib.rs
@@ -0,0 +1,33 @@
+#![deny(
+    missing_debug_implementations,
+    missing_copy_implementations,
+    trivial_casts,
+    trivial_numeric_casts,
+    unsafe_code,
+    unstable_features,
+    unused_import_braces,
+    unused_qualifications,
+    missing_docs,
+    rustdoc::broken_intra_doc_links,
+    unused_results,
+    clippy::all,
+    clippy::pedantic,
+    clippy::exhaustive_enums
+)]
+//! Gadget SDK
+
+#![cfg_attr(not(feature = "std"), no_std)]
+
+#[cfg(not(feature = "std"))]
+extern crate alloc;
+
+/// Keystore Module
+pub mod keystore;
+
+/// Metrics Module
+// pub mod metrics;
+
+/// Logging Module
+pub mod logging;
+/// Randomness generation module
+pub mod random;
diff --git a/sdk/src/logging/mod.rs b/sdk/src/logging/mod.rs
new file mode 100644
index 000000000..244a1928e
--- /dev/null
+++ b/sdk/src/logging/mod.rs
@@ -0,0 +1,17 @@
+/// Module for structured logging using the slog crate.
+pub mod slog;
+
+/// Trait defining the methods for logging.
+pub trait Logger {
+    /// Log a debug message.
+    fn debug(&self, target: &str, message: &str, tags: &[&str]);
+
+    /// Log an info message.
+    fn info(&self, target: &str, message: &str, tags: &[&str]);
+
+    /// Log a warning message.
+    fn warn(&self, target: &str, message: &str, tags: &[&str]);
+
+    /// Log an error message.
+    fn error(&self, target: &str, message: &str, tags: &[&str]);
+}
diff --git a/sdk/src/logging/slog.rs b/sdk/src/logging/slog.rs
new file mode 100644
index 000000000..17364021c
--- /dev/null
+++ b/sdk/src/logging/slog.rs
@@ -0,0 +1,54 @@
+use slog::{debug, error, info, warn};
+
+use super::Logger;
+
+struct SlogLogger {
+    logger: slog::Logger,
+}
+
+macro_rules! custom_log {
+    ($level:ident, $logger:expr, $target:expr, $message:expr, $tags:expr) => {{
+        $level!($logger, "{} {}", $target, $message);
+        for tag in $tags {
+            $level!($logger, "{} {}", tag, $message);
+        }
+    }};
+}
+
+impl Logger for SlogLogger {
+    fn debug(&self, target: &str, message: &str, tags: &[&str]) {
+        custom_log!(debug, self.logger, target, message, tags);
+    }
+
+    fn info(&self, target: &str, message: &str, tags: &[&str]) {
+        custom_log!(info, self.logger, target, message, tags);
+    }
+
+    fn warn(&self, target: &str, message: &str, tags: &[&str]) {
+        custom_log!(warn, self.logger, target, message, tags);
+    }
+
+    fn error(&self, target: &str, message: &str, tags: &[&str]) {
+        custom_log!(error, self.logger, target, message, tags);
+    }
+}
+#[cfg(test)]
+mod test {
+    use slog::Drain;
+
+    use crate::logging::Logger;
+
+    use super::SlogLogger;
+
+    #[test]
+    fn test_slog_logger() {
+        let plain = slog_term::PlainSyncDecorator::new(std::io::stdout());
+        let inner_logger =
+            slog::Logger::root(slog_term::FullFormat::new(plain).build().fuse(), slog::o!());
+
+        let logger = SlogLogger {
+            logger: inner_logger,
+        };
+        logger.debug("my_target", "Hello, world!", &["tag1", "tag2"]);
+    }
+}
diff --git a/common/src/gadget/metrics/mod.rs b/sdk/src/metrics/mod.rs
similarity index 99%
rename from common/src/gadget/metrics/mod.rs
rename to sdk/src/metrics/mod.rs
index 2eb5b10d4..8c1fdaccc 100644
--- a/common/src/gadget/metrics/mod.rs
+++ b/sdk/src/metrics/mod.rs
@@ -5,5 +5,6 @@ mod wasm;
 
 #[cfg(not(target_family = "wasm"))]
 pub use standard::Metrics;
+
 #[cfg(target_family = "wasm")]
 pub use wasm::Metrics;
diff --git a/sdk/src/metrics/standard/metrics.rs b/sdk/src/metrics/standard/metrics.rs
new file mode 100644
index 000000000..1fc2fb358
--- /dev/null
+++ b/sdk/src/metrics/standard/metrics.rs
@@ -0,0 +1,169 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use hyper::{
+    http::StatusCode,
+    server::Server,
+    service::{make_service_fn, service_fn},
+    Body, Request, Response,
+};
+pub use prometheus::{
+    self,
+    core::{AtomicU64 as U64, GenericCounter as Counter},
+    Error as PrometheusError, Registry,
+};
+use prometheus::{core::Collector, Encoder, TextEncoder};
+use std::net::SocketAddr;
+
+#[allow(unused)]
+pub use super::sourced::{MetricSource, SourcedCounter, SourcedGauge, SourcedMetric};
+
+pub fn register<T: Clone + Collector + 'static>(
+    metric: T,
+    registry: &Registry,
+) -> Result<T, PrometheusError> {
+    registry.register(Box::new(metric.clone()))?;
+    Ok(metric)
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+    /// Hyper internal error.
+    #[error(transparent)]
+    Hyper(#[from] hyper::Error),
+
+    /// Http request error.
+    #[error(transparent)]
+    Http(#[from] hyper::http::Error),
+
+    /// i/o error.
+    #[error(transparent)]
+    Io(#[from] std::io::Error),
+
+    #[error("Prometheus port {0} already in use.")]
+    PortInUse(SocketAddr),
+}
+
+async fn request_metrics(req: Request<Body>, registry: Registry) -> Result<Response<Body>, Error> {
+    if req.uri().path() == "/metrics" {
+        let metric_families = registry.gather();
+        let mut buffer = vec![];
+        let encoder = TextEncoder::new();
+        encoder.encode(&metric_families, &mut buffer).unwrap();
+
+        Response::builder()
+            .status(StatusCode::OK)
+            .header("Content-Type", encoder.format_type())
+            .body(Body::from(buffer))
+            .map_err(Error::Http)
+    } else {
+        Response::builder()
+            .status(StatusCode::NOT_FOUND)
+            .body(Body::from("Not found."))
+            .map_err(Error::Http)
+    }
+}
+
+/// Initializes the metrics context, and starts an HTTP server
+/// to serve metrics.
+pub async fn init_prometheus(prometheus_addr: SocketAddr, registry: Registry) -> Result<(), Error> {
+    let listener = tokio::net::TcpListener::bind(&prometheus_addr)
+        .await
+        .map_err(|_| Error::PortInUse(prometheus_addr))?;
+
+    init_prometheus_with_listener(listener, registry).await
+}
+
+/// Init prometheus using the given listener.
+async fn init_prometheus_with_listener(
+    listener: tokio::net::TcpListener,
+    registry: Registry,
+) -> Result<(), Error> {
+    let listener = hyper::server::conn::AddrIncoming::from_listener(listener)?;
+    log::info!(
+        "〽️ Prometheus exporter started at {}",
+        listener.local_addr()
+    );
+
+    let service = make_service_fn(move |_| {
+        let registry = registry.clone();
+
+        async move {
+            Ok::<_, hyper::Error>(service_fn(move |req: Request<Body>| {
+                request_metrics(req, registry.clone())
+            }))
+        }
+    });
+
+    let (signal, on_exit) = tokio::sync::oneshot::channel::<()>();
+    let server = Server::builder(listener)
+        .serve(service)
+        .with_graceful_shutdown(async {
+            let _ = on_exit.await;
+        });
+
+    let result = server.await.map_err(Into::into);
+
+    // Gracefully shutdown server, otherwise the server does not stop if it has open connections
+    let _ = signal.send(());
+
+    result
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use hyper::{Client, Uri};
+
+    #[test]
+    fn prometheus_works() {
+        const METRIC_NAME: &str = "test_test_metric_name_test_test";
+
+        let runtime = tokio::runtime::Runtime::new().expect("Creates the runtime");
+
+        let listener = runtime
+            .block_on(tokio::net::TcpListener::bind("127.0.0.1:0"))
+            .expect("Creates listener");
+
+        let local_addr = listener.local_addr().expect("Returns the local addr");
+
+        let registry = Registry::default();
+        let _ = register(
+            prometheus::Counter::new(METRIC_NAME, "yeah").expect("Creates test counter"),
+            &registry,
+        )
+        .expect("Registers the test metric");
+
+        let _ = runtime.spawn(init_prometheus_with_listener(listener, registry));
+
+        runtime.block_on(async {
+            let client = Client::new();
+
+            let res = client
+                .get(Uri::try_from(&format!("http://{local_addr}/metrics")).expect("Parses URI"))
+                .await
+                .expect("Requests metrics");
+
+            let buf = hyper::body::to_bytes(res)
+                .await
+                .expect("Converts body to bytes");
+
+            let body = String::from_utf8(buf.to_vec()).expect("Converts body to String");
+            assert!(body.contains(&format!("{METRIC_NAME} 0")));
+        });
+    }
+}
diff --git a/common/src/gadget/metrics/standard.rs b/sdk/src/metrics/standard/mod.rs
similarity index 66%
rename from common/src/gadget/metrics/standard.rs
rename to sdk/src/metrics/standard/mod.rs
index 560ab7212..368fb7c90 100644
--- a/common/src/gadget/metrics/standard.rs
+++ b/sdk/src/metrics/standard/mod.rs
@@ -1,6 +1,17 @@
 #![allow(dead_code)]
-use substrate_prometheus_endpoint::{register, Counter, PrometheusError, Registry, U64};
 
+pub mod metrics;
+pub mod sourced;
+
+pub use metrics::*;
+
+/// Standard metrics for the gadget gossip service.
+///
+/// This module provides a set of standard metrics that are used to monitor
+/// the behavior of the gadget gossip service. It includes counters for both
+/// registered and expired messages, which can be used to track message
+/// throughput and lifecycle within the service.
+#[derive(Debug)]
 pub struct Metrics {
     registered_messages: Counter<U64>,
     expired_messages: Counter<U64>,
diff --git a/sdk/src/metrics/standard/sourced.rs b/sdk/src/metrics/standard/sourced.rs
new file mode 100644
index 000000000..d10ac2926
--- /dev/null
+++ b/sdk/src/metrics/standard/sourced.rs
@@ -0,0 +1,167 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Metrics that are collected from existing sources.
+
+use prometheus::{
+    core::{Collector, Desc, Describer, Number, Opts},
+    proto,
+};
+use std::{cmp::Ordering, marker::PhantomData};
+
+/// A counter whose values are obtained from an existing source.
+///
+/// > **Note*: The counter values provided by the source `S`
+/// > must be monotonically increasing. Otherwise use a
+/// > [`SourcedGauge`] instead.
+pub type SourcedCounter<S> = SourcedMetric<Counter, S>;
+
+/// A gauge whose values are obtained from an existing source.
+pub type SourcedGauge<S> = SourcedMetric<Gauge, S>;
+
+/// The type of a sourced counter.
+#[derive(Copy, Clone)]
+pub enum Counter {}
+
+/// The type of a sourced gauge.
+#[derive(Copy, Clone)]
+pub enum Gauge {}
+
+/// A metric whose values are obtained from an existing source,
+/// instead of being independently recorded.
+#[derive(Debug, Clone)]
+pub struct SourcedMetric<T, S> {
+    source: S,
+    desc: Desc,
+    _type: PhantomData<T>,
+}
+
+/// A source of values for a [`SourcedMetric`].
+pub trait MetricSource: Sync + Send + Clone {
+    /// The type of the collected values.
+    type N: Number;
+    /// Collects the current values of the metrics from the source.
+    fn collect(&self, set: impl FnMut(&[&str], Self::N));
+}
+
+impl<T: SourcedType, S: MetricSource> SourcedMetric<T, S> {
+    /// Creates a new metric that obtains its values from the given source.
+    pub fn new(opts: &Opts, source: S) -> prometheus::Result<Self> {
+        let desc = opts.describe()?;
+        Ok(Self {
+            source,
+            desc,
+            _type: PhantomData,
+        })
+    }
+}
+
+impl<T: SourcedType, S: MetricSource> Collector for SourcedMetric<T, S> {
+    fn desc(&self) -> Vec<&Desc> {
+        vec![&self.desc]
+    }
+
+    fn collect(&self) -> Vec<proto::MetricFamily> {
+        let mut counters = Vec::new();
+
+        self.source.collect(|label_values, value| {
+            let mut m = proto::Metric::default();
+
+            match T::proto() {
+                proto::MetricType::COUNTER => {
+                    let mut c = proto::Counter::default();
+                    c.set_value(value.into_f64());
+                    m.set_counter(c);
+                }
+                proto::MetricType::GAUGE => {
+                    let mut g = proto::Gauge::default();
+                    g.set_value(value.into_f64());
+                    m.set_gauge(g);
+                }
+                t => {
+                    log::error!("Unsupported sourced metric type: {:?}", t);
+                }
+            }
+
+            debug_assert_eq!(self.desc.variable_labels.len(), label_values.len());
+            match self.desc.variable_labels.len().cmp(&label_values.len()) {
+                Ordering::Greater => {
+                    log::warn!(
+                        "Missing label values for sourced metric {}",
+                        self.desc.fq_name
+                    );
+                }
+                Ordering::Less => {
+                    log::warn!(
+                        "Too many label values for sourced metric {}",
+                        self.desc.fq_name
+                    );
+                }
+                Ordering::Equal => {}
+            }
+
+            m.set_label(
+                self.desc
+                    .variable_labels
+                    .iter()
+                    .zip(label_values)
+                    .map(|(l_name, l_value)| {
+                        let mut l = proto::LabelPair::default();
+                        l.set_name(l_name.to_string());
+                        l.set_value((*l_value).to_string());
+                        l
+                    })
+                    .chain(self.desc.const_label_pairs.iter().cloned())
+                    .collect::<Vec<_>>(),
+            );
+
+            counters.push(m);
+        });
+
+        let mut m = proto::MetricFamily::default();
+        m.set_name(self.desc.fq_name.clone());
+        m.set_help(self.desc.help.clone());
+        m.set_field_type(T::proto());
+        m.set_metric(counters);
+
+        vec![m]
+    }
+}
+
+/// Types of metrics that can obtain their values from an existing source.
+pub trait SourcedType: private::Sealed + Sync + Send {
+    #[doc(hidden)]
+    fn proto() -> proto::MetricType;
+}
+
+impl SourcedType for Counter {
+    fn proto() -> proto::MetricType {
+        proto::MetricType::COUNTER
+    }
+}
+
+impl SourcedType for Gauge {
+    fn proto() -> proto::MetricType {
+        proto::MetricType::GAUGE
+    }
+}
+
+mod private {
+    pub trait Sealed {}
+    impl Sealed for super::Counter {}
+    impl Sealed for super::Gauge {}
+}
diff --git a/common/src/gadget/metrics/wasm.rs b/sdk/src/metrics/wasm.rs
similarity index 100%
rename from common/src/gadget/metrics/wasm.rs
rename to sdk/src/metrics/wasm.rs
diff --git a/sdk/src/random.rs b/sdk/src/random.rs
new file mode 100644
index 000000000..35951fb8d
--- /dev/null
+++ b/sdk/src/random.rs
@@ -0,0 +1,45 @@
+//! Returns `OsRng` with `getrandom`, or a `CryptoRng` which panics without `getrandom`.
+
+/// Re-export [`rand_core`] types to simplify dependences
+pub use rand_core::{self, CryptoRng, CryptoRngCore, RngCore, SeedableRng};
+
+/// Returns `OsRng` with `getrandom`, or a `CryptoRng` which panics without `getrandom`.
+#[cfg(all(feature = "getrandom", not(feature = "std")))]
+#[must_use]
+pub fn getrandom_or_panic() -> impl RngCore + CryptoRng {
+    rand_core::OsRng
+}
+
+/// Returns `OsRng` with `getrandom`, or a `CryptoRng` which panics without `getrandom`.
+#[cfg(all(feature = "getrandom", feature = "std"))]
+#[must_use]
+pub fn getrandom_or_panic() -> impl RngCore + CryptoRng {
+    rand::thread_rng()
+}
+
+/// Returns `OsRng` with `getrandom`, or a `CryptoRng` which panics without `getrandom`.
+#[cfg(not(feature = "getrandom"))]
+pub fn getrandom_or_panic() -> impl RngCore + CryptoRng {
+    const PRM: &'static str = "Attempted to use functionality that requires system randomness!!";
+
+    // TODO: Should we panic when invoked or when used?
+
+    struct PanicRng;
+    impl rand_core::RngCore for PanicRng {
+        fn next_u32(&mut self) -> u32 {
+            panic!("{}", PRM)
+        }
+        fn next_u64(&mut self) -> u64 {
+            panic!("{}", PRM)
+        }
+        fn fill_bytes(&mut self, _dest: &mut [u8]) {
+            panic!("{}", PRM)
+        }
+        fn try_fill_bytes(&mut self, _dest: &mut [u8]) -> Result<(), rand_core::Error> {
+            panic!("{}", PRM)
+        }
+    }
+    impl rand_core::CryptoRng for PanicRng {}
+
+    PanicRng
+}


Test patch: