Skip to content

Commit

Permalink
feat(wallet): export seed words
Browse files Browse the repository at this point in the history
  • Loading branch information
mmrrnn committed Aug 8, 2024
1 parent 7eeeaaf commit 365eafa
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 24 deletions.
28 changes: 16 additions & 12 deletions src-tauri/src/internal_wallet.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use anyhow::anyhow;
use keyring::Entry;
use log::{info, warn};
use rand::Rng;
use serde::{Deserialize, Serialize};
Expand All @@ -21,6 +20,7 @@ use tari_core::transactions::key_manager::{
TransactionKeyManagerInterface,
};
use tari_key_manager::mnemonic::{Mnemonic, MnemonicLanguage};
use tari_key_manager::SeedWords;
use tari_utilities::hex::Hex;

const KEY_MANAGER_COMMS_SECRET_KEY_BRANCH_KEY: &str = "comms";
Expand Down Expand Up @@ -67,17 +67,10 @@ impl InternalWallet {
view_key_private_hex: "".to_string(),
seed_words_encrypted_base58: "".to_string(),
spend_public_key_hex: "".to_string(),
passphrase: "".to_string(),
};
let entry = Entry::new("com.tari.universe", "internal_wallet")?;

let passphrase = SafePassword::from(match entry.get_password() {
Ok(pass) => pass,
Err(_) => {
let passphrase = generate_password(32);
entry.set_password(&passphrase)?;
passphrase
}
});
let passphrase = generate_password(32);
let safe_password = SafePassword::from(passphrase.clone());

let seed = CipherSeed::new();
// TODO: Don't print out the seed words lol
Expand All @@ -86,7 +79,7 @@ impl InternalWallet {
dbg!(seed_words.get_word(i).unwrap());
info!(target: LOG_TARGET, "Seed: {}:{}", i+1, seed_words.get_word(i).unwrap());
}
let seed_file = seed.encipher(Some(passphrase))?;
let seed_file = seed.encipher(Some(safe_password))?;
config.seed_words_encrypted_base58 = seed_file.to_base58();

