Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

UI support for hardware wallets #4539

Merged
merged 73 commits into from
Mar 2, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
b4c6de5
Add parity_hardwareAccountsInfo
jacogr Feb 14, 2017
03bfa10
Ledger Promise interface wrapper
jacogr Feb 14, 2017
b39dac5
Initial hardwarestore
jacogr Feb 14, 2017
572cf3d
Move ~/views/historyStore to ~/mobx
jacogr Feb 14, 2017
3c25135
Merge branch 'master' into jg-hardware
jacogr Feb 14, 2017
f1b18b7
split scanLedger
jacogr Feb 14, 2017
b099e3f
test createEntry
jacogr Feb 14, 2017
06107fe
Also scan via parity_hardwareAccountsInfo
jacogr Feb 14, 2017
fd4cd81
Explanation for scanning options
jacogr Feb 14, 2017
072ad3f
react-intl-inify tooltips
jacogr Feb 14, 2017
23f188a
add hwstore
jacogr Feb 14, 2017
8ac0d34
Listen for hw walet updates
jacogr Feb 14, 2017
7e5b3c2
Return arrays from scanning
jacogr Feb 14, 2017
6d0d261
Readability
jacogr Feb 14, 2017
908278d
Merge branch 'master' into jg-hardware
jacogr Feb 14, 2017
78f418c
add u2f-api polyfill
jacogr Feb 14, 2017
a3c8534
check response.errorCode
jacogr Feb 14, 2017
4d0f53a
Support hardware types in state.personal
jacogr Feb 15, 2017
4e4b732
Tooltips (to be split into sep. PR)
jacogr Feb 15, 2017
fde81f2
Tooltips support intl strings
jacogr Feb 15, 2017
6d9abac
FormattedMessage for strings to Tooltip
jacogr Feb 15, 2017
817b3e7
Fix TabBar tooltip display
jacogr Feb 15, 2017
4c7d4f3
Merge branch 'jg-tooltips-intl' into jg-hardware
jacogr Feb 15, 2017
7878983
signLedger
jacogr Feb 15, 2017
b955fad
Merge branch 'master' into jg-hardware
jacogr Feb 15, 2017
f54ba14
Use wallets as an object map
jacogr Feb 15, 2017
2520f36
PendingForm -> FormattedMessage
jacogr Feb 15, 2017
85b5894
Pending form doesn't render password for hardware
jacogr Feb 15, 2017
0dea5f6
Groundwork for JS API signing
jacogr Feb 15, 2017
0a6dbae
Merge branch 'master' into jg-hardware
jacogr Feb 15, 2017
8ea0752
Show hardware accounts in list
jacogr Feb 15, 2017
61fe906
Cleanup rendering conditions
jacogr Feb 15, 2017
fee2c13
Update RequestPending rendering tests (verification)
jacogr Feb 15, 2017
59312f9
Tests for extended signer middleware
jacogr Feb 15, 2017
2ab7484
Merge branch 'master' into jg-hardware
jacogr Feb 16, 2017
3a3e60f
sign properly & handle response, error
jacogr Feb 16, 2017
dc53bb9
Align outputs between Parity & Ledger u2f
jacogr Feb 16, 2017
42c633c
Ledger returns checksummed addresses
jacogr Feb 16, 2017
969560c
Update ethereum-tx for EIP155 support
jacogr Feb 16, 2017
c13d4fb
Update construction of tx
jacogr Feb 16, 2017
32807cf
Updates after sanity checks (thanks @tomusdrw)
jacogr Feb 16, 2017
5af9b1c
Allow display for disabled IdentityIcon
jacogr Feb 16, 2017
4f78550
Disabled accounts
jacogr Feb 16, 2017
2e49346
Disabled auto-disabling
jacogr Feb 16, 2017
785e51d
Password button ebaled for hardware
jacogr Feb 16, 2017
d93735f
Don't display password hint for hardware
jacogr Feb 16, 2017
063f69e
Disable non-applicable options when not connected
jacogr Feb 16, 2017
afc7f8e
Merge branch 'master' into jg-hardware
jacogr Feb 16, 2017
3ec51f6
Fix failing test
jacogr Feb 16, 2017
bae33d0
Confirmation via ledger (u2f)
jacogr Feb 16, 2017
1737f15
Confirm on device message
jacogr Feb 16, 2017
219bd83
Cleanups & support checks
jacogr Feb 16, 2017
d626929
Merge branch 'master' into jg-hardware
jacogr Feb 16, 2017
daadff3
Mark u2f as unsupported (until https)
jacogr Feb 16, 2017
455842f
rewording
jacogr Feb 16, 2017
98705b4
Pass account & disabled flags
jacogr Feb 17, 2017
b78bbbe
Render attach device message
jacogr Feb 17, 2017
b967f7f
Merge branch 'master' into jg-hardware
jacogr Feb 17, 2017
98a1cf2
Use isConnected for checking availability
jacogr Feb 17, 2017
a983e7a
Show hardware accounts in defaults list
jacogr Feb 17, 2017
365b219
Pass signerstore
jacogr Feb 17, 2017
2ca3ea5
Merge branch 'master' into jg-hardware
jacogr Feb 17, 2017
4aa2244
Update u2f to correct version
jacogr Feb 17, 2017
45b9ae9
Merge branch 'master' into jg-hardware
jacogr Feb 17, 2017
1e6cb64
remove debug u2f lib
jacogr Feb 17, 2017
8679e1d
Update test (prop name change)
jacogr Feb 17, 2017
38aa951
Add ETC path (future work)
jacogr Feb 17, 2017
afbc989
Merge branch 'master' into jg-hardware
jacogr Feb 20, 2017
67116d3
new Buffer -> Buffer.from (thanks @derhuerst)
jacogr Feb 21, 2017
b214c5f
Merge branch 'master' into jg-hardware
jacogr Feb 24, 2017
db9f973
Merge branch 'master' into jg-hardware
jacogr Feb 24, 2017
471a233
Merge branch 'master' into jg-hardware
jacogr Mar 2, 2017
ed808b9
Merge branch 'master' into jg-hardware
gavofyork Mar 2, 2017
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: 3 additions & 1 deletion js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@
"debounce": "1.0.0",
"es6-error": "4.0.0",
"es6-promise": "4.0.5",
"ethereumjs-tx": "1.1.4",
"ethereumjs-tx": "1.2.5",
"eventemitter3": "2.0.2",
"file-saver": "1.3.3",
"flat": "2.0.1",
Expand Down Expand Up @@ -200,6 +200,8 @@
"scryptsy": "2.0.0",
"solc": "ngotchac/solc-js",
"store": "1.3.20",
"u2f-api": "0.0.9",
"u2f-api-polyfill": "0.4.3",
"uglify-js": "2.8.2",
"useragent.js": "0.5.6",
"utf8": "2.1.2",
Expand Down
10 changes: 1 addition & 9 deletions js/src/3rdparty/ledger/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,4 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.

