diff --git a/js/src/modals/PasswordManager/store.js b/js/src/modals/PasswordManager/store.js
index c60576e0f..659543c28 100644
--- a/js/src/modals/PasswordManager/store.js
+++ b/js/src/modals/PasswordManager/store.js
@@ -133,7 +133,7 @@ export default class Store {
}
testPassword = (password) => {
- this.setBusy(false);
+ this.setBusy(true);
return this._api.parity
.testPassword(this.address, password || this.validatePassword)
diff --git a/js/src/redux/providers/compilerActions.js b/js/src/redux/providers/compilerActions.js
deleted file mode 100644
index d638c03a2..000000000
--- a/js/src/redux/providers/compilerActions.js
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2015, 2016 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 .
-
-import PromiseWorker from 'promise-worker';
-import runtime from 'serviceworker-webpack-plugin/lib/runtime';
-
-let workerRegistration;
-
-// Setup the Service Worker
-if ('serviceWorker' in navigator) {
- workerRegistration = runtime
- .register()
- .then(() => navigator.serviceWorker.ready)
- .then((registration) => {
- const _worker = registration.active;
- _worker.controller = registration.active;
- const worker = new PromiseWorker(_worker);
-
- return worker;
- });
-} else {
- workerRegistration = Promise.reject('Service Worker is not available in your browser.');
-}
-
-export function setWorker (worker) {
- return {
- type: 'setWorker',
- worker
- };
-}
-
-export function setError (error) {
- return {
- type: 'setError',
- error
- };
-}
-
-export function setupWorker () {
- return (dispatch, getState) => {
- const state = getState();
-
- if (state.compiler.worker) {
- return;
- }
-
- workerRegistration
- .then((worker) => {
- dispatch(setWorker(worker));
- })
- .catch((error) => {
- console.error('sw', error);
- dispatch(setWorker(null));
- });
- };
-}
diff --git a/js/src/redux/providers/index.js b/js/src/redux/providers/index.js
index 6a000bdac..e1441c479 100644
--- a/js/src/redux/providers/index.js
+++ b/js/src/redux/providers/index.js
@@ -22,7 +22,7 @@ export Status from './status';
export apiReducer from './apiReducer';
export balancesReducer from './balancesReducer';
export blockchainReducer from './blockchainReducer';
-export compilerReducer from './compilerReducer';
+export workerReducer from './workerReducer';
export imagesReducer from './imagesReducer';
export personalReducer from './personalReducer';
export signerReducer from './signerReducer';
diff --git a/js/src/redux/providers/signerMiddleware.js b/js/src/redux/providers/signerMiddleware.js
index 018e01e59..ba51d3426 100644
--- a/js/src/redux/providers/signerMiddleware.js
+++ b/js/src/redux/providers/signerMiddleware.js
@@ -17,7 +17,7 @@
import * as actions from './signerActions';
import { inHex } from '~/api/format/input';
-import { Wallet } from '../../util/wallet';
+import { Signer } from '../../util/signer';
export default class SignerMiddleware {
constructor (api) {
@@ -58,6 +58,7 @@ export default class SignerMiddleware {
promise
.then((txHash) => {
console.log('confirmRequest', id, txHash);
+
if (!txHash) {
store.dispatch(actions.errorConfirmRequest({ id, err: 'Unable to confirm.' }));
return;
@@ -73,33 +74,49 @@ export default class SignerMiddleware {
// Sign request in-browser
const transaction = payload.sendTransaction || payload.signTransaction;
+
if (wallet && transaction) {
- (transaction.nonce.isZero()
+ const noncePromise = transaction.nonce.isZero()
? this._api.parity.nextNonce(transaction.from)
- : Promise.resolve(transaction.nonce)
- ).then(nonce => {
- let txData = {
- to: inHex(transaction.to),
- nonce: inHex(transaction.nonce.isZero() ? nonce : transaction.nonce),
- gasPrice: inHex(transaction.gasPrice),
- gasLimit: inHex(transaction.gas),
- value: inHex(transaction.value),
- data: inHex(transaction.data)
- };
+ : Promise.resolve(transaction.nonce);
- try {
- // NOTE: Derving the key takes significant amount of time,
- // make sure to display some kind of "in-progress" state.
- const signer = Wallet.fromJson(wallet, password);
- const rawTx = signer.signTransaction(txData);
+ const { worker } = store.getState().worker;
- handlePromise(this._api.signer.confirmRequestRaw(id, rawTx));
- } catch (error) {
- console.error(error);
+ const signerPromise = worker && worker._worker.state === 'activated'
+ ? worker
+ .postMessage({
+ action: 'getSignerSeed',
+ data: { wallet, password }
+ })
+ .then((result) => {
+ const seed = Buffer.from(result.data);
+ return new Signer(seed);
+ })
+ : Signer.fromJson(wallet, password);
+
+ // NOTE: Derving the key takes significant amount of time,
+ // make sure to display some kind of "in-progress" state.
+ return Promise
+ .all([ signerPromise, noncePromise ])
+ .then(([ signer, nonce ]) => {
+ const txData = {
+ to: inHex(transaction.to),
+ nonce: inHex(transaction.nonce.isZero() ? nonce : transaction.nonce),
+ gasPrice: inHex(transaction.gasPrice),
+ gasLimit: inHex(transaction.gas),
+ value: inHex(transaction.value),
+ data: inHex(transaction.data)
+ };
+
+ return signer.signTransaction(txData);
+ })
+ .then((rawTx) => {
+ return handlePromise(this._api.signer.confirmRequestRaw(id, rawTx));
+ })
+ .catch((error) => {
+ console.error(error.message);
store.dispatch(actions.errorConfirmRequest({ id, err: error.message }));
- }
- });
- return;
+ });
}
handlePromise(this._api.signer.confirmRequest(id, { gas, gasPrice }, password));
diff --git a/js/src/redux/providers/status.js b/js/src/redux/providers/status.js
index ef4c09224..6d0e24c6b 100644
--- a/js/src/redux/providers/status.js
+++ b/js/src/redux/providers/status.js
@@ -125,12 +125,13 @@ export default class Status {
this._store.dispatch(statusCollection(status));
this._status = status;
}
+
+ nextTimeout();
})
.catch((error) => {
console.error('_pollStatus', error);
+ nextTimeout();
});
-
- nextTimeout();
}
/**
diff --git a/js/src/redux/providers/worker.js b/js/src/redux/providers/worker.js
new file mode 100644
index 000000000..35ca0f173
--- /dev/null
+++ b/js/src/redux/providers/worker.js
@@ -0,0 +1,68 @@
+// Copyright 2015, 2016 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 .
+
+import PromiseWorker from 'promise-worker';
+import runtime from 'serviceworker-webpack-plugin/lib/runtime';
+
+import { setWorker } from './workerActions';
+
+function getWorker () {
+ // Setup the Service Worker
+ if ('serviceWorker' in navigator) {
+ return runtime
+ .register()
+ .then(() => navigator.serviceWorker.ready)
+ .then((registration) => {
+ const worker = registration.active;
+ worker.controller = registration.active;
+
+ return new PromiseWorker(worker);
+ });
+ }
+
+ return Promise.reject('Service Worker is not available in your browser.');
+}
+
+export const setupWorker = (store) => {
+ const { dispatch, getState } = store;
+
+ const state = getState();
+ const stateWorker = state.worker.worker;
+
+ if (stateWorker !== undefined && !(stateWorker && stateWorker._worker.state === 'redundant')) {
+ return;
+ }
+
+ getWorker()
+ .then((worker) => {
+ if (worker) {
+ worker._worker.addEventListener('statechange', (event) => {
+ console.warn('worker state changed to', worker._worker.state);
+
+ // Re-install the new Worker
+ if (worker._worker.state === 'redundant') {
+ setupWorker(store);
+ }
+ });
+ }
+
+ dispatch(setWorker(worker));
+ })
+ .catch((error) => {
+ console.error('sw', error);
+ dispatch(setWorker(null));
+ });
+};
diff --git a/js/src/redux/providers/workerActions.js b/js/src/redux/providers/workerActions.js
new file mode 100644
index 000000000..50a877508
--- /dev/null
+++ b/js/src/redux/providers/workerActions.js
@@ -0,0 +1,29 @@
+// Copyright 2015, 2016 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 .
+
+export function setWorker (worker) {
+ return {
+ type: 'setWorker',
+ worker
+ };
+}
+
+export function setError (error) {
+ return {
+ type: 'setError',
+ error
+ };
+}
diff --git a/js/src/redux/providers/compilerReducer.js b/js/src/redux/providers/workerReducer.js
similarity index 94%
rename from js/src/redux/providers/compilerReducer.js
rename to js/src/redux/providers/workerReducer.js
index e23bf3b16..27144e11b 100644
--- a/js/src/redux/providers/compilerReducer.js
+++ b/js/src/redux/providers/workerReducer.js
@@ -24,7 +24,7 @@ const initialState = {
export default handleActions({
setWorker (state, action) {
const { worker } = action;
- return Object.assign({}, state, { worker });
+ return Object.assign({}, state, { worker: worker || null });
},
setError (state, action) {
diff --git a/js/src/redux/reducers.js b/js/src/redux/reducers.js
index aac1ea779..1156d1836 100644
--- a/js/src/redux/reducers.js
+++ b/js/src/redux/reducers.js
@@ -19,7 +19,7 @@ import { routerReducer } from 'react-router-redux';
import {
apiReducer, balancesReducer, blockchainReducer,
- compilerReducer, imagesReducer, personalReducer,
+ workerReducer, imagesReducer, personalReducer,
signerReducer, statusReducer as nodeStatusReducer,
snackbarReducer, walletReducer
} from './providers';
@@ -40,12 +40,12 @@ export default function () {
balances: balancesReducer,
certifications: certificationsReducer,
blockchain: blockchainReducer,
- compiler: compilerReducer,
images: imagesReducer,
nodeStatus: nodeStatusReducer,
personal: personalReducer,
signer: signerReducer,
snackbar: snackbarReducer,
- wallet: walletReducer
+ wallet: walletReducer,
+ worker: workerReducer
});
}
diff --git a/js/src/redux/store.js b/js/src/redux/store.js
index dc043e242..9924aa461 100644
--- a/js/src/redux/store.js
+++ b/js/src/redux/store.js
@@ -20,6 +20,7 @@ import initMiddleware from './middleware';
import initReducers from './reducers';
import { load as loadWallet } from './providers/walletActions';
+import { setupWorker } from './providers/worker';
import {
Balances as BalancesProvider,
@@ -43,6 +44,7 @@ export default function (api, browserHistory) {
new StatusProvider(store, api).start();
store.dispatch(loadWallet(api));
+ setupWorker(store);
return store;
}
diff --git a/js/src/serviceWorker.js b/js/src/serviceWorker.js
index 136e6a6b7..3fdfc02ac 100644
--- a/js/src/serviceWorker.js
+++ b/js/src/serviceWorker.js
@@ -15,6 +15,7 @@
// along with Parity. If not, see .
import registerPromiseWorker from 'promise-worker/register';
+import { Signer } from '~/util/signer';
import SolidityUtils from '~/util/solidity';
const CACHE_NAME = 'parity-cache-v1';
@@ -93,12 +94,21 @@ function handleMessage (message) {
case 'setFiles':
return setFiles(message.data);
+ case 'getSignerSeed':
+ return getSignerSeed(message.data);
+
default:
console.warn(`unknown action "${message.action}"`);
return null;
}
}
+function getSignerSeed (data) {
+ console.log('deriving seed from service-worker');
+ const { wallet, password } = data;
+ return Signer.getSeed(wallet, password);
+}
+
function compile (data) {
const { build } = data;
diff --git a/js/src/ui/MethodDecoding/methodDecoding.css b/js/src/ui/MethodDecoding/methodDecoding.css
index adb899e1c..c782d6ce7 100644
--- a/js/src/ui/MethodDecoding/methodDecoding.css
+++ b/js/src/ui/MethodDecoding/methodDecoding.css
@@ -38,6 +38,10 @@
justify-content: center;
}
+.details {
+ line-height: 1.75em;
+}
+
.details,
.gasDetails {
color: #aaa;
diff --git a/js/src/ui/MethodDecoding/methodDecoding.js b/js/src/ui/MethodDecoding/methodDecoding.js
index 59704a731..693ae60b5 100644
--- a/js/src/ui/MethodDecoding/methodDecoding.js
+++ b/js/src/ui/MethodDecoding/methodDecoding.js
@@ -196,7 +196,7 @@ class MethodDecoding extends Component {
: text.slice(0, 50) + '...';
return (
-
+
with the
{
+ return new Signer(seed);
+ });
+ }
+
+ static getSeed (json, password) {
+ try {
+ const seed = Signer.getSyncSeed(json, password);
+ return Promise.resolve(seed);
+ } catch (error) {
+ return Promise.reject(error);
+ }
+ }
+
+ static getSyncSeed (json, password) {
if (json.version !== 3) {
throw new Error('Only V3 wallets are supported');
}
@@ -43,15 +60,17 @@ export class Wallet {
if (kdfparams.prf !== 'hmac-sha256') {
throw new Error('Unsupported parameters to PBKDF2');
}
+
derivedKey = pbkdf2Sync(pwd, salt, kdfparams.c, kdfparams.dklen, 'sha256');
} else {
throw new Error('Unsupported key derivation scheme');
}
const ciphertext = Buffer.from(json.crypto.ciphertext, 'hex');
- let mac = sha3(Buffer.concat([derivedKey.slice(16, 32), ciphertext]));
+ const mac = sha3(Buffer.concat([derivedKey.slice(16, 32), ciphertext]));
+
if (mac !== inHex(json.crypto.mac)) {
- throw new Error('Key derivation failed - possibly wrong passphrase');
+ throw new Error('Key derivation failed - possibly wrong password');
}
const decipher = createDecipheriv(
@@ -59,6 +78,7 @@ export class Wallet {
derivedKey.slice(0, 16),
Buffer.from(json.crypto.cipherparams.iv, 'hex')
);
+
let seed = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
while (seed.length < 32) {
@@ -66,7 +86,7 @@ export class Wallet {
seed = Buffer.concat([nullBuff, seed]);
}
- return new Wallet(seed);
+ return seed;
}
constructor (seed) {
diff --git a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/transactionPendingFormConfirm.js b/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/transactionPendingFormConfirm.js
index 99bd1c5f3..45eb3e5dd 100644
--- a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/transactionPendingFormConfirm.js
+++ b/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/transactionPendingFormConfirm.js
@@ -77,13 +77,28 @@ class TransactionPendingFormConfirm extends Component {
}
}
+ getPasswordHint () {
+ const { account } = this.props;
+ const accountHint = account && account.meta && account.meta.passwordHint;
+
+ if (accountHint) {
+ return accountHint;
+ }
+
+ const { wallet } = this.state;
+ const walletHint = wallet && wallet.meta && wallet.meta.passwordHint;
+
+ return walletHint || null;
+ }
+
render () {
const { account, address, isSending } = this.props;
const { password, wallet, walletError } = this.state;
const isExternal = !account.uuid;
- const passwordHint = account.meta && account.meta.passwordHint
- ? ((hint) { account.meta.passwordHint }
)
+ const passwordHintText = this.getPasswordHint();
+ const passwordHint = passwordHintText
+ ? ((hint) { passwordHintText }
)
: null;
const isWalletOk = !isExternal || (walletError === null && wallet !== null);
@@ -170,12 +185,26 @@ class TransactionPendingFormConfirm extends Component {
}
onKeySelect = (event) => {
+ // Check that file have been selected
+ if (event.target.files.length === 0) {
+ return this.setState({
+ wallet: null,
+ walletError: null
+ });
+ }
+
const fileReader = new FileReader();
fileReader.onload = (e) => {
try {
const wallet = JSON.parse(e.target.result);
+ try {
+ if (wallet && typeof wallet.meta === 'string') {
+ wallet.meta = JSON.parse(wallet.meta);
+ }
+ } catch (e) {}
+
this.setState({
wallet,
walletError: null
diff --git a/js/src/views/WriteContract/writeContract.js b/js/src/views/WriteContract/writeContract.js
index c013775a1..8a3ddf3d1 100644
--- a/js/src/views/WriteContract/writeContract.js
+++ b/js/src/views/WriteContract/writeContract.js
@@ -18,7 +18,6 @@ import React, { PropTypes, Component } from 'react';
import { observer } from 'mobx-react';
import { MenuItem, Toggle } from 'material-ui';
import { connect } from 'react-redux';
-import { bindActionCreators } from 'redux';
import CircularProgress from 'material-ui/CircularProgress';
import moment from 'moment';
import { throttle } from 'lodash';
@@ -32,8 +31,6 @@ import SendIcon from 'material-ui/svg-icons/content/send';
import { Actionbar, ActionbarExport, ActionbarImport, Button, Editor, Page, Select, Input } from '~/ui';
import { DeployContract, SaveContract, LoadContract } from '~/modals';
-import { setupWorker } from '~/redux/providers/compilerActions';
-
import WriteContractStore from './writeContractStore';
import styles from './writeContract.css';
@@ -42,7 +39,6 @@ class WriteContract extends Component {
static propTypes = {
accounts: PropTypes.object.isRequired,
- setupWorker: PropTypes.func.isRequired,
worker: PropTypes.object,
workerError: PropTypes.any
};
@@ -55,8 +51,7 @@ class WriteContract extends Component {
};
componentWillMount () {
- const { setupWorker, worker } = this.props;
- setupWorker();
+ const { worker } = this.props;
if (worker !== undefined) {
this.store.setWorker(worker);
@@ -575,17 +570,10 @@ class WriteContract extends Component {
function mapStateToProps (state) {
const { accounts } = state.personal;
- const { worker, error } = state.compiler;
+ const { worker, error } = state.worker;
return { accounts, worker, workerError: error };
}
-function mapDispatchToProps (dispatch) {
- return bindActionCreators({
- setupWorker
- }, dispatch);
-}
-
export default connect(
- mapStateToProps,
- mapDispatchToProps
+ mapStateToProps
)(WriteContract);