let comms_key_manager = KeyManager::<RistrettoPublicKey, KeyDigest>::from(
Expand Down Expand Up @@ -115,6 +108,7 @@ impl InternalWallet {
config.tari_address_base58 = tari_address.to_base58();
config.view_key_private_hex = view_key_private.to_hex();
config.spend_public_key_hex = comms_pub_key.to_hex();
config.passphrase = passphrase;
Ok((
Self {
tari_address,
Expand All @@ -124,6 +118,15 @@ impl InternalWallet {
))
}

pub fn decrypt_seed_words(&self) -> Result<SeedWords, anyhow::Error> {
let safe_password = SafePassword::from(self.config.passphrase.clone());
let seed_binary = Vec::<u8>::from_base58(&self.config.seed_words_encrypted_base58)
.map_err(|e| anyhow!(e.to_string()))?;
let seed = CipherSeed::from_enciphered_bytes(&seed_binary, Some(safe_password))?;
let seed_words = seed.to_mnemonic(MnemonicLanguage::English, None)?;
Ok(seed_words)
}

pub fn get_view_key(&self) -> String {
self.config.view_key_private_hex.clone()
}
Expand Down Expand Up @@ -154,4 +157,5 @@ pub struct WalletConfig {
view_key_private_hex: String,
spend_public_key_hex: String,
seed_words_encrypted_base58: String,
passphrase: String,
}
23 changes: 22 additions & 1 deletion src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,26 @@ async fn setup_application<'r>(
Ok(())
}

#[tauri::command]
async fn get_seed_words<'r>(
_window: tauri::Window,
_state: tauri::State<'r, UniverseAppState>,
app: tauri::AppHandle,
) -> Result<Vec<String>, String> {
let config_path = app.path_resolver().app_config_dir().unwrap();
let internal_wallet = InternalWallet::load_or_create(config_path)
.await
.map_err(|e| e.to_string())?;
let seed_words = internal_wallet
.decrypt_seed_words()
.map_err(|e| e.to_string())?;
let mut res = vec![];
for i in 0..seed_words.len() {
res.push(seed_words.get_word(i).unwrap().clone());
}
Ok(res)
}

#[tauri::command]
async fn start_mining<'r>(
window: tauri::Window,
Expand Down Expand Up @@ -376,7 +396,8 @@ fn main() {
status,
start_mining,
stop_mining,
set_mode
set_mode,
get_seed_words
])
.build(tauri::generate_context!())
.expect("error while running tauri application");
Expand Down
92 changes: 81 additions & 11 deletions src/containers/SideBar/components/Settings.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useState } from 'react';
import { CgEye, CgEyeAlt, CgCopy } from 'react-icons/cg';
import {
IconButton,
Dialog,
Expand All @@ -10,15 +11,27 @@ import {
Box,
Typography,
Divider,
CircularProgress,
Tooltip,
} from '@mui/material';
import { IoSettingsOutline, IoClose } from 'react-icons/io5';
import { useGetSeedWords } from '../../../hooks/useGetSeedWords';
import truncateString from '../../../utils/truncateString';

const Settings: React.FC = () => {
const [open, setOpen] = useState(false);
const [formState, setFormState] = useState({ field1: '', field2: '' });
const [showSeedWords, setShowSeedWords] = useState(false);
const [isCopyTooltipHidden, setIsCopyTooltipHidden] = useState(true);
const { seedWords, getSeedWords, seedWordsFetched, seedWordsFetching } =
useGetSeedWords();

const handleClickOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
const handleClose = () => {
setOpen(false);
setFormState({ field1: '', field2: '' });
setShowSeedWords(false);
};

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = event.target;
Expand All @@ -36,6 +49,22 @@ const Settings: React.FC = () => {
handleClose();
};

const toggleSeedWordsVisibility = async () => {
if (!seedWordsFetched) {
await getSeedWords();
}
setShowSeedWords((p) => !p);
};

const copySeedWords = async () => {
if (!seedWordsFetched) {
await getSeedWords();
}
setIsCopyTooltipHidden(false);
navigator.clipboard.writeText(seedWords.join(','));
setTimeout(() => setIsCopyTooltipHidden(true), 1000);
};

return (
<>
<IconButton onClick={handleClickOpen}>
Expand All @@ -60,7 +89,48 @@ const Settings: React.FC = () => {
</IconButton>
</Stack>
<Divider />
<Box component="form" onSubmit={handleSubmit}>
<Box my={1}>
<Typography sx={{}} variant="h5">
Seed Words
</Typography>
<Stack flexDirection="row" alignItems="center" gap={1}>
<Typography variant="body2">
{showSeedWords
? truncateString(seedWords.join(','), 50)
: '****************************************************'}
</Typography>
{seedWordsFetching ? (
<CircularProgress size="34px" />
) : (
<>
<IconButton
onClick={toggleSeedWordsVisibility}
>
{showSeedWords ? (
<CgEyeAlt />
) : (
<CgEye />
)}
</IconButton>
<Tooltip
title="Copied!"
placement="top"
open={!isCopyTooltipHidden}
disableFocusListener
disableHoverListener
disableTouchListener
PopperProps={{ disablePortal: true }}
>
<IconButton onClick={copySeedWords}>
<CgCopy />
</IconButton>
</Tooltip>
</>
)}
</Stack>
</Box>
<Box component="form" onSubmit={handleSubmit} my={1}>
<Typography variant="h5">Random</Typography>
<Stack spacing={1} pt={1}>
<TextField
label="Field 1"
Expand All @@ -75,16 +145,16 @@ const Settings: React.FC = () => {
onChange={handleChange}
/>
</Stack>
<Divider />
<DialogActions>
<Button onClick={handleCancel} variant="outlined">
Cancel
</Button>
<Button type="submit" variant="contained">
Submit
</Button>
</DialogActions>
</Box>
<Divider />
<DialogActions>
<Button onClick={handleCancel} variant="outlined">
Cancel
</Button>
<Button type="submit" variant="contained">
Submit
</Button>
</DialogActions>
</DialogContent>
</Dialog>
</>
Expand Down
26 changes: 26 additions & 0 deletions src/hooks/useGetSeedWords.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useCallback, useState } from 'react';
import { invoke } from '@tauri-apps/api/tauri';

export function useGetSeedWords() {
const [seedWords, setSeedWords] = useState<string[]>([]);
const [seedWordsFetching, setSeedWordsFetching] = useState(false);

const getSeedWords = useCallback(async () => {
setSeedWordsFetching(true);
try {
const seedWords = await invoke('get_seed_words') as string[];
setSeedWords(seedWords);
} catch (e) {
console.error('Could not get seed words', e);
} finally {
setSeedWordsFetching(false);
}
}, []);

return {
seedWords,
getSeedWords,
seedWordsFetched: seedWords.length > 0,
seedWordsFetching,
};
}
6 changes: 6 additions & 0 deletions src/utils/truncateString.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const truncateString = (str: string, num: number): string => {
if (str.length <= num) return str;
return str.slice(0, num) + "...";
};

export default truncateString;

0 comments on commit 365eafa

Please sign in to comment.