-
Nicolas80 authored
* Added "/.idea" exclusion in .gitignore (for when using JetBrains IDEs) * Added dialoguer dependency for easier user input handling (see in inputs.rs) * Added sea-orm dependency to allow having DB entity mappings and use a local sqlite file database * Added rstest test dependency for parameterized tests support * Added derivation tests for each SecretFormat (including cesium v1 key derivation, using sp_core::ed25519::Pair) * Made a lot of changes to add vault_account and vault_derivation db tables to persist vault keys & derivations * Added support for KeyPair::Ed25519 linking to sp_core::ed25519::Pair which can be created from secret seed retrieved from nacl::sign::Keypair (which is created from cesium id + secret) ** This was necessary to allow deriving keys from "cesium v1" keys (to be reviewed - it might be a bad idea to permit that from a security point of view) * Only kept original (substrate) keyfiles support for migration (use "vault list-files" and "vault migrate") * Added possibility to give either "-a" Address or "-v" Vault Name as general option * Added extra commands in Vault ** list-files: (deprecated)List available key files (needs to be migrated with command "vault migrate" in order to use them) ** migrate: (deprecated)Migrate old key files into db (will have to provide password for each key) ** 'list' now has sub-commands 'all' or 'root' to show all keys or only root keys (without derivation path) ** use: "Use specific vault key (changes the config address)", which will have the same behaviour as `gcli <-a <Address>|-v <VaultName>> config save` (left a FIXME in there to review) ** derivation: Add a derivation to an existing (root) vault key ** rename: Give a meaningful vault name to a vault key or derivation ** remove: Remove a vault key (and potential derivations if it's a root key) * Had to bubble up "await" and "async" in a lot of places * ...
Nicolas80 authored* Added "/.idea" exclusion in .gitignore (for when using JetBrains IDEs) * Added dialoguer dependency for easier user input handling (see in inputs.rs) * Added sea-orm dependency to allow having DB entity mappings and use a local sqlite file database * Added rstest test dependency for parameterized tests support * Added derivation tests for each SecretFormat (including cesium v1 key derivation, using sp_core::ed25519::Pair) * Made a lot of changes to add vault_account and vault_derivation db tables to persist vault keys & derivations * Added support for KeyPair::Ed25519 linking to sp_core::ed25519::Pair which can be created from secret seed retrieved from nacl::sign::Keypair (which is created from cesium id + secret) ** This was necessary to allow deriving keys from "cesium v1" keys (to be reviewed - it might be a bad idea to permit that from a security point of view) * Only kept original (substrate) keyfiles support for migration (use "vault list-files" and "vault migrate") * Added possibility to give either "-a" Address or "-v" Vault Name as general option * Added extra commands in Vault ** list-files: (deprecated)List available key files (needs to be migrated with command "vault migrate" in order to use them) ** migrate: (deprecated)Migrate old key files into db (will have to provide password for each key) ** 'list' now has sub-commands 'all' or 'root' to show all keys or only root keys (without derivation path) ** use: "Use specific vault key (changes the config address)", which will have the same behaviour as `gcli <-a <Address>|-v <VaultName>> config save` (left a FIXME in there to review) ** derivation: Add a derivation to an existing (root) vault key ** rename: Give a meaningful vault name to a vault key or derivation ** remove: Remove a vault key (and potential derivations if it's a root key) * Had to bubble up "await" and "async" in a lot of places * ...
keys.rs 23.30 KiB
use crate::*;
use sp_core::ed25519;
use sp_core::ed25519::Pair as Ed25519Pair;
use sp_core::sr25519::Pair as Sr25519Pair;
pub const SUBSTRATE_MNEMONIC: &str =
"bottom drive obey lake curtain smoke basket hold race lonely fit walk";
/// secret format
#[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)
#[default]
Substrate,
/// Predefined (Alice, Bob, ...)
Predefined,
/// Cesium (scrypt + nacl)
Cesium,
}
pub enum Secret {
SimpleSecret(String),
DualSecret(String, String),
}
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),
"cesium" => Ok(SecretFormat::Cesium),
_ => 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",
SecretFormat::Cesium => "cesium",
}
}
}
impl From<SecretFormat> for OsStr {
fn from(val: SecretFormat) -> 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
#[allow(clippy::large_enum_variant)]
pub enum KeyPair {
Sr25519(Sr25519Pair),
Ed25519(Ed25519Pair),
//FIXME Cleanup
Nacl(nacl::sign::Keypair),
}
impl KeyPair {
pub fn address(&self) -> AccountId {
match self {
KeyPair::Sr25519(keypair) => keypair.public().into(),
KeyPair::Ed25519(keypair) => keypair.public().into(),
KeyPair::Nacl(keypair) => keypair.pkey.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),
KeyPair::Nacl(keypair) => KeyPair::Nacl(nacl::sign::Keypair {
skey: keypair.skey,
pkey: keypair.pkey,
}),
}
}
}
impl From<Sr25519Pair> for KeyPair {
fn from(pair: Sr25519Pair) -> KeyPair {
KeyPair::Sr25519(pair)
}
}
impl From<Ed25519Pair> for KeyPair {
fn from(pair: Ed25519Pair) -> KeyPair {
KeyPair::Ed25519(pair)
}
}
impl From<nacl::sign::Keypair> for KeyPair {
fn from(pair: nacl::sign::Keypair) -> KeyPair {
KeyPair::Nacl(pair)
}
}
pub enum Signature {
Sr25519(sr25519::Signature),
Ed25519(ed25519::Signature),
//FIXME Cleanup
Nacl(Vec<u8>),
}
/// 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,
) -> Result<Sr25519Pair, 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::Cesium => Err(GcliError::Logic(
"cesium format incompatible with single secret".to_string(),
)),
}
}
/// get keypair from given string secret
pub fn pair_from_sr25519_str(secret: &str) -> Result<Sr25519Pair, GcliError> {
Sr25519Pair::from_string(secret, None)
.map_err(|_| GcliError::Input("Invalid secret".to_string()))
}
/// get keypair from given seed
// note: Sr25519Pair::from_string does exactly that when seed is 0x prefixed
// (see from_string_with_seed method in crypto core)
pub fn pair_from_sr25519_seed(secret: &str) -> Result<Sr25519Pair, GcliError> {
let mut seed = [0; 32];
hex::decode_to_slice(secret, &mut seed)
.map_err(|_| GcliError::Input("Invalid secret".to_string()))?;
let pair = Sr25519Pair::from_seed(&seed);
Ok(pair)
}
/// get keypair from given ed25519 string secret (used for cesium)
pub fn pair_from_ed25519_str(secret: &str) -> Result<Ed25519Pair, GcliError> {
Ed25519Pair::from_string(secret, None)
.map_err(|_| GcliError::Input("Invalid secret".to_string()))
}
/// get keypair from given ed25519 seed (used for cesium)
#[allow(unused)]
pub fn pair_from_ed25519_seed(secret: &str) -> Result<Ed25519Pair, GcliError> {
let mut seed = [0; 32];
hex::decode_to_slice(secret, &mut seed)
.map_err(|_| GcliError::Input("Invalid secret".to_string()))?;
let pair = Ed25519Pair::from_seed(&seed);
Ok(pair)
}
/// get mnemonic from predefined derivation path
pub fn predefined_mnemonic(deriv: &str) -> String {
format!("{SUBSTRATE_MNEMONIC}//{deriv}")
}
/// get keypair from predefined secret
pub fn pair_from_predefined(deriv: &str) -> Result<Sr25519Pair, GcliError> {
pair_from_sr25519_str(&predefined_mnemonic(deriv))
}
/// get keypair from Cesium id/pwd
pub fn pair_from_cesium(id: String, pwd: String) -> nacl::sign::Keypair {
let params = scrypt::Params::new(12u8, 16u32, 1u32, 32).unwrap();
let seed = &mut [0u8; 32];
scrypt::scrypt(&pwd.into_bytes(), &id.into_bytes(), ¶ms, seed).unwrap();
nacl::sign::generate_keypair(seed)
}
/// ask user to input a secret
pub fn prompt_secret_substrate() -> Sr25519Pair {
// Only interested in the keypair which is the second element of the tuple
prompt_secret_substrate_and_compute_keypair().1
}
pub fn prompt_secret_substrate_and_compute_keypair() -> (Secret, Sr25519Pair) {
loop {
let mnemonic = rpassword::prompt_password("Mnemonic: ").unwrap();
match pair_from_sr25519_str(&mnemonic) {
Ok(pair) => return (Secret::SimpleSecret(mnemonic), pair),
Err(_) => println!("Invalid secret"),
}
}
}
/// ask user pass (Cesium format)
pub fn prompt_secret_cesium() -> nacl::sign::Keypair {
// Only interested in the keypair which is the second element of the tuple
prompt_secret_cesium_and_compute_keypair().1
}
pub fn prompt_secret_cesium_and_compute_keypair() -> (Secret, nacl::sign::Keypair) {
let id = rpassword::prompt_password("Cesium id: ").unwrap();
let pwd = rpassword::prompt_password("Cesium password: ").unwrap();
(
Secret::DualSecret(id.clone(), pwd.clone()),
pair_from_cesium(id, pwd),
)
}
/// ask user to input a seed
pub fn prompt_seed() -> Sr25519Pair {
// Only interested in the keypair which is the second element of the tuple
prompt_seed_and_compute_keypair().1
}
pub fn prompt_seed_and_compute_keypair() -> (Secret, Sr25519Pair) {
loop {
let seed = rpassword::prompt_password("Seed: ").unwrap();
match pair_from_sr25519_seed(&seed) {
Ok(pair) => return (Secret::SimpleSecret(seed), pair),
Err(_) => println!("Invalid seed"),
}
}
}
/// ask user pass (Cesium format)
pub fn prompt_predefined() -> Sr25519Pair {
// Only interested in the keypair which is the second element of the tuple
prompt_predefined_and_compute_keypair().1
}
pub fn prompt_predefined_and_compute_keypair() -> (Secret, Sr25519Pair) {
let deriv = rpassword::prompt_password("Enter derivation path: ").unwrap();
(
Secret::SimpleSecret(predefined_mnemonic(&deriv)),
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::Cesium => 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,
}
}
// Unit tests
#[cfg(test)]
mod tests {
use super::*;
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 seed linked to `SUBSTRATE_MNEMONIC` for tests)
///
/// ##### The root seed
/// ```
/// subkey inspect
/// URI:
/// Secret Key URI `0xfac7959dbfe72f052e5a0c3c8d6530f202b02fd8f9f5ca3580ec8deb7797479e` is account:
/// 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 `0xfac7959dbfe72f052e5a0c3c8d6530f202b02fd8f9f5ca3580ec8deb7797479e//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_seed_derivations() {
let root_seed = "fac7959dbfe72f052e5a0c3c8d6530f202b02fd8f9f5ca3580ec8deb7797479e";
let root_sr25519_pair = pair_from_sr25519_seed(root_seed).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 = "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 cesium_keypair = pair_from_cesium(cesium_id, cesium_pwd);
println!(
"Nacl keypair: pkey:'0x{}' skey:'0x{}'",
hex::encode(cesium_keypair.pkey),
hex::encode(cesium_keypair.skey)
);
let cesium_v1_pubkey = bs58::encode(cesium_keypair.pkey).into_string();
println!("Cesium v1 Pubkey: '{}'", cesium_v1_pubkey);
let cesium_v1_address: AccountId = cesium_keypair.pkey.into();
println!("SS58 Address: '{}'", cesium_v1_address);
//ed25519 seed **seems** to be the first 32 bytes of the secret key from nacl keypair
let mut seed: [u8; 32] = [0; 32];
seed.copy_from_slice(&cesium_keypair.skey[0..32]);
println!();
println!("ed25519 seed: '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!(cesium_v1_address, ed25519_address_from_seed);
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!(cesium_v1_address, ed25519_address_from_suri);
// 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())
);
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()
);
}
}
}