Skip to content

Commit

Permalink
Add seed recovery functionality to pre-login screen
Browse files Browse the repository at this point in the history
  • Loading branch information
michaeltout committed Aug 19, 2024
1 parent 3cf65f9 commit 44e6d04
Show file tree
Hide file tree
Showing 8 changed files with 290 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ import {
import { NavigationActions } from '@react-navigation/compat';
import { connect } from 'react-redux';
import QRCode from 'react-native-qrcode-svg';
import Styles from '../../../../styles/index'
import Colors from "../../../../globals/colors";
import Styles from '../../styles/index'
import Colors from "../../globals/colors";
import { CommonActions } from '@react-navigation/native';
import { DLIGHT_PRIVATE, ELECTRUM, ETH, WYRE_SERVICE } from "../../../../utils/constants/intervalConstants";
import { DLIGHT_PRIVATE, ELECTRUM, ETH, WYRE_SERVICE } from "../../utils/constants/intervalConstants";
import { Card, Paragraph, Title, Button } from 'react-native-paper'
import { deriveKeyPair, dlightSeedToBytes, isSeedPhrase } from "../../../../utils/keys";
import { createAlert } from "../../../../actions/actions/alert/dispatchers/alert";
import { coinsList } from "../../../../utils/CoinData/CoinsList";
import { deriveKeyPair, dlightSeedToBytes, isSeedPhrase } from "../../utils/keys";
import { createAlert } from "../../actions/actions/alert/dispatchers/alert";
import { coinsList } from "../../utils/CoinData/CoinsList";

class DisplaySeed extends Component {
constructor() {
Expand All @@ -31,7 +31,8 @@ class DisplaySeed extends Component {
dualSameSeed: false,
derivedKeys: {},
toggleDerivedKey: {},
fetchingDerivedKey: {}
fetchingDerivedKey: {},
completeOnBack: false,
};

this.SEED_NAMES = {
Expand Down Expand Up @@ -65,6 +66,15 @@ class DisplaySeed extends Component {
.fromDeleteAccount,
});
}

if (
data &&
data.completeOnBack
) {
this.setState({
completeOnBack: data.completeOnBack,
});
}
}

resetToScreen = () => {
Expand Down Expand Up @@ -114,8 +124,9 @@ class DisplaySeed extends Component {
}
}

// Method to derive the key from seed. Replace this with your actual implementation
deriveKeyFromSeed = async (seed, key, coinObj) => {
const { data } = this.props.route.params;

switch (key) {
case DLIGHT_PRIVATE:
return Buffer.from(await dlightSeedToBytes(seed)).toString('hex');
Expand All @@ -124,22 +135,23 @@ class DisplaySeed extends Component {
seed,
coinsList.ETH,
key,
this.props.activeAccount.keyDerivationVersion,
data.keyDerivationVersion,
)).privKey;
case ELECTRUM:
return (await deriveKeyPair(
seed,
coinObj,
key,
this.props.activeAccount.keyDerivationVersion,
data.keyDerivationVersion,
)).privKey;
default:
return seed
}
}

