Skip to content

Commit

Permalink
crypto: Mnemonics word length option (MystenLabs#10329)
Browse files Browse the repository at this point in the history
## Description 

Default CLI mnemonics generation to 12 words. Optional flag is allowed
to generate / import other lengths.
```
target/debug/sui client new-address ed25519                                                                                                                                                                                                                                                                       
Created new keypair for address with scheme ED25519: [0xaf3806a93b7fb6ac24fda8cbbf95c7ae7bd8619559a2283bbab0565116ad5940]
Secret Recovery Phrase : [usage empower claw syrup only leave title arrow phone comic earth rely]
target/debug/sui client new-address ed25519 word24                                                                                                                                                                                                                                                                  

Created new keypair for address with scheme ED25519: [0x2c21f35067b22afea0023a81585ada73e54128a7c0cd8ee5d2aa1bb58117e4f9]
Secret Recovery Phrase : [library erode february enter wild claim cave list typical robot write fault eternal symbol enforce chapter advance exact believe between holiday bachelor bring rebel]
```

## Test Plan 

How did you test the new or updated feature?

---
If your changes are not user-facing and not a breaking change, you can
skip the following section. Otherwise, please indicate what changed, and
then add to the Release Notes section as highlighted during the release
process.

### Type of Change (Check all that apply)

- [ ] user-visible impact
- [ ] breaking change for a client SDKs
- [ ] breaking change for FNs (FN binary must upgrade)
- [ ] breaking change for validators or node operators (must upgrade
binaries)
- [ ] breaking change for on-chain data layout
- [ ] necessitate either a data wipe or data migration

### Release notes
  • Loading branch information
joyqvq authored Apr 3, 2023
1 parent 93cfbf5 commit a652bbc
Show file tree
Hide file tree
Showing 11 changed files with 49 additions and 19 deletions.
23 changes: 19 additions & 4 deletions crates/sui-keys/src/key_derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@
// SPDX-License-Identifier: Apache-2.0

use anyhow::anyhow;
use bip32::{ChildNumber, DerivationPath, Mnemonic, XPrv};
use bip32::{ChildNumber, DerivationPath, XPrv};

use bip39::{Language, Mnemonic, MnemonicType, Seed};
use fastcrypto::ed25519::Ed25519KeyPair;
use fastcrypto::secp256r1::{Secp256r1KeyPair, Secp256r1PrivateKey};
use fastcrypto::{
ed25519::Ed25519PrivateKey,
secp256k1::{Secp256k1KeyPair, Secp256k1PrivateKey},
traits::{KeyPair, ToFromBytes},
};
use signature::rand_core::OsRng;
use slip10_ed25519::derive_ed25519_private_key;
use sui_types::{
base_types::SuiAddress,
Expand Down Expand Up @@ -169,11 +169,26 @@ pub fn validate_path(
pub fn generate_new_key(
key_scheme: SignatureScheme,
derivation_path: Option<DerivationPath>,
word_length: Option<String>,
) -> Result<(SuiAddress, SuiKeyPair, SignatureScheme, String), anyhow::Error> {
let mnemonic = Mnemonic::random(OsRng, Default::default());
let seed = mnemonic.to_seed("");
let mnemonic = Mnemonic::new(parse_word_length(word_length)?, Language::English);
let seed = Seed::new(&mnemonic, "");
match derive_key_pair_from_path(seed.as_bytes(), derivation_path, &key_scheme) {
Ok((address, kp)) => Ok((address, kp, key_scheme, mnemonic.phrase().to_string())),
Err(e) => Err(anyhow!("Failed to generate keypair: {:?}", e)),
}
}

fn parse_word_length(s: Option<String>) -> Result<MnemonicType, anyhow::Error> {
match s {
None => Ok(MnemonicType::Words12),
Some(s) => match s.as_str() {
"word12" => Ok(MnemonicType::Words12),
"word15" => Ok(MnemonicType::Words15),
"word18" => Ok(MnemonicType::Words18),
"word21" => Ok(MnemonicType::Words21),
"word24" => Ok(MnemonicType::Words24),
_ => anyhow::bail!("Invalid word length"),
},
}
}
4 changes: 3 additions & 1 deletion crates/sui-keys/src/keystore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,10 @@ pub trait AccountKeystore: Send + Sync {
&mut self,
key_scheme: SignatureScheme,
derivation_path: Option<DerivationPath>,
word_length: Option<String>,
) -> Result<(SuiAddress, String, SignatureScheme), anyhow::Error> {
let (address, kp, scheme, phrase) = generate_new_key(key_scheme, derivation_path)?;
let (address, kp, scheme, phrase) =
generate_new_key(key_scheme, derivation_path, word_length)?;
self.add_key(kp)?;
Ok((address, phrase, scheme))
}
Expand Down
2 changes: 1 addition & 1 deletion crates/sui-keys/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ fn mnemonic_test() {
let keystore_path = temp_dir.path().join("sui.keystore");
let mut keystore = Keystore::from(FileBasedKeystore::new(&keystore_path).unwrap());
let (address, phrase, scheme) = keystore
.generate_and_add_new_key(SignatureScheme::ED25519, None)
.generate_and_add_new_key(SignatureScheme::ED25519, None, None)
.unwrap();

let keystore_path_2 = temp_dir.path().join("sui2.keystore");
Expand Down
4 changes: 2 additions & 2 deletions crates/sui-rosetta/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,10 +247,10 @@ fn test_read_keystore() {
let path = temp_dir.path().join("sui.keystore");
let mut ks = Keystore::from(FileBasedKeystore::new(&path).unwrap());
let key1 = ks
.generate_and_add_new_key(SignatureScheme::ED25519, None)
.generate_and_add_new_key(SignatureScheme::ED25519, None, None)
.unwrap();
let key2 = ks
.generate_and_add_new_key(SignatureScheme::Secp256k1, None)
.generate_and_add_new_key(SignatureScheme::Secp256k1, None, None)
.unwrap();

let accounts = read_prefunded_account(&path).unwrap();
Expand Down
2 changes: 1 addition & 1 deletion crates/sui-sdk/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ fn mnemonic_test() {
let keystore_path = temp_dir.path().join("sui.keystore");
let mut keystore = Keystore::from(FileBasedKeystore::new(&keystore_path).unwrap());
let (address, phrase, scheme) = keystore
.generate_and_add_new_key(SignatureScheme::ED25519, None)
.generate_and_add_new_key(SignatureScheme::ED25519, None, None)
.unwrap();

let keystore_path_2 = temp_dir.path().join("sui2.keystore");
Expand Down
15 changes: 10 additions & 5 deletions crates/sui/src/client_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -359,10 +359,13 @@ pub enum SuiClientCommands {
Addresses,

/// Generate new address and keypair with keypair scheme flag {ed25519 | secp256k1 | secp256r1}
/// with optional derivation path, default to m/44'/784'/0'/0'/0' for ed25519 or m/54'/784'/0'/0/0 for secp256k1 or m/74'/784'/0'/0/0 for secp256r1.
/// with optional derivation path, default to m/44'/784'/0'/0'/0' for ed25519 or
/// m/54'/784'/0'/0/0 for secp256k1 or m/74'/784'/0'/0/0 for secp256r1. Word length can be
/// { word12 | word15 | word18 | word21 | word24} default to word12 if not specified.
#[clap(name = "new-address")]
NewAddress {
key_scheme: SignatureScheme,
word_length: Option<String>,
derivation_path: Option<DerivationPath>,
},

Expand Down Expand Up @@ -898,11 +901,13 @@ impl SuiClientCommands {
SuiClientCommands::NewAddress {
key_scheme,
derivation_path,
word_length,
} => {
let (address, phrase, scheme) = context
.config
.keystore
.generate_and_add_new_key(key_scheme, derivation_path)?;
let (address, phrase, scheme) = context.config.keystore.generate_and_add_new_key(
key_scheme,
derivation_path,
word_length,
)?;
SuiClientCommandResult::NewAddress((address, phrase, scheme))
}
SuiClientCommands::Gas { address } => {
Expand Down
11 changes: 8 additions & 3 deletions crates/sui/src/keytool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,16 @@ mod keytool_tests;
pub enum KeyToolCommand {
/// Generate a new keypair with key scheme flag {ed25519 | secp256k1 | secp256r1}
/// with optional derivation path, default to m/44'/784'/0'/0'/0' for ed25519 or
/// m/54'/784'/0'/0/0 for secp256k1 or m/74'/784'/0'/0/0 for secp256r1.
/// m/54'/784'/0'/0/0 for secp256k1 or m/74'/784'/0'/0/0 for secp256r1. Word
/// length can be { word12 | word15 | word18 | word21 | word24} default to word12
/// if not specified.
///
/// The keypair file is output to the current directory. The content of the file is
/// a Base64 encoded string of 33-byte `flag || privkey`. Note: To generate and add keypair
/// to sui.keystore, use `sui client new-address`), see more at [enum SuiClientCommands].
Generate {
key_scheme: SignatureScheme,
word_length: Option<String>,
derivation_path: Option<DerivationPath>,
},
/// This reads the content at the provided file path. The accepted format can be
Expand Down Expand Up @@ -68,7 +71,7 @@ pub enum KeyToolCommand {
},
/// Add a new key to sui.key based on the input mnemonic phrase, the key scheme flag {ed25519 | secp256k1 | secp256r1}
/// and an optional derivation path, default to m/44'/784'/0'/0'/0' for ed25519 or m/54'/784'/0'/0/0 for secp256k1
/// or m/74'/784'/0'/0/0 for secp256r1.
/// or m/74'/784'/0'/0/0 for secp256r1. Supports mnemonic phrase of word length 12, 15, 18`, 21, 24.
Import {
mnemonic_phrase: String,
key_scheme: SignatureScheme,
Expand Down Expand Up @@ -112,6 +115,7 @@ impl KeyToolCommand {
KeyToolCommand::Generate {
key_scheme,
derivation_path,
word_length,
} => {
if "bls12381" == key_scheme.to_string() {
// Generate BLS12381 key for authority without key derivation.
Expand All @@ -120,7 +124,8 @@ impl KeyToolCommand {
let file_name = format!("bls-{address}.key");
write_authority_keypair_to_file(&keypair, file_name)?;
} else {
let (address, kp, scheme, _) = generate_new_key(key_scheme, derivation_path)?;
let (address, kp, scheme, _) =
generate_new_key(key_scheme, derivation_path, word_length)?;
let file = format!("{address}.key");
write_keypair_to_file(&kp, &file)?;
println!(
Expand Down
2 changes: 1 addition & 1 deletion crates/sui/src/sui_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,7 @@ async fn prompt_if_no_config(
}
};
let (new_address, phrase, scheme) =
keystore.generate_and_add_new_key(key_scheme, None)?;
keystore.generate_and_add_new_key(key_scheme, None, None)?;
println!(
"Generated new keypair for address with scheme {:?} [{new_address}]",
scheme.to_string()
Expand Down
2 changes: 2 additions & 0 deletions crates/sui/src/unit_tests/cli_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1305,6 +1305,7 @@ async fn test_switch_command() -> Result<(), anyhow::Error> {
let os = SuiClientCommands::NewAddress {
key_scheme: SignatureScheme::ED25519,
derivation_path: None,
word_length: None,
}
.execute(context)
.await?;
Expand Down Expand Up @@ -1356,6 +1357,7 @@ async fn test_new_address_command_by_flag() -> Result<(), anyhow::Error> {
SuiClientCommands::NewAddress {
key_scheme: SignatureScheme::Secp256k1,
derivation_path: None,
word_length: None,
}
.execute(context)
.await?;
Expand Down
1 change: 1 addition & 0 deletions crates/sui/src/unit_tests/keytool_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ fn test_keytool_bls12381() -> Result<(), anyhow::Error> {
KeyToolCommand::Generate {
key_scheme: SignatureScheme::BLS12381,
derivation_path: None,
word_length: None,
}
.execute(&mut keystore)?;
Ok(())
Expand Down
2 changes: 1 addition & 1 deletion crates/sui/src/validator_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ fn make_key_files(
key
}
None => {
let (_, kp, _, _) = generate_new_key(SignatureScheme::ED25519, None)?;
let (_, kp, _, _) = generate_new_key(SignatureScheme::ED25519, None, None)?;
println!("Generated new key file: {:?}.", file_name);
kp
}
Expand Down

0 comments on commit a652bbc

Please sign in to comment.