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

Store hexSeed and wif in expo's SecureStorage #10

Merged
merged 3 commits into from Nov 28, 2017
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 0 additions & 2 deletions src/config/shape.js
Expand Up @@ -54,7 +54,5 @@ export default {
readOnly: bool,
trend: number,
type: string,
wif: string,
hexSeed: string,
},
};
10 changes: 7 additions & 3 deletions src/containers/ModalMnemonic.js
@@ -1,3 +1,4 @@
import { SecureStore } from 'expo';
import { bool, func, shape } from 'prop-types';
import React, { Component } from 'react';
import { View } from 'react-native';
Expand Down Expand Up @@ -25,7 +26,10 @@ class ModalMnemonic extends Component {
this._onValue = this._onValue.bind(this);
}

componentWillReceiveProps({ wallet: { hexSeed } }) {
async componentWillReceiveProps({ visible, wallet: { address, coin } }) {
if (!visible) return;
const hexSeed = address && await SecureStore.getItemAsync(`${coin}_${address}`);

this.setState({
words: (hexSeed) ? MnemonicService.backup(hexSeed) : Array(WORDS_LENGTH).fill(''),
});
Expand All @@ -50,9 +54,9 @@ class ModalMnemonic extends Component {

render() {
const {
_onBackup, _onRecover, _onValue, props: { onClose, visible, wallet: { hexSeed } }, state: { words = [] },
_onBackup, _onRecover, _onValue, props: { onClose, visible, wallet: { address } }, state: { words = [] },
} = this;
const readOnly = hexSeed !== undefined;
const readOnly = !!address;

return (
<Modal
Expand Down
7 changes: 4 additions & 3 deletions src/containers/ModalWallet.js
Expand Up @@ -46,10 +46,11 @@ class ModalWallet extends Component {
props: {
onClose, visible,
wallet: {
address, backup, hexSeed, type,
address, backup, imported, readOnly, type,
},
},
} = this;
const created = !imported && !readOnly;

return (
<Modal title="Wallet" visible={visible} onClose={onClose}>
Expand All @@ -58,7 +59,7 @@ class ModalWallet extends Component {
<Text style={styles.address}>{address}</Text>
</View>
<View style={[STYLE.COL]}>
{ hexSeed &&
{ created &&
<Option
caption="Create a backup"
hint="The simplest way to have control of your wallet."
Expand All @@ -69,7 +70,7 @@ class ModalWallet extends Component {
caption="Archive this wallet"
hint="If you do not want to use this wallet anymore."
icon="remove"
disabled={hexSeed !== undefined && backup === undefined}
disabled={created && !backup}
onPress={_onArchive}
/>
{ type !== PRO &&
Expand Down
4 changes: 2 additions & 2 deletions src/screens/Main/Main.js
Expand Up @@ -92,11 +92,11 @@ class Main extends Component {
} = this;
const wallet = wallets[index];
const focus = !showTransaction && !showWallet && !showWalletNew;
const readOnly = wallet && !wallet.hexSeed && !wallet.wif;
const { readOnly, coin } = wallet || { readOnly: false };

return (
<View style={STYLE.SCREEN}>
<View style={[STYLE.LAYOUT_TOP, (wallet && STYLE[wallet.coin])]}>
<View style={[STYLE.LAYOUT_TOP, (wallet && STYLE[coin])]}>
<Header symbol="USD" />
<Swiper
bounces
Expand Down
4 changes: 2 additions & 2 deletions src/screens/Main/components/Transactions.js
Expand Up @@ -8,7 +8,7 @@ import { updateTransactionsAction } from '../../../store/actions';
import TransactionItem from './TransactionItem';
import styles from './Transactions.style';

const { STATE: { ARCHIVED } } = C;
const { STATE: { ARCHIVED, REQUESTED } } = C;
const { TRANSACTION, WALLET } = SHAPE;

class Transactions extends Component {
Expand Down Expand Up @@ -77,7 +77,7 @@ const mapStateToProps = ({ device, transactions = [] }, { wallet = {} }) => ({
state !== ARCHIVED &&
(
[from.address, to.address].includes(wallet.address) ||
[from.device, to.device].includes(device.id)
(state === REQUESTED && [from.device, to.device].includes(device.id))
)
)),
});
Expand Down
4 changes: 2 additions & 2 deletions src/screens/Main/components/WalletItem.js
Expand Up @@ -27,7 +27,7 @@ const WalletItem = ({
currencies, data, device: { currency }, onOption, onPress, style,
}) => {
const {
balance = 0, coin = 'BTC', hexSeed, name = '', trend = 0, type, wif,
balance = 0, coin = 'BTC', name = '', readOnly, trend = 0, type,
} = data || {};

return (
Expand Down Expand Up @@ -55,7 +55,7 @@ const WalletItem = ({
<Icon value={trend > 0 ? 'trendingUp' : 'trendingDown'} style={styles.trend} />
<Text style={[styles.label]}>{`${trend.toFixed(2)}%`}</Text>
<View style={styles.tags}>
{ !hexSeed && !wif && <View style={styles.tag}><Text style={styles.tagLabel}>{READ_ONLY}</Text></View> }
{ readOnly && <View style={styles.tag}><Text style={styles.tagLabel}>{READ_ONLY}</Text></View> }
{ type === PRO && <View style={[styles.tag, styles.pro]}><Text style={styles.tagLabel}>PRO</Text></View> }
</View>
</View>
Expand Down
12 changes: 7 additions & 5 deletions src/services/transaction.js
@@ -1,6 +1,7 @@
import BitcoinJS from 'bitcoinjs-lib';
import { service } from './modules';
import { C } from '../config';
import { SecureStore } from '../store';
import { service } from './modules';

const { NETWORKS } = C;

Expand All @@ -18,13 +19,14 @@ export default {
return service('transaction/request', { method: 'POST', body: JSON.stringify(props) });
},

async send(props, { coin, wif, hexSeed }) {
async send(props, { coin, address, imported }) {
const network = BitcoinJS.networks[NETWORKS[coin]];
const { tx: hexTx, fee } = await service('transaction/prepare', { method: 'POST', body: JSON.stringify(props) });
const tx = BitcoinJS.TransactionBuilder.fromTransaction(BitcoinJS.Transaction.fromHex(hexTx), network);
const ECPair = wif
? BitcoinJS.ECPair.fromWIF(wif, network)
: BitcoinJS.HDNode.fromSeedHex(hexSeed, network).keyPair;
const secret = await SecureStore.get(`${coin}_${address}`);
const ECPair = imported
? BitcoinJS.ECPair.fromWIF(secret, network)
: BitcoinJS.HDNode.fromSeedHex(secret, network).keyPair;

// @TODO: verify outputs are what we expect
tx.inputs.forEach((_, i) => {
Expand Down
20 changes: 12 additions & 8 deletions src/services/wallet.js
@@ -1,8 +1,9 @@
import BitcoinJS from 'bitcoinjs-lib';
import { service } from './modules';
import { C } from '../config';
import { PushService } from './';
import { csprng } from '../modules';
import { SecureStore } from '../store';
import { service } from './modules';
import { PushService } from './';

const { CRYPTO: { BTC }, NETWORKS } = C;

Expand All @@ -13,7 +14,8 @@ export default {
const hexSeed = params.hexSeed || (await csprng()).substring(0, 32);
const address = params.address || BitcoinJS.HDNode.fromSeedHex(hexSeed, network).getAddress();

const wallet = await service('wallet', {
if (hexSeed) await SecureStore.set(`${coin}_${address}`, hexSeed);
const wallet = service('wallet', {
method: 'POST',
body: JSON.stringify({
address,
Expand All @@ -23,30 +25,32 @@ export default {
}),
});

return ({ ...wallet, hexSeed });
return wallet; // @TODO: Dispatch error if doesnt exist.
},

async import(props) {
const {
wif,
coin = BTC,
address,
address: readOnlyAddress,
...inherit
} = props;
const network = BitcoinJS.networks[NETWORKS[coin]];
const address = wif ? BitcoinJS.ECPair.fromWIF(wif, network).getAddress() : readOnlyAddress;

const wallet = await service('wallet', {
if (wif) await SecureStore.set(`${coin}_${address}`, wif);
const wallet = service('wallet', {
method: 'POST',
body: JSON.stringify({
...inherit,
address: wif ? BitcoinJS.ECPair.fromWIF(wif, network).getAddress() : address,
coin,
address,
imported: true,
readOnly: (wif === undefined),
}),
});

return ({ ...wallet, wif });
return wallet; // @TODO: Dispatch error if doesnt exist.
},

async archive(props) {
Expand Down
2 changes: 2 additions & 0 deletions src/store/index.js
@@ -1,9 +1,11 @@
import actions from './actions';
import initialize from './initialize';
import reducer from './reducer';
import SecureStore from './secure';

export {
actions,
initialize,
reducer,
SecureStore,
};
14 changes: 14 additions & 0 deletions src/store/secure.js
@@ -0,0 +1,14 @@
import { SecureStore } from 'expo';

const PROPS = { keychainAccessible: SecureStore.WHEN_UNLOCKED };

export default {

async set(key, value) {
return SecureStore.setItemAsync(key, value, PROPS);
},

async get(key) {
return SecureStore.getItemAsync(key);
},
};