import Ledger3 from './vendor/ledger3';
import LedgerEth from './vendor/ledger-eth';

export function create () {
const ledger = new Ledger3('w0w');
const app = new LedgerEth(ledger);

return app;
}
export default from './ledger';
136 changes: 136 additions & 0 deletions js/src/3rdparty/ledger/ledger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// Copyright 2015-2017 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/>.

import 'u2f-api-polyfill';

import BigNumber from 'bignumber.js';
import Transaction from 'ethereumjs-tx';
import u2fapi from 'u2f-api';

import Ledger3 from './vendor/ledger3';
import LedgerEth from './vendor/ledger-eth';

const LEDGER_PATH_ETC = "44’/60’/160720'/0'/0";
const LEDGER_PATH_ETH = "44'/60'/0'/0";
const SCRAMBLE_KEY = 'w0w';

function numberToHex (number) {
return `0x${new BigNumber(number).toString(16)}`;
}

export default class Ledger {
constructor (api, ledger) {
this._api = api;
this._ledger = ledger;

this._isSupported = false;

this.checkJSSupport();
}

// FIXME: Until we have https support from Parity u2f will not work. Here we mark it completely
// as unsupported until a full end-to-end environment is available.
get isSupported () {
return false && this._isSupported;
}

checkJSSupport () {
return u2fapi
.isSupported()
.then((isSupported) => {
console.log('Ledger:checkJSSupport', isSupported);

this._isSupported = isSupported;
});
}

getAppConfiguration () {
return new Promise((resolve, reject) => {
this._ledger.getAppConfiguration((response, error) => {
if (error) {
reject(error);
return;
}

resolve(response);
});
});
}

scan () {
return new Promise((resolve, reject) => {
this._ledger.getAddress(LEDGER_PATH_ETH, (response, error) => {
if (error) {
reject(error);
return;
}

resolve([response.address]);
}, true, false);
});
}

signTransaction (transaction) {
return this._api.net.version().then((_chainId) => {
return new Promise((resolve, reject) => {
const chainId = new BigNumber(_chainId).toNumber();
const tx = new Transaction({
data: transaction.data || transaction.input,
gasPrice: numberToHex(transaction.gasPrice),
gasLimit: numberToHex(transaction.gasLimit),
nonce: numberToHex(transaction.nonce),
to: transaction.to ? transaction.to.toLowerCase() : undefined,
value: numberToHex(transaction.value),
v: Buffer.from([chainId]), // pass the chainId to the ledger
r: Buffer.from([]),
s: Buffer.from([])
});
const rawTransaction = tx.serialize().toString('hex');

this._ledger.signTransaction(LEDGER_PATH_ETH, rawTransaction, (response, error) => {
if (error) {
reject(error);
return;
}

tx.v = Buffer.from(response.v, 'hex');
tx.r = Buffer.from(response.r, 'hex');
tx.s = Buffer.from(response.s, 'hex');

if (chainId !== Math.floor((tx.v[0] - 35) / 2)) {
reject(new Error('Invalid EIP155 signature received from Ledger.'));
return;
}

resolve(`0x${tx.serialize().toString('hex')}`);
});
});
});
}

static create (api, ledger) {
if (!ledger) {
ledger = new LedgerEth(new Ledger3(SCRAMBLE_KEY));
}

return new Ledger(api, ledger);
}
}

