Unverified Commit be3edf8b authored by Thibaut Sardan's avatar Thibaut Sardan Committed by GitHub
Browse files

Substrate accounts management (#293)

* chore: Squashed old branch

* feat: expose blake2s hash function

* fix(): network selection

* fix(): backward compatible

* fix(): Mock for a sparta account, add prefixes for substrate

* fix(): address map for substrate accounts

* fix: Duplicate function names in iOS

* feat: Expose `substrateAddress` in native.js

* fix: use genesisHash and Address

* fix: new account creation with mock

* fix: advanced derived path field for new account

* fix: use react hooks in AccountIcon

* fix: fix address and nits

* fix: fix genesis in QR

* fix: genesisHash as string

* fix: use real accounts and mnemonic

* fix: unneeded hexToAscii

* feat: sr25519 signing

* fix: put current account in the state

* feat: Complete SURI derivation

* fix: use genesisHash

* fix: use substrateAddress

* fix: recover

* fix: recover derivation

* fix: refactor advanced (derivation path) field

* fix: derivation path validity + refactor

* fix: display derivation path and link to check pasword

* fix: derivation for new accounts

* fix: verify derivation pw

* fix: lock when saving accounts

* fix: lock when unmounting backup view

* fix: name wasn't save properly in securestore

* fix: comment out Polkadot and add testnet

* fix: bug ethereum new account and duplicated functions

* fix: stray comment

* fix: derive path

* fix: seed validation
parent 10e30dd4
Pipeline #49830 failed with stage
in 14 seconds
......@@ -119024,8 +119024,8 @@ __d(function (global, _$$_REQUIRE, _$$_IMPORT_DEFAULT, _$$_IMPORT_ALL, module, e
 
function accountId(_ref) {
var address = _ref.address,
_ref$networkType = _ref.networkType,
networkType = _ref$networkType === void 0 ? 'ethereum' : _ref$networkType,
_ref$protocol = _ref.protocol,
protocol = _ref$protocol === void 0 ? 'ethereum' : _ref$protocol,
_ref$chainId = _ref.chainId,
chainId = _ref$chainId === void 0 ? '1' : _ref$chainId;
 
......@@ -119033,14 +119033,14 @@ __d(function (global, _$$_REQUIRE, _$$_IMPORT_DEFAULT, _$$_IMPORT_ALL, module, e
throw new Error("Couldn't create an accountId, missing address");
}
 
return networkType + ":0x" + address.toLowerCase() + "@" + chainId;
return protocol + ":0x" + address.toLowerCase() + "@" + chainId;
}
 
function empty() {
var account = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
return _objectSpread({
name: '',
networkType: _constants.NETWORK_TYPE.ethereum,
protocol: _constants.NETWORK_TYPE.ethereum,
chainId: _constants.NETWORK_ID.frontier,
seed: '',
address: '00a329c0648769A73afAc7F9381E08FB43dBEA72',
......@@ -119180,11 +119180,11 @@ __d(function (global, _$$_REQUIRE, _$$_IMPORT_DEFAULT, _$$_IMPORT_ALL, module, e
 
function accountTxsKey(_ref) {
var address = _ref.address,
networkType = _ref.networkType,
protocol = _ref.protocol,
chainId = _ref.chainId;
return 'account_txs_' + (0, _account.accountId)({
address: address,
networkType: networkType,
protocol: protocol,
chainId: chainId
});
}
......@@ -122930,7 +122930,7 @@ __d(function (global, _$$_REQUIRE, _$$_IMPORT_DEFAULT, _$$_IMPORT_ALL, module, e
tx = _context3.sent;
_tx$chainId = tx.chainId, chainId = _tx$chainId === void 0 ? '1' : _tx$chainId;
sender = accountsStore.getById({
networkType: 'ethereum',
protocol: 'ethereum',
chainId: chainId,
address: txRequest.data.account
});
......@@ -122945,7 +122945,7 @@ __d(function (global, _$$_REQUIRE, _$$_IMPORT_DEFAULT, _$$_IMPORT_ALL, module, e
 
case 12:
recipient = accountsStore.getById({
networkType: 'ethereum',
protocol: 'ethereum',
chainId: tx.chainId,
address: tx.action
});
......@@ -83,6 +83,17 @@ class EthkeyBridge: NSObject {
}
}
@objc func blake2s(_ data: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
var error: UInt32 = 0
var data_ptr = data.asPtr()
let hash_rust_str = blake(&error, &data_ptr)
let hash_rust_str_ptr = rust_string_ptr(hash_rust_str)
let hash = String.fromStringPtr(ptr: hash_rust_str_ptr!.pointee)
rust_string_ptr_destroy(hash_rust_str_ptr)
rust_string_destroy(hash_rust_str)
resolve(hash)
}
@objc func ethSign(_ data: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
var error: UInt32 = 0
var data_ptr = data.asPtr()
......
......@@ -19,19 +19,16 @@
import PropTypes from 'prop-types';
import React from 'react';
import { StyleSheet, Text, View, ViewPropTypes } from 'react-native';
import AccountIcon from './AccountIcon';
import Address from './Address';
import colors from '../colors';
import fonts from '../fonts';
import { NETWORK_LIST } from '../constants';
import AccountIcon from './AccountIcon';
import fonts from '../fonts';
import TouchableItem from './TouchableItem';
export default class AccountCard extends React.PureComponent<{
address: string,
networkKey: string,
onPress?: () => any,
title?: string,
seedType?: string
}> {
export default class AccountCard extends React.PureComponent {
static propTypes = {
address: PropTypes.string.isRequired,
networkKey: PropTypes.string,
......@@ -47,7 +44,7 @@ export default class AccountCard extends React.PureComponent<{
};
render() {
const { address, networkKey, onPress, seedType, style } = this.props;
const { address, networkKey, onPress, seedType, shortAddress = false, style } = this.props;
let { title } = this.props;
title = title.length ? title : AccountCard.defaultProps.title;
const seedTypeDisplay = seedType || '';
......@@ -61,19 +58,20 @@ export default class AccountCard extends React.PureComponent<{
>
<View style={[styles.body, style]}>
<View style={styles.content}>
<AccountIcon style={styles.icon} seed={'0x' + address} />
<AccountIcon
address={address}
protocol={network.protocol}
style={styles.icon}
/>
<View style={styles.desc}>
<Text numberOfLines={1} style={styles.titleText}>
{title}
</Text>
<Text
numberOfLines={1}
adjustsFontSizeToFit
minimumFontScale={0.01}
style={styles.secondaryText}
>
0x{address}
</Text>
<Address
address={address}
protocol={network.protocol}
short = {shortAddress}
/>
</View>
</View>
<View
......
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
// @flow
import PropTypes from 'prop-types';
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import colors from '../colors';
import { NETWORK_LIST } from '../constants';
import AccountIcon from './AccountIcon';
import TouchableItem from './TouchableItem';
export default class AccountDetailsCard extends React.PureComponent<{
title: string,
address: string,
networkKey: string,
onPress: () => any
}> {
static propTypes = {
title: PropTypes.string.isRequired,
address: PropTypes.string.isRequired,
networkKey: PropTypes.string,
onPress: PropTypes.func
};
render() {
const { title, address, networkKey, onPress } = this.props;
const network = NETWORK_LIST[networkKey];
return (
<TouchableItem
accessibilityComponentType="button"
disabled={false}
onPress={onPress}
>
<View style={styles.body}>
<View style={styles.content}>
<AccountIcon style={styles.icon} seed={'0x' + address} />
<View style={styles.desc}>
<Text numberOfLines={1} style={styles.titleText}>
{title}
</Text>
<Text style={styles.editText}>Tap to edit account</Text>
</View>
</View>
<View>
<Text
numberOfLines={1}
adjustsFontSizeToFit
minimumFontScale={0.01}
style={styles.addressText}
>
0x{address}
</Text>
</View>
</View>
<View
style={[
styles.footer,
{
backgroundColor: network.color
}
]}
>
<Text
style={[
styles.footerText,
{
color: network.secondaryColor
}
]}
>
{network.title}
</Text>
</View>
</TouchableItem>
);
}
}
const styles = StyleSheet.create({
body: {
padding: 20,
backgroundColor: colors.card_bg
},
content: {
flexDirection: 'row',
backgroundColor: colors.card_bg
},
icon: {
width: 70,
height: 70
},
desc: {
flexDirection: 'column',
paddingLeft: 10,
flex: 1
},
footer: {
backgroundColor: '#977CF6',
flexDirection: 'row-reverse',
padding: 5
},
titleText: {
fontSize: 20
},
editText: {
paddingTop: 12,
color: colors.bg_text_sec,
fontWeight: '500',
fontSize: 15
},
addressText: {
paddingTop: 20,
color: colors.bg,
fontWeight: '700',
fontSize: 16
},
footerText: {
color: colors.card_bg,
fontWeight: 'bold'
}
});
......@@ -16,59 +16,55 @@
'use strict';
import Identicon from '@polkadot/reactnative-identicon';
import PropTypes from 'prop-types';
import React from 'react';
import { Image, StyleSheet, View } from 'react-native';
import { blockiesIcon } from '../util/native';
import React, { useEffect, useState } from 'react';
import { Image } from 'react-native';
export default class AccountIcon extends React.PureComponent {
import { NetworkProtocols } from '../constants'
import { blockiesIcon } from '../util/native';
constructor(...args) {
super(...args);
this.displayIcon = this.displayIcon.bind(this);
}
export default function AccountIcon (props) {
static propTypes = {
seed: PropTypes.string.isRequired
AccountIcon.propTypes = {
address: PropTypes.string.isRequired,
protocol: PropTypes.string.isRequired
};
state = {};
const {address, protocol, style} = props;
const [ethereumIconUri, setEthereumIconUri] = useState('');
async displayIcon(seed) {
try {
let icon = await blockiesIcon(seed);
this.setState({
icon: icon
});
} catch (e) {
console.log(e);
useEffect(() => {
if (protocol === NetworkProtocols.ETHEREUM) {
loadEthereumIcon(address);
}
}
},[protocol, address])
componentDidMount() {
this.displayIcon(this.props.seed);
const loadEthereumIcon = function (address){
blockiesIcon('0x'+address)
.then((ethereumIconUri) => {
setEthereumIconUri(ethereumIconUri);
})
.catch(console.error)
}
componentWillReceiveProps(newProps) {
if (newProps.seed !== this.props.seed) {
this.displayIcon(newProps.seed);
}
}
if (protocol === NetworkProtocols.SUBSTRATE) {
render() {
return (
<View style={styles.identicon}>
<Image
style={this.props.style || {}}
source={{ uri: this.state.icon }}
/>
</View>
<Identicon
value={address}
size={style.width || 50 }
/>
);
}
}
} else if (protocol === NetworkProtocols.ETHEREUM && ethereumIconUri){
const styles = StyleSheet.create({
identicon: {
alignItems: 'center'
return (
<Image
source={{ uri: ethereumIconUri }}
style={style || { width: 47, height: 47 }}
/>
);
}
});
return null;
}
......@@ -27,40 +27,55 @@ import {
import Icon from 'react-native-vector-icons/MaterialIcons';
import AccountIcon from './AccountIcon';
import Address from './Address'
import Address from './Address';
import colors from '../colors';
import { NetworkProtocols } from '../constants';
import fonts from "../fonts";
import { brainWalletAddress, words } from '../util/native';
import { debounce } from '../util/debounce';
import { brainWalletAddress, substrateAddress, words } from '../util/native';
export default class AccountIconChooser extends React.PureComponent {
constructor(props) {
super(props);
this.state = { icons: [] };
this.state = {
icons: []
};
}
componentDidMount() {
this.refreshAccount();
}
refreshIcons = async () => {
const {derivationPassword, derivationPath, network : {protocol, prefix}, onSelect} = this.props;
// clean previous selection
onSelect({ newAddress: '', isBip39: false, newSeed: ''});
refreshAccount = async () => {
try {
const icons = await Promise.all(
Array(4)
.join(' ')
.split(' ')
.map(async () => {
const seed = await words();
const { address, bip39 } = await brainWalletAddress(seed);
return {
address,
bip39,
seed,
};
let result = {
address: '',
bip39: false,
seed: ''
}
result.seed = await words();
if (protocol === NetworkProtocols.ETHEREUM) {
Object.assign(result, await brainWalletAddress(result.seed));
} else {
try {
result.address = await substrateAddress(`${result.seed}${derivationPath}///${derivationPassword}`, prefix);
result.bip39 = true;
} catch (e){
// invalid seed or derivation path
// console.error(e);
}
}
return result;
})
);
this.setState({ icons });
} catch (e) {
console.error(e);
......@@ -68,12 +83,13 @@ export default class AccountIconChooser extends React.PureComponent {
}
renderAddress = () => {
const {value} = this.props;
const {network: {protocol}, value} = this.props;
if (value) {
return (
<Address
address={value}
protocol={protocol}
style = {styles.addressText}
/>
);
......@@ -83,29 +99,46 @@ export default class AccountIconChooser extends React.PureComponent {
}
renderIcon = ({ item, index }) => {
const { value, onSelect } = this.props;
const { onSelect, network : {protocol}, value } = this.props;
const { address, bip39, seed } = item;
const isSelected = address.toLowerCase() === value.toLowerCase();
if (!address) {
//return an empty view to prevent the screen from jumping
return <View
style={styles.icon}
/>
}
return (
<TouchableOpacity
key={index}
style={[styles.iconBorder, isSelected ? styles.selected : {}]}
onPress={() => onSelect({ address, bip39, seed })}
onPress={() => onSelect({ newAddress: address, isBip39: bip39, newSeed: seed })}
>
<AccountIcon
address={address}
protocol={protocol}
style={styles.icon}
seed={'0x' + address}
/>
</TouchableOpacity>
);
}
onRefresh = () => {
const { onSelect } = this.props;
componentDidMount() {
this.refreshIcons();
}
debouncedRefreshIcons = debounce(this.refreshIcons, 200);
componentDidUpdate(prevProps){
const {derivationPassword, derivationPath, network} = this.props;
this.refreshAccount();
onSelect({ address: '', bip39: false, seed: ''});
if ((prevProps.network !== network) ||
(prevProps.derivationPassword !== derivationPassword) ||
(prevProps.derivationPath !== derivationPath)){
this.debouncedRefreshIcons();
}
}
render() {
......@@ -119,12 +152,12 @@ export default class AccountIconChooser extends React.PureComponent {
data={icons}
extraData={value}
horizontal
keyExtractor={item => item.address}
keyExtractor={item => item.seed}
renderItem={this.renderIcon}
style={styles.icons}
/>
<TouchableOpacity
onPress={this.onRefresh}
onPress={this.refreshIcons}
>
<Icon
name={'refresh'}
......
......@@ -24,9 +24,11 @@ import {
import colors from '../colors';
import fonts from "../fonts";
import {NetworkProtocols} from '../constants'
export default function Address (props) {
const {address, short = false ,style = {}} = props;
const {address, protocol = NetworkProtocols.SUBSTRATE, short = false ,style = {}} = props;
const prefix = protocol === NetworkProtocols.ETHEREUM ? '0x' : '';
let result = address;
if (short) {
......@@ -35,7 +37,7 @@ export default function Address (props) {
return (
<Text numberOfLines={1} style={[style, styles.secondaryText]}>
0x{result}
{prefix}{result}
</Text>
);
}
......
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
'use strict';
import React, { useState } from 'react';
import {
StyleSheet,
Text,
TouchableOpacity,
View
} from 'react-native';
import Icon from 'react-native-vector-icons/MaterialIcons';
import colors from '../colors';
import fonts from "../fonts";
import TextInput from './TextInput';
export default function DerivationPasswordVerify(props) {
const { password } = props;
const [enteredPassword, setEnteredPassword] = useState('')