This repository has been archived by the owner on Nov 6, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
UI support for hardware wallets (#4539)
* Add parity_hardwareAccountsInfo * Ledger Promise interface wrapper * Initial hardwarestore * Move ~/views/historyStore to ~/mobx * split scanLedger * test createEntry * Also scan via parity_hardwareAccountsInfo * Explanation for scanning options * react-intl-inify tooltips * add hwstore * Listen for hw walet updates * Return arrays from scanning * Readability * add u2f-api polyfill * check response.errorCode * Support hardware types in state.personal * Tooltips (to be split into sep. PR) * Tooltips support intl strings * FormattedMessage for strings to Tooltip * Fix TabBar tooltip display * signLedger * Use wallets as an object map * PendingForm -> FormattedMessage * Pending form doesn't render password for hardware * Groundwork for JS API signing * Show hardware accounts in list * Cleanup rendering conditions * Update RequestPending rendering tests (verification) * Tests for extended signer middleware * sign properly & handle response, error * Align outputs between Parity & Ledger u2f * Ledger returns checksummed addresses * Update ethereum-tx for EIP155 support * Update construction of tx * Updates after sanity checks (thanks @tomusdrw) * Allow display for disabled IdentityIcon * Disabled accounts * Disabled auto-disabling * Password button ebaled for hardware * Don't display password hint for hardware * Disable non-applicable options when not connected * Fix failing test * Confirmation via ledger (u2f) * Confirm on device message * Cleanups & support checks * Mark u2f as unsupported (until https) * rewording * Pass account & disabled flags * Render attach device message * Use isConnected for checking availability * Show hardware accounts in defaults list * Pass signerstore * Update u2f to correct version * remove debug u2f lib * Update test (prop name change) * Add ETC path (future work) * new Buffer -> Buffer.from (thanks @derhuerst)
- Loading branch information
Showing
44 changed files
with
1,650 additions
and
260 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.