Skip to content
Snippets Groups Projects
keys.rs 30.7 KiB
Newer Older
Nicolas80's avatar
Nicolas80 committed
use crate::commands::vault;
use crate::*;
Nicolas80's avatar
Nicolas80 committed
use sp_core::sr25519;
pub const SUBSTRATE_MNEMONIC: &str =
	"bottom drive obey lake curtain smoke basket hold race lonely fit walk";
/// secret format
Hugo Trentesaux's avatar
Hugo Trentesaux committed
#[derive(Clone, Copy, Debug, Eq, PartialEq, Default)]
pub enum SecretFormat {
	/// Raw 32B seed
	Seed,
	/// Substrate secret key or BIP39 mnemonic (optionally followed by derivation path)
Hugo Trentesaux's avatar
Hugo Trentesaux committed
	#[default]
	Substrate,
	/// Predefined (Alice, Bob, ...)
	Predefined,
	/// G1v1 id+secret using (scrypt + ed25519)
	G1v1,
impl FromStr for SecretFormat {
	type Err = std::io::Error;
	fn from_str(s: &str) -> std::io::Result<Self> {
		match s {
			"seed" => Ok(SecretFormat::Seed),
			"substrate" => Ok(SecretFormat::Substrate),
			"predefined" => Ok(SecretFormat::Predefined),
			"g1v1" => Ok(SecretFormat::G1v1),
			//Still support "cesium" input as well for backward compatibility
			"cesium" => Ok(SecretFormat::G1v1),
			_ => Err(std::io::Error::from(std::io::ErrorKind::InvalidInput)),
		}
	}
}
impl From<SecretFormat> for &'static str {
	fn from(val: SecretFormat) -> &'static str {
		match val {
			SecretFormat::Seed => "seed",
			SecretFormat::Substrate => "substrate",
			SecretFormat::Predefined => "predefined",
}
impl From<SecretFormat> for OsStr {
	fn from(val: SecretFormat) -> OsStr {
		OsStr::from(Into::<&str>::into(val))
	}
/// The crypto scheme to use - partial copy from sc_cli::arg_enums::CryptoScheme
///
/// Preferred making a copy since adding the dependency to sc-cli brings more than 300 dependencies
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum CryptoScheme {
	/// Use ed25519 - used for SecretFormat::Cesium
	Ed25519,
	/// Use sr25519.
	Sr25519,
}

impl FromStr for CryptoScheme {
	type Err = std::io::Error;

	fn from_str(s: &str) -> std::io::Result<Self> {
		match s {
			"ed25519" => Ok(CryptoScheme::Ed25519),
			"sr25519" => Ok(CryptoScheme::Sr25519),
			_ => Err(std::io::Error::from(std::io::ErrorKind::InvalidInput)),
		}
	}
}
impl From<CryptoScheme> for &'static str {
	fn from(val: CryptoScheme) -> &'static str {
		match val {
			CryptoScheme::Ed25519 => "ed25519",
			CryptoScheme::Sr25519 => "sr25519",
		}
	}
}
impl From<CryptoScheme> for OsStr {
	fn from(val: CryptoScheme) -> OsStr {
		OsStr::from(Into::<&str>::into(val))
	}
}

