Skip to content

Commit

Permalink
feat!: implement new CipherSeed and upgrade encryption KDF (#3505)
Browse files Browse the repository at this point in the history
Description
---
This PR adds a new CipherSeed implementation for use a seed for Key Derivation. The goal of the scheme is produce a wallet seed that is versioned, contains the birthday of the wallet, starting
entropy of the wallet to seed key generation, can be enciphered with a passphrase and has a checksum. During this process it was noted that we used a naive method to derive our database encryption key from the passphrase. This PR also updates that method to use Argon2 as a proper password hashing scheme that is not vulnerable to rainbow table brute forcing and timing attacks.

- Update db encryption key generation to use Argon2 KDF
- Persist the Argon2 salted hash in the DB to detect when encryption has been applied
- Implement a CipherSeed scheme based on aezeed that can be encoded using the Mnemonic seed words
- Integrate the new CipherSeed into the KeyManagers
- Update Wallet backend and Clients to use the new CipherSeeds

Motivation and Context
---
The CipherSeed scheme has three main benefits
- It contains the seed birthday which means we perform recoveries more efficiently and not scan the whole blockchain
- It contains a checksum to verify the seed phrase is correct
- It can be encrypted with a passphrase and decrypted and authenticated. We don’t current’y use a passphrase on the seeds. That will be future work.

How Has This Been Tested?
---
Test have been updated
  • Loading branch information
philipr-za committed Nov 4, 2021
1 parent 21b5c5f commit ef4f84f
Show file tree
Hide file tree
Showing 34 changed files with 1,054 additions and 898 deletions.
47 changes: 43 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions applications/tari_console_wallet/src/init/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ use rustyline::Editor;

use tari_app_utilities::utilities::create_transport_type;
use tari_common::{exit_codes::ExitCodes, ConfigBootstrap, GlobalConfig};
use tari_common_types::types::PrivateKey;
use tari_comms::{
peer_manager::{Peer, PeerFeatures},
types::CommsSecretKey,
NodeIdentity,
};
use tari_comms_dht::{store_forward::SafConfig, DbConnectionUrl, DhtConfig};
use tari_core::transactions::CryptoFactories;
use tari_key_manager::cipher_seed::CipherSeed;
use tari_p2p::{
auto_update::AutoUpdateConfig,
initialization::P2pConfig,
Expand Down Expand Up @@ -254,7 +254,7 @@ pub async fn init_wallet(
config: &GlobalConfig,
arg_password: Option<String>,
seed_words_file_name: Option<PathBuf>,
recovery_master_key: Option<PrivateKey>,
recovery_seed: Option<CipherSeed>,
shutdown_signal: ShutdownSignal,
) -> Result<WalletSqlite, ExitCodes> {
fs::create_dir_all(
Expand Down Expand Up @@ -411,7 +411,7 @@ pub async fn init_wallet(
output_manager_backend,
contacts_backend,
shutdown_signal,
recovery_master_key.clone(),
recovery_seed.clone(),
)
.await
.map_err(|e| {
Expand Down Expand Up @@ -453,7 +453,7 @@ pub async fn init_wallet(

debug!(target: LOG_TARGET, "Wallet encrypted.");

if interactive && recovery_master_key.is_none() {
if interactive && recovery_seed.is_none() {
match confirm_seed_words(&mut wallet).await {
Ok(()) => {
print!("\x1Bc"); // Clear the screen
Expand Down
19 changes: 8 additions & 11 deletions applications/tari_console_wallet/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#![deny(unreachable_patterns)]
#![deny(unknown_lints)]
#![recursion_limit = "1024"]
use crate::{recovery::get_private_key_from_seed_words, wallet_modes::WalletModeConfig};
use crate::{recovery::get_seed_from_seed_words, wallet_modes::WalletModeConfig};
use init::{
boot,
change_password,
Expand All @@ -24,7 +24,7 @@ use recovery::prompt_private_key_from_seed_words;
use std::{env, process};
use tari_app_utilities::{consts, initialization::init_configuration};
use tari_common::{configuration::bootstrap::ApplicationType, exit_codes::ExitCodes, ConfigBootstrap};
use tari_common_types::types::PrivateKey;
use tari_key_manager::cipher_seed::CipherSeed;
use tari_shutdown::Shutdown;
use tracing_subscriber::{layer::SubscriberExt, Registry};
use wallet_modes::{command_mode, grpc_mode, recovery_mode, script_mode, tui_mode, WalletMode};
Expand Down Expand Up @@ -89,7 +89,7 @@ fn main_inner() -> Result<(), ExitCodes> {
// check for recovery based on existence of wallet file
let mut boot_mode = boot(&bootstrap, &global_config)?;

let recovery_master_key: Option<PrivateKey> = get_recovery_master_key(boot_mode, &bootstrap)?;
let recovery_seed: Option<CipherSeed> = get_recovery_seed(boot_mode, &bootstrap)?;

if bootstrap.init {
info!(target: LOG_TARGET, "Default configuration created. Done.");
Expand All @@ -112,7 +112,7 @@ fn main_inner() -> Result<(), ExitCodes> {
&global_config,
arg_password,
seed_words_file_name,
recovery_master_key,
recovery_seed,
shutdown_signal,
))?;

Expand Down Expand Up @@ -165,24 +165,21 @@ fn main_inner() -> Result<(), ExitCodes> {
result
}

fn get_recovery_master_key(
boot_mode: WalletBoot,
bootstrap: &ConfigBootstrap,
) -> Result<Option<PrivateKey>, ExitCodes> {
fn get_recovery_seed(boot_mode: WalletBoot, bootstrap: &ConfigBootstrap) -> Result<Option<CipherSeed>, ExitCodes> {
if matches!(boot_mode, WalletBoot::Recovery) {
let private_key = if bootstrap.seed_words.is_some() {
let seed = if bootstrap.seed_words.is_some() {
let seed_words: Vec<String> = bootstrap
.seed_words
.clone()
.unwrap()
.split_whitespace()
.map(|v| v.to_string())
.collect();
get_private_key_from_seed_words(seed_words)?
get_seed_from_seed_words(seed_words)?
} else {
prompt_private_key_from_seed_words()?
};
Ok(Some(private_key))
Ok(Some(seed))
} else {
Ok(None)
}
Expand Down
22 changes: 11 additions & 11 deletions applications/tari_console_wallet/src/recovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,8 @@ use futures::FutureExt;
use log::*;
use rustyline::Editor;
use tari_common::exit_codes::ExitCodes;
use tari_common_types::types::PrivateKey;
use tari_crypto::tari_utilities::hex::Hex;
use tari_key_manager::mnemonic::to_secretkey;
use tari_key_manager::mnemonic::Mnemonic;
use tari_shutdown::Shutdown;
use tari_wallet::{
storage::sqlite_db::WalletSqliteDatabase,
Expand All @@ -36,12 +35,13 @@ use tari_wallet::{
};

use crate::wallet_modes::PeerConfig;
use tari_key_manager::cipher_seed::CipherSeed;
use tokio::sync::broadcast;

pub const LOG_TARGET: &str = "wallet::recovery";

/// Prompt the user to input their seed words in a single line.
pub fn prompt_private_key_from_seed_words() -> Result<PrivateKey, ExitCodes> {
pub fn prompt_private_key_from_seed_words() -> Result<CipherSeed, ExitCodes> {
debug!(target: LOG_TARGET, "Prompting for seed words.");
let mut rl = Editor::<()>::new();

Expand All @@ -52,8 +52,8 @@ pub fn prompt_private_key_from_seed_words() -> Result<PrivateKey, ExitCodes> {
let input = rl.readline(">> ").map_err(|e| ExitCodes::IOError(e.to_string()))?;
let seed_words: Vec<String> = input.split_whitespace().map(str::to_string).collect();

match to_secretkey(&seed_words) {
Ok(key) => break Ok(key),
match CipherSeed::from_mnemonic(&seed_words, None) {
Ok(seed) => break Ok(seed),
Err(e) => {
debug!(target: LOG_TARGET, "MnemonicError parsing seed words: {}", e);
println!("Failed to parse seed words! Did you type them correctly?");
Expand All @@ -63,14 +63,14 @@ pub fn prompt_private_key_from_seed_words() -> Result<PrivateKey, ExitCodes> {
}
}

/// Return secret key matching the seed words.
pub fn get_private_key_from_seed_words(seed_words: Vec<String>) -> Result<PrivateKey, ExitCodes> {
debug!(target: LOG_TARGET, "Return secret key matching the provided seed words");
match to_secretkey(&seed_words) {
Ok(key) => Ok(key),
/// Return seed matching the seed words.
pub fn get_seed_from_seed_words(seed_words: Vec<String>) -> Result<CipherSeed, ExitCodes> {
debug!(target: LOG_TARGET, "Return seed derived from the provided seed words");
match CipherSeed::from_mnemonic(&seed_words, None) {
Ok(seed) => Ok(seed),
Err(e) => {
let err_msg = format!("MnemonicError parsing seed words: {}", e);
debug!(target: LOG_TARGET, "{}", err_msg);
warn!(target: LOG_TARGET, "{}", err_msg);
Err(ExitCodes::RecoveryError(err_msg))
},
}
Expand Down
11 changes: 8 additions & 3 deletions base_layer/key_manager/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,16 @@ edition = "2018"

[dependencies]
tari_crypto = { git = "https://github.com/tari-project/tari-crypto.git", branch = "main" }

arrayvec = "0.7.1"
argon2 = { version = "0.2", features = ["std"] }
blake2 = "0.9.1"
chacha20 = "0.7.1"
chrono = { version = "0.4.6", features = ["serde"] }
clear_on_drop = "=0.2.4"
crc32fast = "1.2.1"
rand = "0.8"
digest = "0.9.0"
serde = "1.0.89"
serde_derive = "1.0.89"
serde_json = "1.0.39"
thiserror = "1.0.26"

[dev-dependencies]
Expand Down
Loading

0 comments on commit ef4f84f

Please sign in to comment.