Skip to content

Commit

Permalink
feat: add FFI function to create seed words from Mbase58 encrypted ci…
Browse files Browse the repository at this point in the history
…pher seed (#6620)

Description
---
add FFI function to create seed words from Mbase58 encrypted cipher seed

Motivation and Context
---
Mobile clients need to get the seed words out so that they can recover
the wallet based on an encrypted cipher seed.

How Has This Been Tested?
---
unit test
  • Loading branch information
SWvheerden authored Oct 10, 2024
1 parent 96941a4 commit 845a9b2
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 7 deletions.
28 changes: 23 additions & 5 deletions applications/minotari_console_wallet/src/automation/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,14 @@ use tari_crypto::{
ristretto::{pedersen::PedersenCommitment, RistrettoSecretKey},
};
use tari_key_manager::{
cipher_seed::CipherSeed,
key_manager_service::{KeyId, KeyManagerInterface},
SeedWords,
};
use tari_p2p::{auto_update::AutoUpdateConfig, peer_seeds::SeedPeer, PeerSeedsConfig};
use tari_script::{push_pubkey_script, CheckSigSchnorrSignature};
use tari_shutdown::Shutdown;
use tari_utilities::{hex::Hex, ByteArray, SafePassword};
use tari_utilities::{encoding::Base58, hex::Hex, ByteArray, SafePassword};
use tokio::{
sync::{broadcast, mpsc},
time::{sleep, timeout},
Expand Down Expand Up @@ -2485,15 +2486,32 @@ pub async fn command_runner(
.join("temp");
println!("saving temp wallet in: {:?}", temp_path);
{
let seed_words = SeedWords::from_str(args.seed_words.as_str())
.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 seed = match (!args.seed_words.is_empty(), !args.cipher_seed.is_empty()) {
(true, false) => {
let seed_words = SeedWords::from_str(args.seed_words.as_str())
.map_err(|e| CommandError::General(e.to_string()))?;

get_seed_from_seed_words(&seed_words, passphrase)
.map_err(|e| CommandError::General(e.to_string()))?
},
(false, true) => {
let bytes = Vec::<u8>::from_base58(args.cipher_seed.as_str())
.map_err(|e| CommandError::General(e.to_string()))?;
CipherSeed::from_enciphered_bytes(&bytes, passphrase)
.map_err(|e| CommandError::General(e.to_string()))?
},
(_, _) => {
return Err(CommandError::General(
"Either seed words or cipher seed must be provided".to_string(),
))
},
};

let wallet_type = WalletType::DerivedKeys;
let password = SafePassword::from("password".to_string());
let shutdown = Shutdown::new();
Expand Down
4 changes: 3 additions & 1 deletion applications/minotari_console_wallet/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -391,9 +391,11 @@ pub struct ExportViewKeyAndSpendKeyArgs {

#[derive(Debug, Args, Clone)]
pub struct ImportPaperWalletArgs {
#[clap(short, long)]
#[clap(short, long, default_value = "")]
pub seed_words: String,
#[clap(short, long, default_value = "")]
pub cipher_seed: String,
#[clap(short, long, default_value = "")]
pub passphrase: String,
}

Expand Down
111 changes: 110 additions & 1 deletion base_layer/wallet_ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,11 @@ use tari_crypto::{
keys::{PublicKey as PublicKeyTrait, SecretKey},
tari_utilities::{ByteArray, Hidden},
};
use tari_key_manager::{cipher_seed::CipherSeed, mnemonic::MnemonicLanguage, SeedWords};
use tari_key_manager::{
cipher_seed::CipherSeed,
mnemonic::{Mnemonic, MnemonicLanguage},
SeedWords,
};
use tari_p2p::{
auto_update::AutoUpdateConfig,
transport::MemoryTransportConfig,
Expand All @@ -177,6 +181,7 @@ use tari_p2p::{
use tari_script::TariScript;
use tari_shutdown::Shutdown;
use tari_utilities::{
encoding::Base58,
hex,
hex::{Hex, HexError},
SafePassword,
Expand Down Expand Up @@ -2786,6 +2791,82 @@ pub unsafe extern "C" fn seed_words_create() -> *mut TariSeedWords {
Box::into_raw(Box::new(TariSeedWords(seed_words)))
}

/// Create an instance of TariSeedWords from optionally encrypted cipher seed
///
/// ## Arguments
/// `cipher_bytes`: base58 encoded string pointer of the cipher bytes
/// `passphrase`: optional passphrase to decrypt the cipher bytes
/// `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions
/// as an out parameter.
///
/// ## Returns
/// `TariSeedWords` - Returns an TariSeedWords instance
///
/// # Safety
/// Tari seed words need to be destroyed
#[no_mangle]
pub unsafe extern "C" fn seed_words_create_from_cipher(
cipher_bytes: *const c_char,
passphrase: *const c_char,
error_out: *mut c_int,
) -> *mut TariSeedWords {
let passphrase = if passphrase.is_null() {
None
} else {
match CStr::from_ptr(passphrase).to_str() {
Ok(v) => Some(SafePassword::from(v.to_owned())),
_ => {
let mut error = LibWalletError::from(InterfaceError::PointerError("passphrase".to_string())).code;
ptr::swap(error_out, &mut error as *mut c_int);
return ptr::null_mut();
},
}
};
if cipher_bytes.is_null() {
let mut error = LibWalletError::from(InterfaceError::NullError("cipher_bytes".to_string())).code;
ptr::swap(error_out, &mut error as *mut c_int);
return ptr::null_mut();
}
let base_58_cipher = match CStr::from_ptr(cipher_bytes).to_str() {
Ok(v) => v.to_owned(),
_ => {
let mut error = LibWalletError::from(InterfaceError::PointerError("cipher_bytes".to_string())).code;
ptr::swap(error_out, &mut error as *mut c_int);
return ptr::null_mut();
},
};
let bytes = match Vec::<u8>::from_base58(&base_58_cipher) {
Ok(v) => v,
Err(_) => {
// code for invalid cipher bytes
let mut error = 420;
ptr::swap(error_out, &mut error as *mut c_int);
return ptr::null_mut();
},
};
let cipher = match CipherSeed::from_enciphered_bytes(&bytes, passphrase) {
Ok(v) => v,
Err(_) => {
// code for invalid cipher bytes
let mut error = 421;
ptr::swap(error_out, &mut error as *mut c_int);
return ptr::null_mut();
},
};

let seed_words = match cipher.to_mnemonic(MnemonicLanguage::English, None) {
Ok(v) => v,
Err(_) => {
// code for invalid cipher bytes
let mut error = 420;
ptr::swap(error_out, &mut error as *mut c_int);
return ptr::null_mut();
},
};

Box::into_raw(Box::new(TariSeedWords(seed_words)))
}

/// Create a TariSeedWords instance containing the entire mnemonic wordlist for the requested language
///
/// ## Arguments
Expand Down Expand Up @@ -9479,6 +9560,7 @@ mod test {
use tari_p2p::initialization::MESSAGING_PROTOCOL_ID;
use tari_script::script;
use tari_test_utils::random;
use tari_utilities::encoding::Base58;
use tempfile::tempdir;

use crate::*;
Expand Down Expand Up @@ -9847,6 +9929,33 @@ mod test {
}
}

#[test]
#[allow(clippy::cast_possible_truncation)]
fn test_seed_words_create() {
unsafe {
let cipher = CipherSeed::new();
let ciper_bytes = cipher.encipher(None).unwrap();
let cipher_string = ciper_bytes.to_base58();

let cipher_cstring = CString::new(cipher_string).unwrap();
let cipher_char: *const c_char = CString::into_raw(cipher_cstring) as *const c_char;
let mut error = 0;
let error_ptr = &mut error as *mut c_int;
let seed_words = cipher.to_mnemonic(MnemonicLanguage::English, None).unwrap();

let ffi_seed_words = seed_words_create_from_cipher(cipher_char, ptr::null(), error_ptr);
assert_eq!(*error_ptr, 0, "No error expected");

for i in 0..seed_words.len() {
let ffi_seed_word = CString::from_raw(seed_words_get_at(ffi_seed_words, i as c_uint, error_ptr));
assert_eq!(*error_ptr, 0, "No error expected");
let seed_word = seed_words.get_word(i).unwrap();
assert_eq!(ffi_seed_word.to_str().unwrap().to_string(), seed_word.to_string());
}
seed_words_destroy(ffi_seed_words);
}
}

#[test]
fn test_emoji_set() {
unsafe {
Expand Down
19 changes: 19 additions & 0 deletions base_layer/wallet_ffi/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -1496,6 +1496,25 @@ void output_features_destroy(TariOutputFeatures *output_features);
*/
struct TariSeedWords *seed_words_create(void);

/**
* Create an instance of TariSeedWords from optionally encrypted cipher seed
*
* ## Arguments
* `cipher_bytes`: base58 encoded string pointer of the cipher bytes
* `passphrase`: optional passphrase to decrypt the cipher bytes
* `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions
* as an out parameter.
*
* ## Returns
* `TariSeedWords` - Returns an TariSeedWords instance
*
* # Safety
* Tari seed words need to be destroyed
*/
struct TariSeedWords *seed_words_create_from_cipher(const char *cipher_bytes,
const char *passphrase,
int *error_out);

/**
* Create a TariSeedWords instance containing the entire mnemonic wordlist for the requested language
*
Expand Down

0 comments on commit 845a9b2

Please sign in to comment.