/// wrapper type for keys + signature
//FIXME check if it's ok to keep large enum variant
// Sr25519 second-largest variant contains at least 256 bytes
// Ed25519 largest variant contains at least 480 bytes
// Replace by CryptoType from sp-core ?
// sp_core::crypto::CryptoType (trait)
pub enum KeyPair {
Nicolas80's avatar
Nicolas80 committed
	Sr25519(sr25519::Pair),
	Ed25519(ed25519::Pair),
}
impl KeyPair {
	pub fn address(&self) -> AccountId {
		match self {
			KeyPair::Sr25519(keypair) => keypair.public().into(),
			KeyPair::Ed25519(keypair) => keypair.public().into(),
	}
}
// can not derive clone because nacl does not implement it
impl Clone for KeyPair {
	fn clone(&self) -> Self {
		match self {
			KeyPair::Sr25519(keypair) => KeyPair::Sr25519(keypair.clone()),
			KeyPair::Ed25519(keypair) => KeyPair::Ed25519(*keypair),
Nicolas80's avatar
Nicolas80 committed
impl From<sr25519::Pair> for KeyPair {
	fn from(pair: sr25519::Pair) -> KeyPair {
		KeyPair::Sr25519(pair)
	}
}
Nicolas80's avatar
Nicolas80 committed
impl From<ed25519::Pair> for KeyPair {
	fn from(pair: ed25519::Pair) -> KeyPair {
pub enum Signature {
	Sr25519(sr25519::Signature),

/// get keypair in any possible way
/// at this point, if secret is predefined, it's not replaced yet
pub fn get_keypair(
	secret_format: SecretFormat,
	secret: Option<&str>,
) -> Result<KeyPair, GcliError> {
	match (secret_format, secret) {
		(SecretFormat::Predefined, Some(deriv)) => pair_from_predefined(deriv).map(|v| v.into()),
		(secret_format, None) => Ok(prompt_secret(secret_format)),
		(_, Some(secret)) => Ok(pair_from_secret(secret_format, secret)?.into()),
	}
}
/// get keypair from given secret
/// if secret is predefined, secret should contain the predefined value
pub fn pair_from_secret(
	secret_format: SecretFormat,
	secret: &str,
Nicolas80's avatar
Nicolas80 committed
) -> Result<sr25519::Pair, GcliError> {
	match secret_format {
		SecretFormat::Substrate => pair_from_sr25519_str(secret),
		SecretFormat::Predefined => pair_from_sr25519_str(secret), /* if predefined, secret arg is replaced in config */
		SecretFormat::Seed => pair_from_sr25519_seed(secret),
		SecretFormat::G1v1 => Err(GcliError::Logic(
			"G1v1 format incompatible with single secret".to_string(),
		)),
	}
}

/// get keypair from given string secret
Nicolas80's avatar
Nicolas80 committed
pub fn pair_from_sr25519_str(secret: &str) -> Result<sr25519::Pair, GcliError> {
	let _validation_only = vault::parse_prefix_and_derivation_path_from_suri(secret.to_string())?;
	sr25519::Pair::from_string(secret, None)
		.map_err(|_| GcliError::Input("Invalid secret".to_string()))
}

/// get keypair from given seed
Nicolas80's avatar
Nicolas80 committed
// note: sr25519::Pair::from_string does exactly that when seed is 0x prefixed
// (see from_string_with_seed method in crypto core)
Nicolas80's avatar
Nicolas80 committed
pub fn pair_from_sr25519_seed(secret: &str) -> Result<sr25519::Pair, GcliError> {
	let mut seed = [0; 32];
	hex::decode_to_slice(secret, &mut seed)
		.map_err(|_| GcliError::Input("Invalid secret".to_string()))?;
Nicolas80's avatar
Nicolas80 committed
	let pair = sr25519::Pair::from_seed(&seed);
/// get keypair from given ed25519 string secret (used for cesium)
Nicolas80's avatar
Nicolas80 committed
pub fn pair_from_ed25519_str(secret: &str) -> Result<ed25519::Pair, GcliError> {
	ed25519::Pair::from_string(secret, None)
		.map_err(|_| GcliError::Input("Invalid secret".to_string()))
}

/// get keypair from given ed25519 seed (used for cesium)
#[allow(unused)]
Nicolas80's avatar
Nicolas80 committed
pub fn pair_from_ed25519_seed(secret: &str) -> Result<ed25519::Pair, GcliError> {
	let mut seed = [0; 32];
	hex::decode_to_slice(secret, &mut seed)
		.map_err(|_| GcliError::Input("Invalid secret".to_string()))?;
Nicolas80's avatar
Nicolas80 committed
	let pair = ed25519::Pair::from_seed(&seed);
/// get mnemonic from predefined derivation path
pub fn predefined_mnemonic(deriv: &str) -> String {
	format!("{SUBSTRATE_MNEMONIC}//{deriv}")
}

/// get keypair from predefined secret
Nicolas80's avatar
Nicolas80 committed
pub fn pair_from_predefined(deriv: &str) -> Result<sr25519::Pair, GcliError> {
	pair_from_sr25519_str(&predefined_mnemonic(deriv))
Nicolas80's avatar
Nicolas80 committed
/// get seed from G1v1 id/pwd (old "cesium")
pub fn seed_from_cesium(id: &str, pwd: &str) -> [u8; 32] {
	let params = scrypt::Params::new(12u8, 16u32, 1u32, 32).unwrap();
	scrypt::scrypt(pwd.as_bytes(), id.as_bytes(), &params, &mut seed).unwrap();
}

/// ask user to input a secret
Nicolas80's avatar
Nicolas80 committed
pub fn prompt_secret_substrate() -> sr25519::Pair {
	// Only interested in the keypair which is the second element of the tuple
	prompt_secret_substrate_and_compute_keypair().1
}

Nicolas80's avatar
Nicolas80 committed
pub fn prompt_secret_substrate_and_compute_keypair() -> (String, sr25519::Pair) {
	loop {
		println!("Substrate URI can be a mnemonic or a mini-secret ('0x' prefixed seed) together with optional derivation path");
		let substrate_suri = inputs::prompt_password_query("Substrate URI: ").unwrap();
		match pair_from_sr25519_str(&substrate_suri) {
			Ok(pair) => return (substrate_suri, pair),
			Err(_) => println!("Invalid secret"),
		}
	}
/// ask user pass (Cesium format)
Nicolas80's avatar
Nicolas80 committed
pub fn prompt_secret_cesium() -> ed25519::Pair {
	// Only interested in the keypair which is the second element of the tuple
	prompt_secret_cesium_and_compute_keypair().1
}

Nicolas80's avatar
Nicolas80 committed
pub fn prompt_secret_cesium_and_compute_keypair() -> (String, ed25519::Pair) {
Nicolas80's avatar
Nicolas80 committed
	let id = inputs::prompt_password_query("G1v1 id: ").unwrap();
	let pwd = inputs::prompt_password_query("G1v1 password: ").unwrap();

	let seed = seed_from_cesium(&id, &pwd);
	let secret_suri = format!("0x{}", hex::encode(seed));

	match pair_from_ed25519_str(&secret_suri) {
		Ok(pair) => (secret_suri, pair),
Nicolas80's avatar
Nicolas80 committed
		Err(_) => panic!("Could not compute KeyPair from G1v1 id/pwd"),
}

/// ask user to input a seed
Nicolas80's avatar
Nicolas80 committed
pub fn prompt_seed() -> sr25519::Pair {
	// Only interested in the keypair which is the second element of the tuple
	prompt_seed_and_compute_keypair().1
}

Nicolas80's avatar
Nicolas80 committed
pub fn prompt_seed_and_compute_keypair() -> (String, sr25519::Pair) {
	loop {
		let seed_str = inputs::prompt_seed().unwrap();
		let secret_suri = format!("0x{}", seed_str);

		match pair_from_sr25519_str(&secret_suri) {
			Ok(pair) => return (secret_suri, pair),
			Err(_) => println!("Invalid seed"),
		}
	}
}

/// ask user pass (Cesium format)
Nicolas80's avatar
Nicolas80 committed
pub fn prompt_predefined() -> sr25519::Pair {
	// Only interested in the keypair which is the second element of the tuple
	prompt_predefined_and_compute_keypair().1
}

Nicolas80's avatar
Nicolas80 committed
pub fn prompt_predefined_and_compute_keypair() -> (String, sr25519::Pair) {
	let deriv = inputs::prompt_password_query("Enter derivation path: ").unwrap();
		pair_from_predefined(&deriv).expect("invalid secret"),
	)
}

/// ask user secret in relevant format
pub fn prompt_secret(secret_format: SecretFormat) -> KeyPair {
	match secret_format {
		SecretFormat::Substrate => prompt_secret_substrate().into(),
		SecretFormat::G1v1 => prompt_secret_cesium().into(),
		SecretFormat::Seed => prompt_seed().into(),
		SecretFormat::Predefined => prompt_predefined().into(),
	}
}

/// get the secret from user, trying first keystore then input
pub async fn fetch_or_get_keypair(
	data: &Data,
	address: Option<AccountId>,
) -> Result<KeyPair, GcliError> {
	if let Some(address) = address {
		// if address corresponds to predefined, (for example saved to config)
		// keypair is already known (useful for dev mode)
		if let Some(d) = catch_known(&address.to_string()) {
			return Ok(pair_from_predefined(d).unwrap().into());
		};

		// look for corresponding KeyPair in keystore
		if let Some(key_pair) = commands::vault::try_fetch_key_pair(data, address).await? {
			return Ok(key_pair);
		};
	}
	// at the moment, there is no way to confg gcli to use an other kind of secret
	// without telling explicitly each time
	Ok(prompt_secret(SecretFormat::Substrate))
}

// catch known addresses
fn catch_known(address: &str) -> Option<&str> {
	match address {
		"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" => Some("Alice"),
		"5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty" => Some("Bob"),
		"5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y" => Some("Charlie"),
		"5DAAnrj7VHTznn2AWBemMuyBwZWs6FNFjdyVXUeYum3PTXFy" => Some("Dave"),
		"5HGjWAeFDfFCWPsjFQdVV2Msvz2XtMktvgocEZcCj68kUMaw" => Some("Eve"),
		_ => None,
	}
}

	mod subkey_like_tests {
		use super::keys::SUBSTRATE_MNEMONIC;

		use sp_core::crypto::Ss58Codec;
		use sp_core::crypto::{Ss58AddressFormat, Ss58AddressFormatRegistry};
		use sp_core::ByteArray;
		use sp_runtime::traits::IdentifyAccount;
		use sp_runtime::MultiSigner;

		#[test]
		fn test_print_from_suri() {
			// sc_cli::CryptoSchemeFlag::augment_args()

			let suri_str = SUBSTRATE_MNEMONIC.to_string();
			print_from_suri::<sp_core::sr25519::Pair>(&suri_str, None, None);
			print_from_suri::<sp_core::sr25519::Pair>(
				&suri_str,
				None,
				Some(Ss58AddressFormatRegistry::G1Account.into()),
			);

			// bottom drive obey lake curtain smoke basket hold race lonely fit walk//Alice
			let suri_str = SUBSTRATE_MNEMONIC.to_string() + "//Alice";
			print_from_suri::<sp_core::sr25519::Pair>(&suri_str, None, None);
			print_from_suri::<sp_core::sr25519::Pair>(
				&suri_str,
				None,
				Some(Ss58AddressFormatRegistry::G1Account.into()),
			);
		}

		/// print account information from suri - simplification of code from
		/// sc_cli::commands::utils::print_from_uri
		pub fn print_from_suri<Pair>(
			uri: &str,
			password: Option<&str>,
			network_override: Option<Ss58AddressFormat>,
		) where
			Pair: sp_core::Pair,
			Pair::Public: Into<MultiSigner>,
		{
			let network_id = String::from(unwrap_or_default_ss58_version(network_override));

			if let Ok((pair, seed)) = Pair::from_string_with_seed(uri, password) {
				let public_key = pair.public();
				let network_override = unwrap_or_default_ss58_version(network_override);

				println!(
					"Secret Key URI `{}` is account:\n  \
					Network ID:        {}\n  \
					Secret seed:       {}\n  \
					Public key (hex):  {}\n  \
					Account ID:        {}\n  \
					Public key (SS58): {}\n  \
					SS58 Address:      {}",
					uri,
					network_id,
					if let Some(seed) = seed {
						// sc_cli::utils::format_seed::<Pair>(seed)
						format!("0x{}", hex::encode(seed.as_ref()))
					} else {
						"n/a".into()
					},
					format_public_key::<Pair>(public_key.clone()),
					format_account_id::<Pair>(public_key.clone()),
					public_key.to_ss58check_with_version(network_override),
					pair.public()
						.into()
						.into_account()
						.to_ss58check_with_version(network_override),
				);
			} else {
				println!("Invalid phrase/URI given");
			}
		}

		/// Public key type for Runtime
		pub type PublicFor<P> = <P as sp_core::Pair>::Public;

		/// formats public key as hex
		fn format_public_key<P: sp_core::Pair>(public_key: PublicFor<P>) -> String {
			format!("0x{}", hex::encode(&public_key.as_ref()))
		}

		/// formats public key as accountId as hex
		fn format_account_id<P: sp_core::Pair>(public_key: PublicFor<P>) -> String
		where
			PublicFor<P>: Into<MultiSigner>,
		{
			format!(
				"0x{}",
				hex::encode(&public_key.into().into_account().as_slice())
			)
		}

		pub fn unwrap_or_default_ss58_version(
			network: Option<Ss58AddressFormat>,
		) -> Ss58AddressFormat {
			network.unwrap_or_else(default_ss58_version)
		}

		pub fn default_ss58_version() -> Ss58AddressFormat {
			DEFAULT_VERSION
				.load(core::sync::atomic::Ordering::Relaxed)
				.into()
		}

		static DEFAULT_VERSION: core::sync::atomic::AtomicU16 = core::sync::atomic::AtomicU16::new(
			from_known_address_format(Ss58AddressFormatRegistry::SubstrateAccount),
		);

		pub const fn from_known_address_format(x: Ss58AddressFormatRegistry) -> u16 {
			x as u16
		}
	}

	mod substrate {
		use super::*;

		/// Testing sr25519 mnemonic derivations
		///
		/// Using `subkey` command to have expected values from mnemonic derivations (using `SUBSTRATE_MNEMONIC` for tests)
		///
		/// ##### The root mnemonic
		/// ```
		/// subkey inspect
		/// URI:
		/// Secret phrase:       bottom drive obey lake curtain smoke basket hold race lonely fit walk
		///   Network ID:        substrate
		///   Secret seed:       0xfac7959dbfe72f052e5a0c3c8d6530f202b02fd8f9f5ca3580ec8deb7797479e
		///   Public key (hex):  0x46ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a
		///   Account ID:        0x46ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a
		///   Public key (SS58): 5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV
		///   SS58 Address:      5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV
		/// ```
		///
		/// ##### The '//0' derivation
		/// ```
		/// subkey inspect
		/// URI:
		/// Secret Key URI `bottom drive obey lake curtain smoke basket hold race lonely fit walk//0` is account:
		///   Network ID:        substrate
		///   Secret seed:       0x914dded06277afbe5b0e8a30bce539ec8a9552a784d08e530dc7c2915c478393
		///   Public key (hex):  0x2afba9278e30ccf6a6ceb3a8b6e336b70068f045c666f2e7f4f9cc5f47db8972
		///   Account ID:        0x2afba9278e30ccf6a6ceb3a8b6e336b70068f045c666f2e7f4f9cc5f47db8972
		///   Public key (SS58): 5D34dL5prEUaGNQtPPZ3yN5Y6BnkfXunKXXz6fo7ZJbLwRRH
		///   SS58 Address:      5D34dL5prEUaGNQtPPZ3yN5Y6BnkfXunKXXz6fo7ZJbLwRRH
		/// ```
		#[test]
		fn test_sr25519_mnemonic_derivations() {
			let root_sr25519_pair = pair_from_sr25519_str(SUBSTRATE_MNEMONIC).unwrap();

			let expected_root_ss58_address_string =
				"5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV".to_string();
			let root_ss58_address: AccountId = root_sr25519_pair.public().into();
			println!("root SS58 Address: '{}'", root_ss58_address);
			assert_eq!(
				expected_root_ss58_address_string,
				root_ss58_address.to_string()
			);

			// Using derive on root keypair to get '//0'
			let (deriv_0_sr25519_pair, _seed) = root_sr25519_pair
				.derive(Some(sp_core::DeriveJunction::hard(0)).into_iter(), None)
				.unwrap();

			let expected_deriv_0_ss58_address_string =
				"5D34dL5prEUaGNQtPPZ3yN5Y6BnkfXunKXXz6fo7ZJbLwRRH".to_string();
			let deriv_0_ss58_address: AccountId = deriv_0_sr25519_pair.public().into();
			println!("derived '//0' SS58 Address: '{}'", deriv_0_ss58_address);
			assert_eq!(
				expected_deriv_0_ss58_address_string,
				deriv_0_ss58_address.to_string()
			);

			// Using sp_core::sr25519::Pair::from_string(suri, None) to derive keypair from suri
			let deriv_0_suri = SUBSTRATE_MNEMONIC.to_string() + "//0";
			let deriv_0_suri_sr25519_pair =
				sp_core::sr25519::Pair::from_string(&deriv_0_suri, None).unwrap();
			let deriv_0_suri_ss58_address: AccountId = deriv_0_suri_sr25519_pair.public().into();
			println!(
				"derived '//0' from suri SS58 Address: '{}'",
				deriv_0_suri_ss58_address
			);
			assert_eq!(
				expected_deriv_0_ss58_address_string,
				deriv_0_suri_ss58_address.to_string()
			);
		}
	}

	mod seed {
		use super::*;

		/// Testing sr25519 seed derivations
		///
		/// Using `subkey` command to have expected values from seed derivations (using a newly generated mnemonic `festival insane keep vivid surface photo razor unaware twice sudden involve false` for this test)
		/// Secret phrase:       festival insane keep vivid surface photo razor unaware twice sudden involve false
		///   Secret seed:       0xf813535799c3a15b8e419a06964e87fabd3f265caebcbb38c935a1acdbe05253
		///   Public key (hex):  0xce47000c942392afacc938b0db0b79d3377b1d1f5fbad6374c2943af05dfe379
		///   Account ID:        0xce47000c942392afacc938b0db0b79d3377b1d1f5fbad6374c2943af05dfe379
		///   Public key (SS58): 5GjAp6kkbsDjxABGouMvu1Mxbr7PaFqbMrprEoZ466vPF2Vt
		///   SS58 Address:      5GjAp6kkbsDjxABGouMvu1Mxbr7PaFqbMrprEoZ466vPF2Vt
		/// ```
		///
		/// When using the seed directly
		/// ```
		/// subkey inspect
		/// URI:
		/// Secret Key URI `0xf813535799c3a15b8e419a06964e87fabd3f265caebcbb38c935a1acdbe05253` is account:
		///   Network ID:        substrate
		///   Secret seed:       0xf813535799c3a15b8e419a06964e87fabd3f265caebcbb38c935a1acdbe05253
		///   Public key (hex):  0xce47000c942392afacc938b0db0b79d3377b1d1f5fbad6374c2943af05dfe379
		///   Account ID:        0xce47000c942392afacc938b0db0b79d3377b1d1f5fbad6374c2943af05dfe379
		///   Public key (SS58): 5GjAp6kkbsDjxABGouMvu1Mxbr7PaFqbMrprEoZ466vPF2Vt
		///   SS58 Address:      5GjAp6kkbsDjxABGouMvu1Mxbr7PaFqbMrprEoZ466vPF2Vt
		/// ```
		///
		/// ##### The '//0' derivation
		/// ```
		/// subkey inspect
		/// URI:
		/// Secret Key URI `0xf813535799c3a15b8e419a06964e87fabd3f265caebcbb38c935a1acdbe05253//0` is account:
		///   Secret seed:       0xb1f84cbfd8db9de2b198f6658cf8ad5aacc157589e891a653ca1eed3979f8220
		///   Public key (hex):  0xe2b36186911ac4bc7480516b8711be124d97c625f255cbf494bd5f997b8e6023
		///   Account ID:        0xe2b36186911ac4bc7480516b8711be124d97c625f255cbf494bd5f997b8e6023
		///   Public key (SS58): 5HBwy19piXNWg7bfShuNgWsBCWRzkG99M8eRGB6PHkxeAKAV
		///   SS58 Address:      5HBwy19piXNWg7bfShuNgWsBCWRzkG99M8eRGB6PHkxeAKAV
			let root_seed = "f813535799c3a15b8e419a06964e87fabd3f265caebcbb38c935a1acdbe05253";
			let root_sr25519_pair = pair_from_sr25519_seed(root_seed).unwrap();

			let expected_root_ss58_address_string =
				"5GjAp6kkbsDjxABGouMvu1Mxbr7PaFqbMrprEoZ466vPF2Vt".to_string();
			let root_ss58_address: AccountId = root_sr25519_pair.public().into();
			println!("root SS58 Address: '{}'", root_ss58_address);
			assert_eq!(
				expected_root_ss58_address_string,
				root_ss58_address.to_string()
			);

			// Using derive on root keypair to get "//0"
			let (deriv_0_sr25519_pair, _seed) = root_sr25519_pair
				.derive(Some(sp_core::DeriveJunction::hard(0)).into_iter(), None)
				.unwrap();

			let expected_deriv_0_ss58_address_string =
				"5HBwy19piXNWg7bfShuNgWsBCWRzkG99M8eRGB6PHkxeAKAV".to_string();
			let deriv_0_ss58_address: AccountId = deriv_0_sr25519_pair.public().into();
			println!("derived '//0' SS58 Address: '{}'", deriv_0_ss58_address);
			assert_eq!(
				expected_deriv_0_ss58_address_string,
				deriv_0_ss58_address.to_string()
			);

			// Using sp_core::sr25519::Pair::from_string(suri, None) to derive keypair from suri
			let deriv_0_suri = "0x".to_string() + root_seed + "//0";
			let deriv_0_suri_sr25519_pair =
				sp_core::sr25519::Pair::from_string(&deriv_0_suri, None).unwrap();
			let deriv_0_suri_ss58_address: AccountId = deriv_0_suri_sr25519_pair.public().into();
			println!(
				"derived '//0' from suri SS58 Address: '{}'",
				deriv_0_suri_ss58_address
			);
			assert_eq!(
				expected_deriv_0_ss58_address_string,
				deriv_0_suri_ss58_address.to_string()
			);
		}
	}

	mod predefined {
		use super::*;

		/// Testing predefined mnemonic derivations (using names instead of indexes)
		///
		/// Using `subkey` command to have expected values from predefined mnemonic derivations
		///
		/// ##### The root mnemonic
		/// ```
		/// subkey inspect
		/// URI:
		/// Secret phrase:       bottom drive obey lake curtain smoke basket hold race lonely fit walk
		///   Network ID:        substrate
		///   Secret seed:       0xfac7959dbfe72f052e5a0c3c8d6530f202b02fd8f9f5ca3580ec8deb7797479e
		///   Public key (hex):  0x46ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a
		///   Account ID:        0x46ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a
		///   Public key (SS58): 5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV
		///   SS58 Address:      5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV
		/// ```
		///
		/// ##### The '//Alice' derivation
		/// ```
		/// subkey inspect
		/// URI:
		/// Secret Key URI `bottom drive obey lake curtain smoke basket hold race lonely fit walk//Alice` is account:
		///   Network ID:        substrate
		///   Secret seed:       0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a
		///   Public key (hex):  0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d
		///   Account ID:        0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d
		///   Public key (SS58): 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
		///   SS58 Address:      5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
		/// ```
		#[test]
		fn test_predefined_mnemonic_derivations() {
			let root_sr25519_pair = pair_from_sr25519_str(SUBSTRATE_MNEMONIC).unwrap();

			let expected_root_ss58_address_string =
				"5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV".to_string();
			let root_ss58_address: AccountId = root_sr25519_pair.public().into();
			println!("root SS58 Address: '{}'", root_ss58_address);
			assert_eq!(
				expected_root_ss58_address_string,
				root_ss58_address.to_string()
			);

			// Using derive on root keypair to get Alice
			let (alice_sr25519_pair, _seed) = root_sr25519_pair
				.derive(
					Some(sp_core::DeriveJunction::hard("Alice")).into_iter(),
					None,
				)
				.unwrap();

			let expected_alice_ss58_address_string =
				"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY".to_string();
			let alice_ss58_address: AccountId = alice_sr25519_pair.public().into();
			println!("Alice SS58 Address: '{}'", alice_ss58_address);
			assert_eq!(
				expected_alice_ss58_address_string,
				alice_ss58_address.to_string()
			);

			// Using sp_core::sr25519::Pair::from_string(suri, None) to derive keypair from suri
			let alice_suri = SUBSTRATE_MNEMONIC.to_owned() + "//Alice";
			let alice_suri_sr25519_pair =
				sp_core::sr25519::Pair::from_string(&alice_suri, None).unwrap();
			let alice_suri_ss58_address: AccountId = alice_suri_sr25519_pair.public().into();
			println!("Alice suri SS58 Address: '{}'", alice_suri_ss58_address);
			assert_eq!(
				expected_alice_ss58_address_string,
				alice_suri_ss58_address.to_string()
			);
		}
	}

	mod cesium {
		use super::*;

		/// Test which verifies that it's possible to derive a key coming from a cesium v1 id & password
		///
		/// Using subkey command with **ed25519** scheme to show we can derive a key from a seed
		/// and to retrieve expected values.
		///
		/// ##### Without derivation (using seed from the test)
		/// ```
		/// subkey inspect --scheme ed25519
		/// URI:
		/// Secret Key URI `0x2101d2bc68de9ad149c06293bfe489c8608de576c88927aa5439a81be17aae84` is account:
		///   Network ID:        substrate
		///   Secret seed:       0x2101d2bc68de9ad149c06293bfe489c8608de576c88927aa5439a81be17aae84
		///   Public key (hex):  0x697f6bd16ddebf142384e503fd3f3efc39fe5c7be7c693bd98d982403bb6eb74
		///   Account ID:        0x697f6bd16ddebf142384e503fd3f3efc39fe5c7be7c693bd98d982403bb6eb74
		///   Public key (SS58): 5ET2jhgJFoNQUpgfdSkdwftK8DKWdqZ1FKm5GKWdPfMWhPr4
		///   SS58 Address:      5ET2jhgJFoNQUpgfdSkdwftK8DKWdqZ1FKm5GKWdPfMWhPr4
		/// ```
		///
		/// ##### With derivation '//0' (using seed from the test)
		/// ```
		/// subkey inspect --scheme ed25519
		/// URI:
		/// Secret Key URI `0x2101d2bc68de9ad149c06293bfe489c8608de576c88927aa5439a81be17aae84//0` is account:
		///   Network ID:        substrate
		///   Secret seed:       0x916e95359a49c82e3d84269b90551433352c433eb9bb270fb8cb86e8a6c9ec85
		///   Public key (hex):  0x1658ce32f039dff26d83b5282f611cfed8c71296a311417ef737db4e016194de
		///   Account ID:        0x1658ce32f039dff26d83b5282f611cfed8c71296a311417ef737db4e016194de
		///   Public key (SS58): 5Ca1HrNxQ4hiekd92Z99fzhfdSAqPy2rUkLBmwLsgLCjeSQf
		///   SS58 Address:      5Ca1HrNxQ4hiekd92Z99fzhfdSAqPy2rUkLBmwLsgLCjeSQf
		/// ```
		#[test]
		fn test_cesium_v1_key_derivation() {
			let cesium_id = "test_cesium_id".to_string();
			let cesium_pwd = "test_cesium_pwd".to_string();

			let expected_cesium_v1_ss58_address: String =
				"5ET2jhgJFoNQUpgfdSkdwftK8DKWdqZ1FKm5GKWdPfMWhPr4".to_string();
			let seed = seed_from_cesium(&cesium_id, &cesium_pwd);

			let ed25519_pair_from_seed = sp_core::ed25519::Pair::from_seed(&seed);

			println!();
			println!(
				"ed25519 keypair from seed: public:'0x{}' raw_vec:'0x{}'",
				hex::encode(ed25519_pair_from_seed.public().0),
				hex::encode(ed25519_pair_from_seed.to_raw_vec().as_slice())
			);

			println!(
				"ed25519 keypair from seed: Cesium v1 Pubkey: '{}'",
				bs58::encode(ed25519_pair_from_seed.public()).into_string()
			);

			let ed25519_address_from_seed: AccountId = ed25519_pair_from_seed.public().into();
			println!(
				"ed25519 keypair from seed: public SS58 Address:'{}'",
			assert_eq!(
				expected_cesium_v1_ss58_address,
				ed25519_address_from_seed.to_string()
			);

			let root_suri = "0x".to_string() + &hex::encode(seed);
			let ed25519_pair_from_suri =
				sp_core::ed25519::Pair::from_string(&root_suri, None).unwrap();
			let ed25519_address_from_suri: AccountId = ed25519_pair_from_suri.public().into();
			println!(
				"ed25519 keypair from suri: public SS58 Address:'{}'",
			assert_eq!(
				expected_cesium_v1_ss58_address,
				ed25519_address_from_suri.to_string()
			);

			// Tested derivation manually with `subkey` command using adapted suri: `0x2101d2bc68de9ad149c06293bfe489c8608de576c88927aa5439a81be17aae84//0`
			let expected_ss58_address_derivation_0 =
				"5Ca1HrNxQ4hiekd92Z99fzhfdSAqPy2rUkLBmwLsgLCjeSQf".to_string();

			// Using derive on the ed25519 keypair
			let (derived_ed25519_pair, _seed) = ed25519_pair_from_seed
				.derive(Some(sp_core::DeriveJunction::hard(0)).into_iter(), None)
				.unwrap();
			println!();
			println!(
				"derived ed25519 keypair: public:'0x{}' raw_vec:'0x{}'",
				hex::encode(derived_ed25519_pair.public().0),
				hex::encode(derived_ed25519_pair.to_raw_vec().as_slice())
			);

			println!(
				"derived ed25519 keypair: Cesium v1 Pubkey: '{}'",
				bs58::encode(derived_ed25519_pair.public()).into_string()
			);

			let derived_ed25519_address =
				subxt::utils::AccountId32::from(derived_ed25519_pair.public());
			println!(
				"derived ed25519 keypair: public SS58 Address:'{}'",
				derived_ed25519_address
			);
			assert_eq!(
				expected_ss58_address_derivation_0,
				derived_ed25519_address.to_string()
			);

			// Using sp_core::ed25519::Pair::from_string(suri, None) to derive keypair from suri
			let deriv_0_suri = root_suri.clone() + "//0";
			let derived_ed25519_pair_from_suri =
				sp_core::ed25519::Pair::from_string(&deriv_0_suri, None).unwrap();
			let derived_ed25519_address_from_suri: AccountId =
				derived_ed25519_pair_from_suri.public().into();
			println!(
				"derived ed25519 keypair from suri: public SS58 Address:'{}'",
				derived_ed25519_address_from_suri
			);
			assert_eq!(
				expected_ss58_address_derivation_0,
				derived_ed25519_address_from_suri.to_string()
			);
		}

		/// Test which verifies that it's possible to directly use a cesium v1 seed retrieved using scryt
		/// as seed or suri to instantiate an ed25519 keypair - keeping the same resulting SS58 Address as when using nacl::sign::Keypair
		///
		/// Using subkey command with **ed25519** scheme to show the seed/suri and associated SS58 Address
		///
		/// ```
		/// subkey inspect --scheme ed25519
		/// URI:
		/// Secret Key URI `0x2101d2bc68de9ad149c06293bfe489c8608de576c88927aa5439a81be17aae84` is account:
		///   Network ID:        substrate
		///   Secret seed:       0x2101d2bc68de9ad149c06293bfe489c8608de576c88927aa5439a81be17aae84
		///   Public key (hex):  0x697f6bd16ddebf142384e503fd3f3efc39fe5c7be7c693bd98d982403bb6eb74
		///   Account ID:        0x697f6bd16ddebf142384e503fd3f3efc39fe5c7be7c693bd98d982403bb6eb74
		///   Public key (SS58): 5ET2jhgJFoNQUpgfdSkdwftK8DKWdqZ1FKm5GKWdPfMWhPr4
		///   SS58 Address:      5ET2jhgJFoNQUpgfdSkdwftK8DKWdqZ1FKm5GKWdPfMWhPr4
		/// ```
		#[test]
		fn test_cesium_v1_seed_using_scrypt() {
			let cesium_id = "test_cesium_id".to_string();
			let cesium_pwd = "test_cesium_pwd".to_string();

			let expected_cesium_v1_ss58_address: String =
				"5ET2jhgJFoNQUpgfdSkdwftK8DKWdqZ1FKm5GKWdPfMWhPr4".to_string();

			// retrieving seed using scrypt
			let seed = seed_from_cesium(&cesium_id, &cesium_pwd);

			println!("seed value from scrypt: '0x{}'", hex::encode(&seed));

			let ed25519_pair_from_seed = sp_core::ed25519::Pair::from_seed(&seed);

			println!();
			println!(
				"ed25519 keypair from seed : public:'0x{}' raw_vec:'0x{}'",
				hex::encode(ed25519_pair_from_seed.public().0),
				hex::encode(ed25519_pair_from_seed.to_raw_vec().as_slice())
			);
			let ed25519_address_from_seed: AccountId = ed25519_pair_from_seed.public().into();
			println!(
				"ed25519 keypair from seed : public SS58 Address:'{}'",
				ed25519_address_from_seed
			);
			assert_eq!(
				expected_cesium_v1_ss58_address,
				ed25519_address_from_seed.to_string()
			);

			let root_suri = "0x".to_string() + &hex::encode(seed);
			let ed25519_pair_from_suri =
				sp_core::ed25519::Pair::from_string(&root_suri, None).unwrap();
			let ed25519_address_from_suri: AccountId = ed25519_pair_from_suri.public().into();
			println!(
				"ed25519 keypair from suri : public SS58 Address:'{}'",
				ed25519_address_from_suri
			);
			assert_eq!(
				expected_cesium_v1_ss58_address,
				ed25519_address_from_suri.to_string()
			);