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

Parse and construct function together #328

Merged
merged 8 commits into from
Aug 26, 2019
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions src/components/AccountIconChooser.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { NetworkProtocols } from '../constants';
import fonts from "../fonts";
import { debounce } from '../util/debounce';
import { brainWalletAddress, substrateAddress, words } from '../util/native';
import {constructSURI} from '../util/suri'

export default class AccountIconChooser extends React.PureComponent {
constructor(props) {
Expand All @@ -48,7 +49,6 @@ export default class AccountIconChooser extends React.PureComponent {

// clean previous selection
onSelect({ newAddress: '', isBip39: false, newSeed: ''});

try {
const icons = await Promise.all(
Array(4)
Expand All @@ -65,12 +65,19 @@ export default class AccountIconChooser extends React.PureComponent {
if (protocol === NetworkProtocols.ETHEREUM) {
Object.assign(result, await brainWalletAddress(result.seed));
} else {
// Substrate
try {
result.address = await substrateAddress(`${result.seed}${derivationPath}///${derivationPassword}`, prefix);
const suri = constructSURI({
phrase: result.seed,
derivePath: derivationPath,
password:derivationPassword
Tbaut marked this conversation as resolved.
Show resolved Hide resolved
});

result.address = await substrateAddress(suri, prefix);
result.bip39 = true;
} catch (e){
// invalid seed or derivation path
// console.error(e);
console.error(e);
}
}
return result;
Expand Down
24 changes: 17 additions & 7 deletions src/components/DerivationPathField.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
} from 'react-native';
import Icon from 'react-native-vector-icons/MaterialIcons';

import keyExtract from '../util/keyExtract'
import {parseDerivationPath} from '../util/suri'
import TextInput from './TextInput';

export default function DerivationPathField(props) {
Expand Down Expand Up @@ -58,12 +58,22 @@ export default function DerivationPathField(props) {
{showAdvancedField &&
<TextInput
onChangeText={(text) => {
const derivationPath = keyExtract(text);
onChange({
derivationPassword: derivationPath.password || '',
derivationPath: derivationPath.derivePath || ''
});
setIsValidPath(!!derivationPath.password || !!derivationPath.derivePath);
try {
const derivationPath = parseDerivationPath(text);
console.log('derivationPath', derivationPath)
Tbaut marked this conversation as resolved.
Show resolved Hide resolved
onChange({
derivationPassword: derivationPath.password || '',
derivationPath: derivationPath.derivePath || ''
});
setIsValidPath(true);
} catch (e) {
// wrong derivationPath
onChange({
derivationPassword: '',
derivationPath: ''
});
setIsValidPath(false);
}
}}
placeholder="optional derivation path"
style={isValidPath ? ownStyles.validInput: ownStyles.invalidInput}
Expand Down
49 changes: 33 additions & 16 deletions src/screens/AccountNew.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
'use strict';

import React from 'react';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { StyleSheet, Text, View } from 'react-native';
import { Subscribe } from 'unstated';

import colors from '../colors';
Expand All @@ -32,6 +32,7 @@ import { NETWORK_LIST, NetworkProtocols } from '../constants';
import fonts from '../fonts';
import AccountsStore from '../stores/AccountsStore';
import { empty, validateSeed } from '../util/account';
import {constructSURI} from "../util/suri";
Tbaut marked this conversation as resolved.
Show resolved Hide resolved

export default class AccountNew extends React.Component {
static navigationOptions = {
Expand Down Expand Up @@ -100,22 +101,38 @@ class AccountNewView extends React.Component {
derivationPassword={derivationPassword}
derivationPath={derivationPath}
onSelect={({ newAddress, isBip39, newSeed }) => {
if (isSubstrate) {
accounts.updateNew({
address: newAddress,
derivationPassword,
derivationPath,
seed: `${newSeed}${derivationPath}///${derivationPassword}`,
seedPhrase: newSeed,
validBip39Seed: isBip39
});
if (newAddress && isBip39 && newSeed){
if (isSubstrate) {
try {
const suri = constructSURI({
derivePath: derivationPath,
password:derivationPassword,
phrase: newSeed
});

accounts.updateNew({
address: newAddress,
derivationPassword,
derivationPath,
seed: suri,
seedPhrase: newSeed,
validBip39Seed: isBip39
});
} catch (e) {
console.error(e);
}
} else {
// Ethereum account
accounts.updateNew({
address: newAddress,
seed: newSeed,
validBip39Seed: isBip39
});
}
} else {
accounts.updateNew({
address: newAddress,
seed: newSeed,
validBip39Seed: isBip39
});
}}}
accounts.updateNew({ address: '', seed: '', validBip39Seed: false})
}
}}
network={selectedNetwork}
value={address && address}
/>
Expand Down
43 changes: 34 additions & 9 deletions src/screens/AccountRecover.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import AccountsStore from '../stores/AccountsStore';
import { empty, validateSeed } from '../util/account';
import { debounce } from '../util/debounce';
import { brainWalletAddress, substrateAddress } from '../util/native';
import {constructSURI} from "../util/suri";
Tbaut marked this conversation as resolved.
Show resolved Hide resolved

export default class AccountRecover extends React.Component {
static navigationOptions = {
Expand Down Expand Up @@ -85,26 +86,50 @@ class AccountRecoverView extends React.Component {
}
}

clearNewAccount = function () {
const { accounts } = this.props;

accounts.updateNew({ address:'', derivationPath:'', derivationPassword:'', seed:'', seedPhrase:'', validBip39Seed: false })
}

addressGeneration = (seedPhrase, derivationPath = '', derivationPassword = '') => {
const { accounts } = this.props;
const { selectedNetwork:{protocol, prefix} } = this.state;

if (!seedPhrase){
this.clearNewAccount();

return;
}

if (protocol === NetworkProtocols.ETHEREUM){
brainWalletAddress(seedPhrase)
.then(({ address, bip39 }) =>
accounts.updateNew({ address, seed: seedPhrase, seedPhrase, validBip39Seed: bip39 })
)
.catch(console.error);
} else {
const suri = `${seedPhrase}${derivationPath}///${derivationPassword}`
substrateAddress(suri, prefix)
.then((address) => {
accounts.updateNew({ address, derivationPath, derivationPassword, seed: suri, seedPhrase, validBip39Seed: true })
})
.catch(
//invalid phrase
accounts.updateNew({ address:'', derivationPath:'', derivationPassword:'', seed:'', seedPhrase:'', validBip39Seed: false })
);
// Substrate
try {
const suri = constructSURI({
phrase: seedPhrase,
derivePath: derivationPath,
password:derivationPassword
});

substrateAddress(suri, prefix)
.then((address) => {
accounts.updateNew({ address, derivationPath, derivationPassword, seed: suri, seedPhrase, validBip39Seed: true })
})
.catch(() => {
//invalid phrase
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one error catch may be redundant?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so since the catch is specific to substrateAddress whereas the other is for constructSURI

this.clearNewAccount();
});
} catch (e) {
// invalid phrase or derivation path
this.clearNewAccount();
}

}
};

Expand Down
4 changes: 2 additions & 2 deletions src/stores/AccountsStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { Container } from 'unstated';

import { accountId, empty } from '../util/account';
import { loadAccounts, saveAccount } from '../util/db';
import keyExtract from '../util/keyExtract'
import {parseSURI} from '../util/suri'
import { decryptData, encryptData } from '../util/native';


Expand Down Expand Up @@ -144,7 +144,7 @@ export default class AccountsStore extends Container {
try {
account.seed = await decryptData(account.encryptedSeed, pin);

const {phrase, derivePath, password} = keyExtract(account.seed)
const {phrase, derivePath, password} = parseSURI(account.seed)

account.seedPhrase = phrase || '';
account.derivationPath = derivePath || '';
Expand Down
23 changes: 0 additions & 23 deletions src/util/keyExtract.js

This file was deleted.

84 changes: 84 additions & 0 deletions src/util/suri.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@

/**
* @typedef {Object} SURIObject
* @property {string} phrase - The valid bip39 seed phrase
* @property {string} derivePath - The derivation path consisting in `/soft` and or `//hard`, can be repeated and interchanges
* @property {string} password - The optionnal password password without the `///`
*/

/**
* @typedef {Object} DerivationPathObject
* @property {string} derivePath - The derivation path consisting in `/soft` and or `//hard`, can be repeated and interchanges
* @property {string} password - The optionnal password password without the `///`
*/

/**
* @description Extract the phrase, path and password from a SURI format for specifying secret keys `<secret>/<soft-key>//<hard-key>///<password>` (the `///password` may be omitted, and `/<soft-key>` and `//<hard-key>` maybe repeated and mixed).
* @param {string} suri The SURI to be parsed
* @returns {SURIObject}
*/

export function parseSURI (suri) {
const RE_CAPTURE = /^(\w+(?: \w+)*)?(.*)$/;
const matches = suri.match(RE_CAPTURE);
let phrase, derivationPath = '';
const ERROR = 'Invalid SURI input.';
if (matches) {
[_, phrase, derivationPath = ''] = matches;
try {
parsedDerivationPath = parseDerivationPath(derivationPath)
} catch {
throw new Error(ERROR);
}
} else {
throw new Error(ERROR);
}

if(!phrase) {
throw new Error('SURI must contain a phrase.')
}

return {
phrase,
derivePath: parsedDerivationPath.derivePath || '',
password: parsedDerivationPath.password || ''
};
}

/**
* @description Extract the path and password from a SURI format for specifying secret keys `/<soft-key>//<hard-key>///<password>` (the `///password` may be omitted, and `/<soft-key>` and `//<hard-key>` maybe repeated and mixed).
* @param {string} suri The SURI to be parsed
* @returns {DerivationPathObject}
*/

export function parseDerivationPath (input) {
const RE_CAPTURE = /^((?:\/\/?[^/]+)*)(?:\/\/\/(.*))?$/;
const matches = input.match(RE_CAPTURE);
let derivePath, password;

if (matches) {
[_,derivePath = '', password = ''] = matches;
} else {
throw new Error('Invalid derivation path input.');
}

return {
derivePath,
password
};
}

/**
* @description Return a SURI format from a bip39 phrase, a derivePath, e.g `//hard/soft` and a password.
* @param {SURIObject} SURIObject
* @returns {string}
*/

export function constructSURI ({ derivePath = '', password = '', phrase }) {

if(!phrase) {
throw new Error('Cannot construct an SURI from emtpy phrase.');
}

return `${phrase}${derivePath}///${password}`;
}
Loading