on master help sign in
Add alternate HD path derivation for Ledger.
1c4ec7eJared Tobin
src/components/Transaction/Signer.js+1−1
@@ -105,7 +105,7 @@ class Signer extends React.Component {
105 const stx = new Tx(utx)
105 const stx = new Tx(utx)
106
106
107 if (walletType === WALLET_NAMES.LEDGER) {
107 if (walletType === WALLET_NAMES.LEDGER) {
108− await ledgerSignTransaction(stx)
108+ await ledgerSignTransaction(stx, walletHdPath)
109 } else if (walletType === WALLET_NAMES.TREZOR) {
109 } else if (walletType === WALLET_NAMES.TREZOR) {
110 await trezorSignTransaction(stx, walletHdPath)
110 await trezorSignTransaction(stx, walletHdPath)
111 } else {
111 } else {
src/lib/ledger.js+8−2
@@ -3,12 +3,18 @@ import Eth from '@ledgerhq/hw-app-eth'
3
3
4const LEDGER_BASE_PATH = "44'/60'/0'/0/0"
4const LEDGER_BASE_PATH = "44'/60'/0'/0/0"
5
5
6+const chopHdPrefix = str =>
7+ str.slice(0, 2) === "m/"
8+ ? str.slice(2)
9+ : str
10+
6−const ledgerSignTransaction = async txn => {
11+const ledgerSignTransaction = async (txn, hdpath) => {
7 const transport = await Transport.create()
12 const transport = await Transport.create()
8 const eth = new Eth(transport)
13 const eth = new Eth(transport)
14+ const path = chopHdPrefix(hdpath)
9
15
10 const serializedTx = txn.serialize().toString('hex')
16 const serializedTx = txn.serialize().toString('hex')
11− const sig = await eth.signTransaction(LEDGER_BASE_PATH, serializedTx)
17+ const sig = await eth.signTransaction(path, serializedTx)
12
18
13 txn.v = Buffer.from(sig.v, 'hex')
19 txn.v = Buffer.from(sig.v, 'hex')
14 txn.r = Buffer.from(sig.r, 'hex')
20 txn.r = Buffer.from(sig.r, 'hex')
src/lib/txn.js+1−1
@@ -111,7 +111,7 @@ const signTransaction = async config => {
111 const stx = new Tx(utx)
111 const stx = new Tx(utx)
112
112
113 if (walletType === WALLET_NAMES.LEDGER) {
113 if (walletType === WALLET_NAMES.LEDGER) {
114− await ledgerSignTransaction(stx)
114+ await ledgerSignTransaction(stx, walletHdPath)
115 } else if (walletType === WALLET_NAMES.TREZOR) {
115 } else if (walletType === WALLET_NAMES.TREZOR) {
116 await trezorSignTransaction(stx, walletHdPath)
116 await trezorSignTransaction(stx, walletHdPath)
117 } else {
117 } else {
src/views/Ledger.js+78−40
@@ -3,6 +3,7 @@ import React from 'react'
3import Maybe from 'folktale/maybe'
3import Maybe from 'folktale/maybe'
4import { Button } from '../components/Base'
4import { Button } from '../components/Base'
5import { Row, Col, H1, P, H2 } from '../components/Base'
5import { Row, Col, H1, P, H2 } from '../components/Base'
6+import { Form, Input, InnerLabel } from '../components/Base'
6import Transport from '@ledgerhq/hw-transport-u2f'
7import Transport from '@ledgerhq/hw-transport-u2f'
7import Eth from '@ledgerhq/hw-app-eth'
8import Eth from '@ledgerhq/hw-app-eth'
8import * as secp256k1 from 'secp256k1'
9import * as secp256k1 from 'secp256k1'
@@ -10,43 +11,56 @@ import * as secp256k1 from 'secp256k1'
10import { LEDGER_BASE_PATH } from '../lib/ledger'
11import { LEDGER_BASE_PATH } from '../lib/ledger'
11import { ROUTE_NAMES } from '../lib/router'
12import { ROUTE_NAMES } from '../lib/router'
12
13
14+const chopHdPrefix = str =>
15+ str.slice(0, 2) === "m/"
16+ ? str.slice(2)
17+ : str
18+
19+const addHdPrefix = str =>
20+ str.slice(0, 2) === "m/"
21+ ? str
22+ : "m/" + str
23+
13class Ledger extends React.Component {
24class Ledger extends React.Component {
25+ constructor(props) {
26+ super(props)
14
27
15− componentDidMount() {
28+ this.state = {
16− this.pollDevice()
29+ hdpath: LEDGER_BASE_PATH
30+ }
17
31
18− this.pollerId = setInterval(
19− () => this.pollDevice(),
32+ this.pollDevice = this.pollDevice.bind(this)
20− 500
33+ this.handleHdPathInput = this.handleHdPathInput.bind(this)
21− )
22 }
34 }
23
35
24− componentWillUnmount() {
36+ handleHdPathInput(hdpath) {
25− clearInterval(this.pollerId)
37+ this.setState({ hdpath })
26 }
38 }
27
39
28 async pollDevice() {
40 async pollDevice() {
29− const { wallet, setWallet } = this.props
41+ const { setWallet, setWalletHdPath } = this.props
30−
42+ const { hdpath } = this.state
31− if (Maybe.Nothing.hasInstance(wallet)) {
43+
32− const transport = await Transport.create()
44+ const transport = await Transport.create()
33− const eth = new Eth(transport)
45+ const eth = new Eth(transport)
34−
46+ const path = chopHdPrefix(hdpath)
47+
35− eth.getAddress(LEDGER_BASE_PATH, false, true)
48+ eth.getAddress(path, false, true)
36− .then(info => {
49+ .then(info => {
37− const publicKey = Buffer.from(info.publicKey, 'hex')
50+ const publicKey = Buffer.from(info.publicKey, 'hex')
38− const chainCode = Buffer.from(info.chainCode, 'hex')
51+ const chainCode = Buffer.from(info.chainCode, 'hex')
39− const pub = secp256k1.publicKeyConvert(publicKey, true)
52+ const pub = secp256k1.publicKeyConvert(publicKey, true)
40− const hd = bip32.fromPublicKey(pub, chainCode)
53+ const hd = bip32.fromPublicKey(pub, chainCode)
41− setWallet(Maybe.Just(hd))
54+ setWallet(Maybe.Just(hd))
55+ setWalletHdPath(addHdPrefix(hdpath))
42− }, _ => {
56+ }, _ => {
43− setWallet(Maybe.Nothing())
57+ setWallet(Maybe.Nothing())
44− })
58+ })
45− }
46 }
59 }
47
60
48 render() {
61 render() {
49 const { pushRoute, popRoute, wallet } = this.props
62 const { pushRoute, popRoute, wallet } = this.props
63+ const { hdpath } = this.state
50
64
51 return (
65 return (
52
66
@@ -58,10 +72,17 @@ class Ledger extends React.Component {
58
72
59 <P>
73 <P>
60 { `Connect and authenticate to your Ledger, and then open the
74 { `Connect and authenticate to your Ledger, and then open the
61− "Ethereum" application. If you're running on older firmware, make sure the browser
75+ "Ethereum" application. If you're running on older firmware,
62− support" option is turned on.` }
76+ make sure the "browser support" option is turned on. To sign
77+ transactions, you'll also need to enable the "contract data"
78+ option.` }
63 </P>
79 </P>
64
80
81+ <P>
82+ { `If you'd like to use a custom derivation path, you may enter
83+ it below.`
84+ }
85+ </P>
65
86
66 <H2>{'Running on HTTP?'}</H2>
87 <H2>{'Running on HTTP?'}</H2>
67
88
@@ -78,19 +99,36 @@ class Ledger extends React.Component {
78 <li className={'mt-4'}>{'Run '}<code>{'python bridge-https.py'}</code></li>
99 <li className={'mt-4'}>{'Run '}<code>{'python bridge-https.py'}</code></li>
79 </ol>
100 </ol>
80
101
81−
102+ <Form>
103+ <Input
104+ className='pt-8'
105+ prop-size='md'
106+ prop-format='innerLabel'
107+ name='hdpath'
108+ value={ addHdPrefix(hdpath) }
109+ autocomplete='off'
110+ onChange={ this.handleHdPathInput }>
111+ <InnerLabel>{'HD Path'}</InnerLabel>
112+ </Input>
113+
114+ <Button
115+ className={'mt-8'}
116+ onClick={ this.pollDevice }>
117+ { 'Authenticate →' }
118+ </Button>
119+
82− <Button
120+ <Button
83− className={'mt-8'}
121+ className={'mt-8'}
84− disabled={ Maybe.Nothing.hasInstance(wallet) }
122+ disabled={ Maybe.Nothing.hasInstance(wallet) }
85− onClick={
123+ onClick={
86− () => {
124+ () => {
87− popRoute()
125+ popRoute()
88− pushRoute(ROUTE_NAMES.SHIPS)
126+ pushRoute(ROUTE_NAMES.SHIPS)
89− }
127+ }
90− }
128+ }>
91− >
92− { 'Continue →' }
129+ { 'Continue →' }
93− </Button>
130+ </Button>
131+ </Form>
94
132
95 </Col>
133 </Col>
96 </Row>
134 </Row>