diff --git a/src/commands/identity.rs b/src/commands/identity.rs index 0cc8f4f1cd765783a93b52df1cad89d04be6d24a..6a64121e00d2fdf4cf43c3880242182ef717f57c 100644 --- a/src/commands/identity.rs +++ b/src/commands/identity.rs @@ -154,7 +154,7 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE secret_format, secret, } => { - let keypair = get_keypair(secret_format, secret.as_deref())?; + let keypair = get_keypair(secret_format, secret.as_deref(), None)?; let address = keypair.address(); data = data.fetch_idty_index().await?; // idty index required for payload link_account(&data, address, keypair).await?; @@ -163,7 +163,7 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE secret_format, secret, } => { - let keypair = get_keypair(secret_format, secret.as_deref())?; + let keypair = get_keypair(secret_format, secret.as_deref(), None)?; let address = keypair.address(); data = data.fetch_idty_index().await?; // idty index required for payload change_owner_key(&data, address, keypair).await?; diff --git a/src/commands/vault.rs b/src/commands/vault.rs index a4e3594097f93dabb3a27f5e7e57243e8d7dce50..ce9942b4f373610c45794ed25cc8cde36828bd5a 100644 --- a/src/commands/vault.rs +++ b/src/commands/vault.rs @@ -475,7 +475,7 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE &txn, &vault_data_to_import, Some(&vault_data_from_file.password), - None, + Some(CryptoScheme::Ed25519), ) .await; diff --git a/src/commands/vault/display.rs b/src/commands/vault/display.rs index e9a9d2d8b580506ffaab468b70ca8bcf706c8617..e51560a30e07d61807bc51fe0672fb3ad4fea009 100644 --- a/src/commands/vault/display.rs +++ b/src/commands/vault/display.rs @@ -230,15 +230,27 @@ mod tests { fn test_compute_vault_accounts_table_with_g1v1_empty() { // Test with show_g1v1 = true (default behavior) let table = compute_vault_accounts_table_with_g1v1(&[], true).unwrap(); - println!("Table with show_g1v1=true:\n{}", table.to_string()); - let expected_table = table.to_string(); - assert_eq!(table.to_string(), expected_table); + + let expected_table_with_g1v1 = indoc! {r#" + ┌─────────────────────────────────────────────────────┠+ │ SS58 Address/G1v1 public key Crypto Path Name │ + â•žâ•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•¡ + └─────────────────────────────────────────────────────┘"# + }; + + assert_eq!(table.to_string(), expected_table_with_g1v1); // Test with show_g1v1 = false let table = compute_vault_accounts_table_with_g1v1(&[], false).unwrap(); - println!("Table with show_g1v1=false:\n{}", table.to_string()); - let expected_table = table.to_string(); - assert_eq!(table.to_string(), expected_table); + + let expected_table_without_g1v1 = indoc! {r#" + ┌─────────────────────────────────────┠+ │ SS58 Address Crypto Path Name │ + â•žâ•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•¡ + └─────────────────────────────────────┘"# + }; + + assert_eq!(table.to_string(), expected_table_without_g1v1); } #[test] @@ -248,16 +260,41 @@ mod tests { let account_tree_nodes = vec![account_tree_node, g1v1_account_tree_node]; // Test with show_g1v1 = true (default behavior) - let table = compute_vault_accounts_table_with_g1v1(&account_tree_nodes, true).unwrap(); - println!("Table with show_g1v1=true:\n{}", table.to_string()); - let expected_table = table.to_string(); - assert_eq!(table.to_string(), expected_table); + let table_with_g1v1 = compute_vault_accounts_table_with_g1v1(&account_tree_nodes, true).unwrap(); + + let expected_table_with_g1v1 = indoc! {r#" + ┌──────────────────────────────────────────────────────────────────────────────────────────┠+ │ SS58 Address/G1v1 public key Crypto Path Name │ + â•žâ•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•¡ + │ 5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV sr25519 <Base> Mother │ + │ ├ 5D34dL5prEUaGNQtPPZ3yN5Y6BnkfXunKXXz6fo7ZJbLwRRH //0 Child 1 │ + │ │ ├ 5Fh5PLQNt1xuEXm71dfDtQdnwceSew4oHewWBLsWAkKspV7d //0 Grandchild 1 │ + │ ├ 5GBNeWRhZc2jXu7D55rBimKYDk8PGk8itRYFTPfC8RJLKG5o //1 <Mother//1> │ + │ │ ├ 5CvdJuB9HLXSi5FS9LW57cyHF13iCv5HDimo2C45KxnxriCT //1 <Mother//1//1> │ + │ 5ET2jhgJFoNQUpgfdSkdwftK8DKWdqZ1FKm5GKWdPfMWhPr4 ed25519 <Base> MotherG1v1 │ + │ â”” G1v1: 86pW1doyJPVH3jeDPZNQa1UZFBo5zcdvHERcaeE758W7 │ + └──────────────────────────────────────────────────────────────────────────────────────────┘"# + }; + + assert_eq!(table_with_g1v1.to_string(), expected_table_with_g1v1); // Test with show_g1v1 = false - let table = compute_vault_accounts_table_with_g1v1(&account_tree_nodes, false).unwrap(); - println!("Table with show_g1v1=false:\n{}", table.to_string()); - let expected_table = table.to_string(); - assert_eq!(table.to_string(), expected_table); + let table_without_g1v1 = compute_vault_accounts_table_with_g1v1(&account_tree_nodes, false).unwrap(); + + let expected_table_without_g1v1 = indoc! {r#" + ┌──────────────────────────────────────────────────────────────────────────────────────────┠+ │ SS58 Address Crypto Path Name │ + â•žâ•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•¡ + │ 5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV sr25519 <Base> Mother │ + │ ├ 5D34dL5prEUaGNQtPPZ3yN5Y6BnkfXunKXXz6fo7ZJbLwRRH //0 Child 1 │ + │ │ ├ 5Fh5PLQNt1xuEXm71dfDtQdnwceSew4oHewWBLsWAkKspV7d //0 Grandchild 1 │ + │ ├ 5GBNeWRhZc2jXu7D55rBimKYDk8PGk8itRYFTPfC8RJLKG5o //1 <Mother//1> │ + │ │ ├ 5CvdJuB9HLXSi5FS9LW57cyHF13iCv5HDimo2C45KxnxriCT //1 <Mother//1//1> │ + │ 5ET2jhgJFoNQUpgfdSkdwftK8DKWdqZ1FKm5GKWdPfMWhPr4 ed25519 <Base> MotherG1v1 │ + └──────────────────────────────────────────────────────────────────────────────────────────┘"# + }; + + assert_eq!(table_without_g1v1.to_string(), expected_table_without_g1v1); } #[test] @@ -267,16 +304,32 @@ mod tests { let account_tree_nodes = vec![child1]; // Test with show_g1v1 = true (default behavior) - let table = compute_vault_accounts_table_with_g1v1(&account_tree_nodes, true).unwrap(); - println!("Table with show_g1v1=true:\n{}", table.to_string()); - let expected_table = table.to_string(); - assert_eq!(table.to_string(), expected_table); + let table_with_g1v1 = compute_vault_accounts_table_with_g1v1(&account_tree_nodes, true).unwrap(); + + let expected_table_with_g1v1 = indoc! {r#" + ┌─────────────────────────────────────────────────────────────────────────────────────┠+ │ SS58 Address/G1v1 public key Crypto Path Name │ + â•žâ•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•¡ + │ ├ 5D34dL5prEUaGNQtPPZ3yN5Y6BnkfXunKXXz6fo7ZJbLwRRH //0 Child 1 │ + │ │ ├ 5Fh5PLQNt1xuEXm71dfDtQdnwceSew4oHewWBLsWAkKspV7d //0 Grandchild 1 │ + └─────────────────────────────────────────────────────────────────────────────────────┘"# + }; + + assert_eq!(table_with_g1v1.to_string(), expected_table_with_g1v1); // Test with show_g1v1 = false - let table = compute_vault_accounts_table_with_g1v1(&account_tree_nodes, false).unwrap(); - println!("Table with show_g1v1=false:\n{}", table.to_string()); - let expected_table = table.to_string(); - assert_eq!(table.to_string(), expected_table); + let table_without_g1v1 = compute_vault_accounts_table_with_g1v1(&account_tree_nodes, false).unwrap(); + + let expected_table_without_g1v1 = indoc! {r#" + ┌─────────────────────────────────────────────────────────────────────────────────────┠+ │ SS58 Address Crypto Path Name │ + â•žâ•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•¡ + │ ├ 5D34dL5prEUaGNQtPPZ3yN5Y6BnkfXunKXXz6fo7ZJbLwRRH //0 Child 1 │ + │ │ ├ 5Fh5PLQNt1xuEXm71dfDtQdnwceSew4oHewWBLsWAkKspV7d //0 Grandchild 1 │ + └─────────────────────────────────────────────────────────────────────────────────────┘"# + }; + + assert_eq!(table_without_g1v1.to_string(), expected_table_without_g1v1); } } } diff --git a/src/data.rs b/src/data.rs index 1e877c0977c8b760d4f00d0c4a596d22eb92957f..40f99181cad419718035e5c5f6cac4bff7a27213 100644 --- a/src/data.rs +++ b/src/data.rs @@ -123,7 +123,7 @@ impl Data { match self.keypair.clone() { Some(keypair) => keypair, None => loop { - match fetch_or_get_keypair(self, self.cfg.address.clone()).await { + match fetch_or_get_keypair(self, self.cfg.address.clone(), None).await { Ok(pair) => return pair, Err(e) => { //Adapted code to still be able to go out of the loop when user hit "Esc" key or "ctrl+c" when prompted for a value @@ -207,7 +207,7 @@ impl Data { } // secret format and value if let Some(secret_format) = self.args.secret_format { - let keypair = get_keypair(secret_format, self.args.secret.as_deref())?; + let keypair = get_keypair(secret_format, self.args.secret.as_deref(), None)?; self.cfg.address = Some(keypair.address()); self.keypair = Some(keypair); } diff --git a/src/keys.rs b/src/keys.rs index a81f2bfbe0923dba4bb708d944362ea49866437f..cdbfb4439f7c6fa5bdb26f09f7eaf18c478497db 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -136,11 +136,48 @@ pub enum Signature { pub fn get_keypair( secret_format: SecretFormat, secret: Option<&str>, + crypto_scheme: Option<CryptoScheme>, ) -> 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, None)), - (_, Some(secret)) => Ok(pair_from_secret(secret_format, secret)?.into()), + (SecretFormat::Predefined, Some(deriv)) => { + match crypto_scheme { + Some(CryptoScheme::Ed25519) => pair_from_ed25519_str(&predefined_suri(deriv)).map(|v| v.into()), + _ => pair_from_predefined(deriv).map(|v| v.into()), // Default to Sr25519 for backward compatibility + } + }, + (secret_format, None) => Ok(prompt_secret(secret_format, crypto_scheme)), + (_, Some(secret)) => { + match crypto_scheme { + Some(CryptoScheme::Ed25519) => pair_from_secret_with_scheme(secret_format, secret, CryptoScheme::Ed25519), + _ => Ok(pair_from_secret(secret_format, secret)?.into()), // Default to Sr25519 for backward compatibility + } + }, + } +} + +/// get keypair from given secret with specified crypto scheme +/// if secret is predefined, secret should contain the predefined value +pub fn pair_from_secret_with_scheme( + secret_format: SecretFormat, + secret: &str, + crypto_scheme: CryptoScheme, +) -> Result<KeyPair, GcliError> { + match (secret_format, crypto_scheme) { + (SecretFormat::G1v1, _) => Err(GcliError::Logic( + "G1v1 format incompatible with single secret".to_string(), + )), + (_, CryptoScheme::Ed25519) => match secret_format { + SecretFormat::Substrate => pair_from_ed25519_str(secret).map(|v| v.into()), + SecretFormat::Predefined => pair_from_ed25519_str(secret).map(|v| v.into()), + SecretFormat::Seed => { + let mut seed = [0; 32]; + hex::decode_to_slice(secret, &mut seed) + .map_err(|_| GcliError::Input("Invalid secret".to_string()))?; + Ok(ed25519::Pair::from_seed(&seed).into()) + }, + SecretFormat::G1v1 => unreachable!(), // Already handled above + }, + (_, CryptoScheme::Sr25519) => pair_from_secret(secret_format, secret).map(|v| v.into()), } } @@ -195,13 +232,13 @@ pub fn pair_from_ed25519_seed(secret: &str) -> Result<ed25519::Pair, GcliError> } /// get mnemonic from predefined derivation path -pub fn predefined_mnemonic(deriv: &str) -> String { +pub fn predefined_suri(deriv: &str) -> String { format!("{SUBSTRATE_MNEMONIC}//{deriv}") } /// get keypair from predefined secret pub fn pair_from_predefined(deriv: &str) -> Result<sr25519::Pair, GcliError> { - pair_from_sr25519_str(&predefined_mnemonic(deriv)) + pair_from_sr25519_str(&predefined_suri(deriv)) } /// get seed from G1v1 id/pwd (old "cesium") @@ -307,18 +344,18 @@ pub fn prompt_predefined() -> sr25519::Pair { pub fn prompt_predefined_and_compute_keypair(crypto_scheme: CryptoScheme) -> (String, KeyPair) { let deriv = inputs::prompt_password_query("Enter derivation path: ").unwrap(); - let mnemonic = predefined_mnemonic(&deriv); + let suri = predefined_suri(&deriv); match crypto_scheme { CryptoScheme::Sr25519 => { - match pair_from_sr25519_str(&mnemonic) { - Ok(pair) => (mnemonic, pair.into()), + match pair_from_sr25519_str(&suri) { + Ok(pair) => (suri, pair.into()), Err(e) => panic!("Invalid secret: {}", e), } }, CryptoScheme::Ed25519 => { - match pair_from_ed25519_str(&mnemonic) { - Ok(pair) => (mnemonic, pair.into()), + match pair_from_ed25519_str(&suri) { + Ok(pair) => (suri, pair.into()), Err(e) => panic!("Invalid secret: {}", e), } } @@ -345,12 +382,16 @@ pub fn prompt_secret(secret_format: SecretFormat, crypto_scheme: Option<CryptoSc pub async fn fetch_or_get_keypair( data: &Data, address: Option<AccountId>, + crypto_scheme: Option<CryptoScheme>, ) -> 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()); + match crypto_scheme { + Some(CryptoScheme::Ed25519) => return pair_from_ed25519_str(&predefined_suri(d)).map(|v| v.into()), + _ => return Ok(pair_from_predefined(d).unwrap().into()), // Default to Sr25519 for backward compatibility + } }; // look for corresponding KeyPair in keystore @@ -360,7 +401,7 @@ pub async fn fetch_or_get_keypair( } // 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, None)) + Ok(prompt_secret(SecretFormat::Substrate, crypto_scheme)) } // catch known addresses