export {
LEDGER_PATH_ETC,
LEDGER_PATH_ETH
};
120 changes: 120 additions & 0 deletions js/src/3rdparty/ledger/ledger.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Copyright 2015-2017 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/>.

import sinon from 'sinon';

import Ledger from './';

const TEST_ADDRESS = '0x63Cf90D3f0410092FC0fca41846f596223979195';

let api;
let ledger;
let vendor;

function createApi () {
api = {
net: {
version: sinon.stub().resolves('2')
}
};

return api;
}

function createVendor (error = null) {
vendor = {
getAddress: (path, callback) => {
callback({
address: TEST_ADDRESS
}, error);
},
getAppConfiguration: (callback) => {
callback({}, error);
},
signTransaction: (path, rawTransaction, callback) => {
callback({
v: [39],
r: [0],
s: [0]
}, error);
}
};

return vendor;
}

function create (error) {
ledger = new Ledger(createApi(), createVendor(error));

return ledger;
}

describe('3rdparty/ledger', () => {
beforeEach(() => {
create();

sinon.spy(vendor, 'getAddress');
sinon.spy(vendor, 'getAppConfiguration');
sinon.spy(vendor, 'signTransaction');
});

afterEach(() => {
vendor.getAddress.restore();
vendor.getAppConfiguration.restore();
vendor.signTransaction.restore();
});

describe('getAppConfiguration', () => {
beforeEach(() => {
return ledger.getAppConfiguration();
});

it('calls into getAppConfiguration', () => {
expect(vendor.getAppConfiguration).to.have.been.called;
});
});

describe('scan', () => {
beforeEach(() => {
return ledger.scan();
});

it('calls into getAddress', () => {
expect(vendor.getAddress).to.have.been.called;
});
});

describe('signTransaction', () => {
beforeEach(() => {
return ledger.signTransaction({
data: '0x0',
gasPrice: 20000000,
gasLimit: 1000000,
nonce: 2,
to: '0x63Cf90D3f0410092FC0fca41846f596223979195',
value: 1
});
});

it('retrieves chainId via API', () => {
expect(api.net.version).to.have.been.called;
});

it('calls into signTransaction', () => {
expect(vendor.signTransaction).to.have.been.called;
});
});
});
12 changes: 12 additions & 0 deletions js/src/api/format/output.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,18 @@ export function outLog (log) {
return log;
}

