Skip to content

Commit

Permalink
feat!: add ability to encrypt seed words (#6569)
Browse files Browse the repository at this point in the history
Description
---
allow the use of encrypted seed words.
  • Loading branch information
SWvheerden authored Sep 20, 2024
1 parent 6b5ef7d commit d03d162
Show file tree
Hide file tree
Showing 9 changed files with 71 additions and 14 deletions.
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)?
} 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 @@ -10271,6 +10298,7 @@ mod test {
0,
passphrase,
ptr::null(),
ptr::null(),
alice_network_str,
dns_string,
false,
Expand Down Expand Up @@ -10318,6 +10346,7 @@ mod test {
0,
passphrase,
ptr::null(),
ptr::null(),
alice_network_str,
dns_string,
false,
Expand Down Expand Up @@ -10435,6 +10464,7 @@ mod test {
0,
passphrase,
ptr::null(),
ptr::null(),
network_str,
dns_string,
false,
Expand Down Expand Up @@ -10566,9 +10596,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 @@ -10607,7 +10637,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 @@ -10617,12 +10647,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 @@ -10663,6 +10693,7 @@ mod test {
0,
passphrase,
ptr::null(),
ptr::null(),
network_str,
dns_string,
false,
Expand Down Expand Up @@ -10730,6 +10761,7 @@ mod test {
0,
0,
passphrase,
ptr::null(),
seed_words,
network_str,
dns_string,
Expand Down Expand Up @@ -10811,6 +10843,7 @@ mod test {
0,
passphrase,
ptr::null(),
ptr::null(),
network_str,
dns_string,
false,
Expand Down Expand Up @@ -10989,6 +11022,7 @@ mod test {
0,
passphrase,
ptr::null(),
ptr::null(),
network_str,
dns_string,
false,
Expand Down Expand Up @@ -11128,6 +11162,7 @@ mod test {
0,
passphrase,
ptr::null(),
ptr::null(),
network_str,
dns_string,
false,
Expand Down Expand Up @@ -11347,6 +11382,7 @@ mod test {
0,
passphrase,
ptr::null(),
ptr::null(),
network_str,
dns_string,
false,
Expand Down Expand Up @@ -11574,6 +11610,7 @@ mod test {
0,
passphrase,
ptr::null(),
ptr::null(),
network_str,
dns_string,
false,
Expand Down Expand Up @@ -11835,6 +11872,7 @@ mod test {
0,
passphrase,
ptr::null(),
ptr::null(),
network_str,
dns_string,
false,
Expand Down Expand Up @@ -12216,6 +12254,7 @@ mod test {
0,
passphrase,
ptr::null(),
ptr::null(),
alice_network_str,
dns_string,
false,
Expand Down Expand Up @@ -12281,6 +12320,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

0 comments on commit d03d162

Please sign in to comment.