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 1 commit
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
4 changes: 2 additions & 2 deletions src/components/AccountIconChooser.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ export default class AccountIconChooser extends React.PureComponent {
if (protocol === NetworkProtocols.ETHEREUM) {
Object.assign(result, await brainWalletAddress(result.seed));
} else {
// Substrate
try {

const suri = constructSURI({
phrase: result.seed,
derivePath: derivationPath,
Expand All @@ -77,7 +77,7 @@ export default class AccountIconChooser extends React.PureComponent {
result.bip39 = true;
} catch (e){
// invalid seed or derivation path
// console.error(e);
console.error(e);
}
}
return result;
Expand Down
25 changes: 17 additions & 8 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 {parseSURI} from '../util/suri'
import {parseDerivationPath} from '../util/suri'
import TextInput from './TextInput';

export default function DerivationPathField(props) {
Expand Down Expand Up @@ -58,13 +58,22 @@ export default function DerivationPathField(props) {
{showAdvancedField &&
<TextInput
onChangeText={(text) => {
const derivationPath = parseSURI(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
54 changes: 32 additions & 22 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 Down Expand Up @@ -101,28 +101,38 @@ class AccountNewView extends React.Component {
derivationPassword={derivationPassword}
derivationPath={derivationPath}
onSelect={({ newAddress, isBip39, newSeed }) => {
if (isSubstrate) {
const suri = constructSURI({
phrase: newSeed,
derivePath: derivationPath,
password:derivationPassword
});

accounts.updateNew({
address: newAddress,
derivationPassword,
derivationPath,
seed: suri,
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
46 changes: 33 additions & 13 deletions src/screens/AccountRecover.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,30 +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 = 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
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
53 changes: 44 additions & 9 deletions src/util/suri.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,68 @@

/**
* @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 ExtraaccountIdcts 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).
* @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 RE_CAPTURE = /^(\w+(?: \w+)*)?(.*)$/;
const matches = suri.match(RE_CAPTURE);
let phrase, derivePath, password = '';

let phrase, derivationPath = '';
const ERROR = 'Invalid SURI input.';
if (matches) {
[_, phrase, derivePath = '', password = ''] = matches;
[_, phrase, derivationPath = ''] = matches;
try {
parsedDerivationPath = parseDerivationPath(derivationPath)
} catch {
throw new Error(ERROR);
}
} else {
throw new Error('SURI input was not valid.');
throw new Error(ERROR);
}

if (!phrase) {
throw new Error('SURI must contain a phrase.');
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
};
Expand All @@ -42,7 +77,7 @@ export function parseSURI (suri) {
export function constructSURI ({ derivePath = '', password = '', phrase }) {

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

return `${phrase}${derivePath}///${password}`;
Expand Down
33 changes: 30 additions & 3 deletions src/util/suri.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,33 @@

'use strict';

import { constructSURI, parseSURI } from './suri';
import { constructSURI, parseDerivationPath, parseSURI } from './suri';

describe('derivation path', () => {
describe('parsing', () => {
it('should properly parse and return a derivation path object from a string containing soft, hard and password', () => {
const parsedDerivationPath = parseDerivationPath('/soft/soft//hard///mypassword');

expect(parsedDerivationPath).toBeDefined();
expect(parsedDerivationPath.derivePath).toBe('/soft/soft//hard');
expect(parsedDerivationPath.password).toBe('mypassword');
});

it('should throw if the string is not a valid derivation path', () => {
const malformed = 'wrong/bla';

expect(() => parseDerivationPath(malformed)).toThrowError('Invalid derivation path input.');
});

it('should accept a password alone', () => {
const passwordAlone = '///mypassword';
const parsedDerivationPath = parseDerivationPath(passwordAlone);

expect(parsedDerivationPath).toBeDefined();
expect(parsedDerivationPath.password).toBe('mypassword');
})
});
});

describe('suri', () => {
describe('parsing', () => {
Expand All @@ -32,7 +58,7 @@ describe('suri', () => {
it('should throw if the string is not a valid suri', () => {
const malformed = '1!,#(&(/)!_c.';

expect(() => parseSURI(malformed)).toThrowError('SURI input was not valid');
expect(() => parseSURI(malformed)).toThrowError('Invalid SURI input.');
});

it('should throw if phrase was empty', () => {
Expand Down Expand Up @@ -63,7 +89,8 @@ describe('suri', () => {
phrase: null
}

expect(() => constructSURI(empty)).toThrow('cannot construct an SURI from emtpy phrase.');expect(() => constructSURI(malformed)).toThrow('cannot construct an SURI from emtpy phrase.');
expect(() => constructSURI(empty)).toThrow('Cannot construct an SURI from emtpy phrase.');
expect(() => constructSURI(malformed)).toThrow('Cannot construct an SURI from emtpy phrase.');
});
});
});