export function outHwAccountInfo (infos) {
return Object
.keys(infos)
.reduce((ret, _address) => {
const address = outAddress(_address);

ret[address] = infos[_address];

return ret;
}, {});
}

export function outNumber (number) {
return new BigNumber(number || 0);
}
Expand Down
12 changes: 11 additions & 1 deletion js/src/api/format/output.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import BigNumber from 'bignumber.js';

import { outBlock, outAccountInfo, outAddress, outChainStatus, outDate, outHistogram, outNumber, outPeer, outPeers, outReceipt, outRecentDapps, outSyncing, outTransaction, outTrace, outVaultMeta } from './output';
import { outBlock, outAccountInfo, outAddress, outChainStatus, outDate, outHistogram, outHwAccountInfo, outNumber, outPeer, outPeers, outReceipt, outRecentDapps, outSyncing, outTransaction, outTrace, outVaultMeta } from './output';
import { isAddress, isBigNumber, isInstanceOf } from '../../../test/types';

describe('api/format/output', () => {
Expand Down Expand Up @@ -163,6 +163,16 @@ describe('api/format/output', () => {
});
});

describe('outHwAccountInfo', () => {
it('returns objects with formatted addresses', () => {
expect(outHwAccountInfo(
{ '0x63cf90d3f0410092fc0fca41846f596223979195': { manufacturer: 'mfg', name: 'type' } }
)).to.deep.equal({
'0x63Cf90D3f0410092FC0fca41846f596223979195': { manufacturer: 'mfg', name: 'type' }
});
});
});

describe('outNumber', () => {
it('returns a BigNumber equalling the value', () => {
const bn = outNumber('0x123456');
Expand Down
8 changes: 7 additions & 1 deletion js/src/api/rpc/parity/parity.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.

import { inAddress, inAddresses, inData, inHex, inNumber16, inOptions, inBlockNumber } from '../../format/input';
import { outAccountInfo, outAddress, outAddresses, outChainStatus, outHistogram, outNumber, outPeers, outRecentDapps, outTransaction, outVaultMeta } from '../../format/output';
import { outAccountInfo, outAddress, outAddresses, outChainStatus, outHistogram, outHwAccountInfo, outNumber, outPeers, outRecentDapps, outTransaction, outVaultMeta } from '../../format/output';

export default class Parity {
constructor (transport) {
Expand Down Expand Up @@ -200,6 +200,12 @@ export default class Parity {
.then(outVaultMeta);
}

hardwareAccountsInfo () {
return this._transport
.execute('parity_hardwareAccountsInfo')
.then(outHwAccountInfo);
}

hashContent (url) {
return this._transport
.execute('parity_hashContent', url);
Expand Down
26 changes: 26 additions & 0 deletions js/src/jsonrpc/interfaces/parity.js
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,32 @@ export default {
}
},

hardwareAccountsInfo: {
section: SECTION_ACCOUNTS,
desc: 'Provides metadata for attached hardware wallets',
params: [],
returns: {
type: Object,
desc: 'Maps account address to metadata.',
details: {
manufacturer: {
type: String,
desc: 'Manufacturer'
},
name: {
type: String,
desc: 'Account name'
}
},
example: {
'0x0024d0c7ab4c52f723f3aaf0872b9ea4406846a4': {
manufacturer: 'Ledger',
name: 'Nano S'
}
}
}
},

listOpenedVaults: {
desc: 'Returns a list of all opened vaults',
params: [],
Expand Down
Loading