render() {
const { seeds, toggleDerivedKey, fetchingDerivedKey, derivedKeys } = this.state;
const { seeds, toggleDerivedKey, fetchingDerivedKey, derivedKeys, completeOnBack } = this.state;
const { data } = this.props.route.params;

return (
<View style={Styles.defaultRoot}>
Expand All @@ -163,28 +175,30 @@ class DisplaySeed extends Component {
<View style={Styles.fullWidthFlexCenterBlock}>
<QRCode value={displayedValue} size={250} />
</View>
{
((key === DLIGHT_PRIVATE && isSeedPhrase(seeds[key])) ||
key === ETH ||
key === ELECTRUM) && (
<Button onPress={() => this.toggleDerived(key, coinsList.VRSC)}>
{fetchingDerivedKey[key]
? 'Fetching...'
: isToggleOn
? 'Show Seed'
: key === ELECTRUM
? 'Show Derived Key (VRSC)'
: 'Show Derived Key'}
</Button>
)
}
{
!isToggleOn && !fetchingDerivedKey[key] && key === ELECTRUM && (
<Button onPress={() => this.toggleDerived(key, coinsList.BTC)}>
{'Show Derived Key (BTC)'}
</Button>
)
}
{data.showDerivedKeys && <>
{
((key === DLIGHT_PRIVATE && isSeedPhrase(seeds[key])) ||
key === ETH ||
key === ELECTRUM) && (
<Button onPress={() => this.toggleDerived(key, coinsList.VRSC)}>
{fetchingDerivedKey[key]
? 'Fetching...'
: isToggleOn
? 'Show Seed'
: key === ELECTRUM
? 'Show Derived Key (VRSC)'
: 'Show Derived Key'}
</Button>
)
}
{
!isToggleOn && !fetchingDerivedKey[key] && key === ELECTRUM && (
<Button onPress={() => this.toggleDerived(key, coinsList.BTC)}>
{'Show Derived Key (BTC)'}
</Button>
)
}
</>}
</Card.Content>
</Card>
</View>
Expand All @@ -194,12 +208,16 @@ class DisplaySeed extends Component {
</ScrollView>
<View style={Styles.highFooterContainer}>
<View style={Styles.standardWidthSpaceBetweenBlock}>
<Button color={Colors.warningButtonColor} onPress={this.back}>
{"Back"}
</Button>
<Button color={Colors.linkButtonColor} onPress={this.resetToScreen}>
{this.state.fromDeleteAccount ? "CONTINUE" : "HOME"}
<Button style={completeOnBack ? {
flex: 1
} : {}} color={completeOnBack ? Colors.primaryColor : Colors.warningButtonColor} onPress={this.back}>
{completeOnBack ? "Done" : "Back"}
</Button>
{!completeOnBack && (
<Button color={Colors.linkButtonColor} onPress={this.resetToScreen}>
{this.state.fromDeleteAccount ? "CONTINUE" : "HOME"}
</Button>
)}
</View>
</View>
</View>
Expand Down
14 changes: 14 additions & 0 deletions src/containers/Login/Login.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
} from '../../utils/constants/sendModal';
import {useSelector} from 'react-redux';
import TallButton from '../../components/LargerButton';
import SignedOutDropdown from '../SignedOutDropdown/SignedOutDropdown';

const {height} = Dimensions.get('window');

Expand All @@ -32,6 +33,9 @@ const Login = props => {
const authModalUsed = useSelector(
state => state.authentication.authModalUsed,
);
const modalVisible = useSelector(
state => state.sendModal.visible,
);
const accounts = useSelector(state => state.authentication.accounts);

openAuthModal = ignoreDefault => {
Expand Down Expand Up @@ -67,12 +71,22 @@ const Login = props => {
props.navigation.navigate('CreateProfile');
};

handleRecoverSeed = () => {
props.navigation.navigate("RecoverSeeds");
};

return (
<SafeAreaView
style={{
backgroundColor: Colors.secondaryColor,
...Styles.focalCenter,
}}>
<View style={{ position: "absolute", width: "100%", height: "100%" }}>
{!modalVisible && <SignedOutDropdown
handleRecoverSeed={() => handleRecoverSeed()}
hasAccount={true}
/>}
</View>
<VerusLogo
width={180}
height={'15%'}
Expand Down
112 changes: 112 additions & 0 deletions src/containers/RecoverSeeds/RecoverSeedsSelectAccount.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet, ScrollView, SafeAreaView } from 'react-native';
import { useSelector } from 'react-redux';
import { Card, Button, List, IconButton } from 'react-native-paper';
import styles from '../../styles';
import Colors from '../../globals/colors';
import TallButton from '../../components/LargerButton';
import PasswordCheck from '../../components/PasswordCheck';
import { createAlert } from '../../actions/actions/alert/dispatchers/alert';
import { checkPinForUser } from '../../utils/asyncStore/authDataStorage';
import { canShowSeed } from '../../actions/actions/channels/dlight/dispatchers/AlertManager';
import { CommonActions } from '@react-navigation/native';

const RecoverSeedsSelectAccount = ({ navigation }) => {
const accounts = useSelector(state => state.authentication.accounts);
const [numSeeds, setNumSeeds] = useState({});
const [passwordDialogOpen, setPasswordDialogOpen] = useState(false);
const [passwordDialogTitle, setPasswordDialogTitle] = useState('');
const [passwordDialogAccount, setPasswordDialogAccount] = useState(null);

useEffect(() => {
for (let account of accounts) {
setNumSeeds(prevState => ({
...prevState,
[account.accountHash]: Object.values(account.encryptedKeys).filter(x => x != null).length,
}));
}
}, [accounts]);

const handleCardPress = async (account) => {
if (await canShowSeed()) {
setPasswordDialogAccount(account);
setPasswordDialogOpen(true);
setPasswordDialogTitle(`Enter your password for "${account.id}"`);
}
};

const onPasswordResult = async (result) => {
if (result.valid) {
setPasswordDialogOpen(false);

try {
const seeds = await checkPinForUser(result.password, passwordDialogAccount.id);

navigation.dispatch(CommonActions.reset({
index: 0,
routes: [{ name: 'DisplaySeed', params: { data: { seeds, showDerivedKeys: true, keyDerivationVersion: passwordDialogAccount.keyDerivationVersion, completeOnBack: true } } }],
}));
} catch(e) {
createAlert("Error", "Failed to retrieve seeds");
}
} else {
createAlert("Authentication Error", "Incorrect password");
}
}

const renderAccountCards = () => {
return accounts.map(account => (
<View style={{ margin: 8 }} key={account.accountHash}>
<Card
onPress={() => handleCardPress(account)}
key={account.accountHash}
style={{ backgroundColor: Colors.primaryColor }}
>
<List.Item
title={account.id}
titleStyle={{
color: Colors.secondaryColor,
fontWeight: '500',
}}
description={`${numSeeds[account.accountHash] ? numSeeds[account.accountHash] : 0} seed${numSeeds[account.accountHash] && numSeeds[account.accountHash] > 1 ? 's' : ''}`}
left={() => <List.Icon color={Colors.secondaryColor} icon="account-circle" />}
descriptionStyle={{ color: Colors.secondaryColor }}
right={() => (
<IconButton name="chevron-right" color={Colors.secondaryColor} size={20} />
)}
/>
</Card>
</View>
));
};

return (
<SafeAreaView style={styles.flexBackground}>
{passwordDialogOpen &&
<PasswordCheck
cancel={() => setPasswordDialogOpen(false)}
submit={(result) => onPasswordResult(result)}
visible={passwordDialogOpen}
title={passwordDialogTitle}
userName={passwordDialogAccount ? passwordDialogAccount.id : ''}
account={passwordDialogAccount}
allowBiometry={true}
/>
}
<Text style={{ ...Styles.centralHeader, marginTop: 16 }}>{'Select a Profile'}</Text>
<ScrollView>
{renderAccountCards()}
</ScrollView>
<TallButton
color={Colors.warningButtonColor}
onPress={() => navigation.goBack()}
mode="outlined"
style={{ marginHorizontal: 8 }}
>
{"Cancel"}
</TallButton>
</SafeAreaView>
);
};

export default RecoverSeedsSelectAccount;
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { createStackNavigator } from "@react-navigation/stack";
import { defaultHeaderOptions } from '../../../utils/navigation/header';
import AddCoin from '../../AddCoin/AddCoin'
import CoinDetails from '../../CoinDetails/CoinDetails'
import DisplaySeed from '../../Settings/ProfileSettings/DisplaySeed/DisplaySeed'
import DisplaySeed from '../../DisplaySeed/DisplaySeed'
import SettingsMenus from '../../Settings/SettingsMenus'
import CoinMenus from '../../Coin/CoinMenus'
import VerusPay from '../../VerusPay/VerusPay'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React, { useState } from 'react';
import { createStackNavigator } from "@react-navigation/stack";
import { defaultHeaderOptions } from '../../../utils/navigation/header';
import RecoverSeedsSelectAccount from '../../RecoverSeeds/RecoverSeedsSelectAccount';
import DisplaySeed from '../../DisplaySeed/DisplaySeed';

const RecoverSeedsStack = createStackNavigator();

const RecoverSeedsStackScreens = props => {
return (
<RecoverSeedsStack.Navigator
screenOptions={defaultHeaderOptions}
>
<RecoverSeedsStack.Screen
name="RecoverSeedsSelectAccount"
options={{
headerShown: false,
}}
>
{(_props) => (
<RecoverSeedsSelectAccount
navigation={_props.navigation}
/>
)}
</RecoverSeedsStack.Screen>

<RecoverSeedsStack.Screen
name="DisplaySeed"
component={DisplaySeed}
options={{
headerShown: false,
}}
/>
</RecoverSeedsStack.Navigator>
);
};

export default RecoverSeedsStackScreens;
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, {useEffect, useState} from 'react';
import {createStackNavigator} from '@react-navigation/stack';
import DisplaySeed from '../../Settings/ProfileSettings/DisplaySeed/DisplaySeed';
import DisplaySeed from '../../DisplaySeed/DisplaySeed';
import RecoverSeed from '../../Settings/ProfileSettings/RecoverSeed/RecoverSeed';
import DeleteProfile from '../../Settings/ProfileSettings/DeleteProfile/DeleteProfile';
import SecureLoading from '../../SecureLoading/SecureLoading';
Expand All @@ -9,6 +9,7 @@ import SignUp from '../../SignUp/SignUp';
import {useDispatch, useSelector} from 'react-redux';
import {setDeeplinkUrl} from '../../../actions/actionCreators';
import CreateProfile from '../../Onboard/CreateProfile/CreateProfile';
import RecoverSeedsStackScreens from '../RecoverSeedsStackScreens/RecoverSeedsStackScreens';

const SignedOutStack = createStackNavigator();

Expand Down Expand Up @@ -84,6 +85,14 @@ const SignedOutStackScreens = props => {
headerShown: false,
}}
/>

<SignedOutStack.Screen
name="RecoverSeeds"
component={RecoverSeedsStackScreens}
options={{
headerShown: false,
}}
/>
</SignedOutStack.Navigator>
);
};
Expand Down
Loading

0 comments on commit 44e6d04

Please sign in to comment.