Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: add ability to encrypt seed words #6569

Merged
merged 5 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1836,8 +1836,13 @@ pub async fn command_runner(
{
let seed_words = SeedWords::from_str(args.seed_words.as_str())
.map_err(|e| CommandError::General(e.to_string()))?;
let seed =
get_seed_from_seed_words(&seed_words).map_err(|e| CommandError::General(e.to_string()))?;
let passphrase = if args.passphrase.is_empty() {
None
} else {
Some(SafePassword::from(args.passphrase))
};
let seed = get_seed_from_seed_words(&seed_words, passphrase)
.map_err(|e| CommandError::General(e.to_string()))?;
let wallet_type = WalletType::DerivedKeys;
let password = SafePassword::from("password".to_string());
let shutdown = Shutdown::new();
Expand Down
2 changes: 2 additions & 0 deletions applications/minotari_console_wallet/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,8 @@ pub struct ExportViewKeyAndSpendKeyArgs {
pub struct ImportPaperWalletArgs {
#[clap(short, long)]
pub seed_words: String,
#[clap(short, long, default_value = "")]
pub passphrase: String,
}

#[derive(Debug, Args, Clone)]
Expand Down
2 changes: 1 addition & 1 deletion applications/minotari_console_wallet/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ fn get_recovery_seed(
) -> Result<Option<CipherSeed>, ExitError> {
if matches!(boot_mode, WalletBoot::Recovery) && !matches!(wallet_type, Some(WalletType::Ledger(_))) {
let seed = if let Some(ref seed_words) = cli.seed_words {
get_seed_from_seed_words(seed_words)?
get_seed_from_seed_words(seed_words, None)?
hansieodendaal marked this conversation as resolved.
Show resolved Hide resolved
} else {
prompt_private_key_from_seed_words()?
};
Expand Down
9 changes: 6 additions & 3 deletions applications/minotari_console_wallet/src/recovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ use tari_common::exit_codes::{ExitCode, ExitError};
use tari_crypto::tari_utilities::Hidden;
use tari_key_manager::{cipher_seed::CipherSeed, mnemonic::Mnemonic, SeedWords};
use tari_shutdown::Shutdown;
use tari_utilities::hex::Hex;
use tari_utilities::{hex::Hex, SafePassword};
use tokio::{runtime::Runtime, sync::broadcast};
use zeroize::{Zeroize, Zeroizing};

Expand Down Expand Up @@ -77,9 +77,12 @@ pub fn prompt_private_key_from_seed_words() -> Result<CipherSeed, ExitError> {
}

/// Return seed matching the seed words.
pub fn get_seed_from_seed_words(seed_words: &SeedWords) -> Result<CipherSeed, ExitError> {
pub fn get_seed_from_seed_words(
seed_words: &SeedWords,
passphrase: Option<SafePassword>,
) -> Result<CipherSeed, ExitError> {
debug!(target: LOG_TARGET, "Return seed derived from the provided seed words");
match CipherSeed::from_mnemonic(seed_words, None) {
match CipherSeed::from_mnemonic(seed_words, passphrase) {
Ok(seed) => Ok(seed),
Err(e) => {
let err_msg = format!("MnemonicError parsing seed words: {}", e);
Expand Down
54 changes: 47 additions & 7 deletions base_layer/wallet_ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2944,6 +2944,7 @@ pub unsafe extern "C" fn seed_words_get_at(
/// ## Returns
/// 'c_uchar' - Returns a u8 version of the `SeedWordPushResult` enum indicating whether the word was not a valid seed
/// word, if the push was successful and whether the push was successful and completed the full Seed Phrase.
/// `passphrase` - Optional passphrase to use when generating the seed phrase
/// `seed_words` is only modified in the event of a `SuccessfulPush`.
/// '0' -> InvalidSeedWord
/// '1' -> SuccessfulPush
Expand All @@ -2953,9 +2954,11 @@ pub unsafe extern "C" fn seed_words_get_at(
/// # Safety
/// The ```string_destroy``` method must be called when finished with a string from rust to prevent a memory leak
#[no_mangle]
#[allow(clippy::too_many_lines)]
pub unsafe extern "C" fn seed_words_push_word(
seed_words: *mut TariSeedWords,
word: *const c_char,
passphrase: *const c_char,
error_out: *mut c_int,
) -> c_uchar {
use tari_key_manager::mnemonic::Mnemonic;
Expand Down Expand Up @@ -2984,6 +2987,18 @@ pub unsafe extern "C" fn seed_words_push_word(
},
}
}
let passphrase = if passphrase.is_null() {
None
} else {
match CStr::from_ptr(passphrase).to_str() {
Ok(v) => Some(SafePassword::from(v.to_owned())),
_ => {
error = LibWalletError::from(InterfaceError::PointerError("passphrase".to_string())).code;
ptr::swap(error_out, &mut error as *mut c_int);
return SeedWordPushResult::InvalidObject as u8;
},
}
};

// Check word is from a word list
match MnemonicLanguage::from(&word_string) {
Expand Down Expand Up @@ -3019,7 +3034,7 @@ pub unsafe extern "C" fn seed_words_push_word(
// depending on word added
if MnemonicLanguage::detect_language(&(*seed_words).0).is_ok() {
if (*seed_words).0.len() >= 24 {
if let Err(e) = CipherSeed::from_mnemonic(&(*seed_words).0, None) {
if let Err(e) = CipherSeed::from_mnemonic(&(*seed_words).0, passphrase) {
log::error!(
target: LOG_TARGET,
"Problem building valid private seed from seed phrase: {:?}",
Expand Down Expand Up @@ -5660,6 +5675,7 @@ unsafe fn init_logging(
/// `passphrase` - An optional string that represents the passphrase used to
/// encrypt/decrypt the databases for this wallet. If it is left Null no encryption is used. If the databases have been
/// encrypted then the correct passphrase is required or this function will fail.
/// `seed_passphrase` - an optional string, if present this will derypt the seed words
/// `seed_words` - An optional instance of TariSeedWords, used to create a wallet for recovery purposes.
/// If this is null, then a new master key is created for the wallet.
/// `callback_received_transaction` - The callback function pointer matching the function signature. This will be
Expand Down Expand Up @@ -5749,6 +5765,7 @@ pub unsafe extern "C" fn wallet_create(
num_rolling_log_files: c_uint,
size_per_log_file_bytes: c_uint,
passphrase: *const c_char,
seed_passphrase: *const c_char,
seed_words: *const TariSeedWords,
network_str: *const c_char,
peer_seed_str: *const c_char,
Expand Down Expand Up @@ -5828,10 +5845,20 @@ pub unsafe extern "C" fn wallet_create(
peer_seed
};

let seed_passphrase = if seed_passphrase.is_null() {
None
} else {
let seed_passphrase = CStr::from_ptr(seed_passphrase)
.to_str()
.expect("A non-null seed passphrase should be able to be converted to string")
.to_owned();
Some(SafePassword::from(seed_passphrase))
};

let recovery_seed = if seed_words.is_null() {
None
} else {
match CipherSeed::from_mnemonic(&(*seed_words).0, None) {
match CipherSeed::from_mnemonic(&(*seed_words).0, seed_passphrase) {
Ok(seed) => Some(seed),
Err(e) => {
error!(target: LOG_TARGET, "Mnemonic Error for given seed words: {:?}", e);
Expand Down Expand Up @@ -10270,6 +10297,7 @@ mod test {
0,
passphrase,
ptr::null(),
ptr::null(),
alice_network_str,
dns_string,
false,
Expand Down Expand Up @@ -10317,6 +10345,7 @@ mod test {
0,
passphrase,
ptr::null(),
ptr::null(),
alice_network_str,
dns_string,
false,
Expand Down Expand Up @@ -10434,6 +10463,7 @@ mod test {
0,
passphrase,
ptr::null(),
ptr::null(),
network_str,
dns_string,
false,
Expand Down Expand Up @@ -10565,9 +10595,9 @@ mod test {
// Try to wrongfully add a new seed word onto the mnemonic wordlist seed words object
let w = CString::new(mnemonic_wordlist[188]).unwrap();
let w_str: *const c_char = CString::into_raw(w) as *const c_char;
seed_words_push_word(mnemonic_wordlist_ffi, w_str, error_ptr);
seed_words_push_word(mnemonic_wordlist_ffi, w_str, ptr::null(), error_ptr);
assert_eq!(
seed_words_push_word(mnemonic_wordlist_ffi, w_str, error_ptr),
seed_words_push_word(mnemonic_wordlist_ffi, w_str, ptr::null(), error_ptr),
SeedWordPushResult::InvalidObject as u8
);
assert_ne!(error, 0);
Expand Down Expand Up @@ -10606,7 +10636,7 @@ mod test {
let w_str: *const c_char = CString::into_raw(w) as *const c_char;

assert_eq!(
seed_words_push_word(seed_words, w_str, error_ptr),
seed_words_push_word(seed_words, w_str, ptr::null(), error_ptr),
SeedWordPushResult::InvalidSeedWord as u8
);

Expand All @@ -10616,12 +10646,12 @@ mod test {

if count + 1 < 24 {
assert_eq!(
seed_words_push_word(seed_words, w_str, error_ptr),
seed_words_push_word(seed_words, w_str, ptr::null(), error_ptr),
SeedWordPushResult::SuccessfulPush as u8
);
} else {
assert_eq!(
seed_words_push_word(seed_words, w_str, error_ptr),
seed_words_push_word(seed_words, w_str, ptr::null(), error_ptr),
SeedWordPushResult::SeedPhraseComplete as u8
);
}
Expand Down Expand Up @@ -10662,6 +10692,7 @@ mod test {
0,
passphrase,
ptr::null(),
ptr::null(),
network_str,
dns_string,
false,
Expand Down Expand Up @@ -10729,6 +10760,7 @@ mod test {
0,
0,
passphrase,
ptr::null(),
seed_words,
network_str,
dns_string,
Expand Down Expand Up @@ -10810,6 +10842,7 @@ mod test {
0,
passphrase,
ptr::null(),
ptr::null(),
network_str,
dns_string,
false,
Expand Down Expand Up @@ -10988,6 +11021,7 @@ mod test {
0,
passphrase,
ptr::null(),
ptr::null(),
network_str,
dns_string,
false,
Expand Down Expand Up @@ -11127,6 +11161,7 @@ mod test {
0,
passphrase,
ptr::null(),
ptr::null(),
network_str,
dns_string,
false,
Expand Down Expand Up @@ -11346,6 +11381,7 @@ mod test {
0,
passphrase,
ptr::null(),
ptr::null(),
network_str,
dns_string,
false,
Expand Down Expand Up @@ -11573,6 +11609,7 @@ mod test {
0,
passphrase,
ptr::null(),
ptr::null(),
network_str,
dns_string,
false,
Expand Down Expand Up @@ -11834,6 +11871,7 @@ mod test {
0,
passphrase,
ptr::null(),
ptr::null(),
network_str,
dns_string,
false,
Expand Down Expand Up @@ -12215,6 +12253,7 @@ mod test {
0,
passphrase,
ptr::null(),
ptr::null(),
alice_network_str,
dns_string,
false,
Expand Down Expand Up @@ -12280,6 +12319,7 @@ mod test {
0,
passphrase,
ptr::null(),
ptr::null(),
bob_network_str,
dns_string,
false,
Expand Down
4 changes: 4 additions & 0 deletions base_layer/wallet_ffi/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -1564,6 +1564,7 @@ char *seed_words_get_at(struct TariSeedWords *seed_words,
* ## Returns
* 'c_uchar' - Returns a u8 version of the `SeedWordPushResult` enum indicating whether the word was not a valid seed
* word, if the push was successful and whether the push was successful and completed the full Seed Phrase.
* `passphrase` - Optional passphrase to use when generating the seed phrase
* `seed_words` is only modified in the event of a `SuccessfulPush`.
* '0' -> InvalidSeedWord
* '1' -> SuccessfulPush
Expand All @@ -1575,6 +1576,7 @@ char *seed_words_get_at(struct TariSeedWords *seed_words,
*/
unsigned char seed_words_push_word(struct TariSeedWords *seed_words,
const char *word,
const char *passphrase,
int *error_out);

/**
Expand Down Expand Up @@ -2864,6 +2866,7 @@ TariPublicKey *public_keys_get_at(const struct TariPublicKeys *public_keys,
* `passphrase` - An optional string that represents the passphrase used to
* encrypt/decrypt the databases for this wallet. If it is left Null no encryption is used. If the databases have been
* encrypted then the correct passphrase is required or this function will fail.
* `seed_passphrase` - an optional string, if present this will derypt the seed words
* `seed_words` - An optional instance of TariSeedWords, used to create a wallet for recovery purposes.
* If this is null, then a new master key is created for the wallet.
* `callback_received_transaction` - The callback function pointer matching the function signature. This will be
Expand Down Expand Up @@ -2950,6 +2953,7 @@ struct TariWallet *wallet_create(TariCommsConfig *config,
unsigned int num_rolling_log_files,
unsigned int size_per_log_file_bytes,
const char *passphrase,
const char *seed_passphrase,
const struct TariSeedWords *seed_words,
const char *network_str,
const char *peer_seed_str,
Expand Down
1 change: 1 addition & 0 deletions integration_tests/src/ffi/ffi_import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,7 @@ extern "C" {
num_rolling_log_files: c_uint,
size_per_log_file_bytes: c_uint,
passphrase: *const c_char,
seed_passphrase: *const c_char,
seed_words: *const TariSeedWords,
network_str: *const c_char,
peer_seed_str: *const c_char,
Expand Down
2 changes: 2 additions & 0 deletions integration_tests/src/ffi/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

use std::{
ffi::CString,
ptr,
ptr::null_mut,
sync::{Arc, Mutex},
};
Expand Down Expand Up @@ -185,6 +186,7 @@ impl Wallet {
50,
104857600, // 100 MB
CString::new("kensentme").unwrap().into_raw(),
ptr::null(),
seed_words_ptr,
CString::new("localnet").unwrap().into_raw(),
CString::new("").unwrap().into_raw(),
Expand Down
2 changes: 1 addition & 1 deletion integration_tests/tests/features/WalletFFI.feature
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ Feature: Wallet FFI
Then wallet RECEIVER has at least 1 transactions that are all TRANSACTION_STATUS_MINED_CONFIRMED and not cancelled
And I stop ffi wallet FFI_WALLET

@critical
@critical @pie
Scenario: As a client I want to receive a one-sided transaction
Given I have a seed node SEED
When I have a base node BASE1 connected to all seed nodes
Expand Down
Loading