on master help sign in
Add alternate HD path derivation for Trezor.

Also fixes Trezor transaction signing.

commita64a26cd181e15a160ff120368aad1f7c8108411 authorJared Tobin <jared@jtobin.io> dateFri 18 Jan 2019, 17:26 (8 years ago) parent6da95c5
14 files changed+94−237
src/Bridge.js+11−4 src/components/StatelessTransaction.js+1 src/components/Transaction/Signer.js+3−2 src/components/Transaction/StatelessSigner.js−194 src/lib/trezor.js+2−2 src/lib/txn.js+4 src/views/AcceptTransfer.js+1 src/views/CancelTransfer.js+1 src/views/CreateGalaxy.js+1 src/views/IssueChild.js+1 src/views/Mnemonic.js+2−1 src/views/SetProxy.js+1 src/views/Transfer.js+1 src/views/Trezor.js+65−34
src/Bridge.js+11−4
@@ -9,7 +9,7 @@ import { Container, Row, Col } from './components/Base'
99
1010import { ROUTE_NAMES, router } from './lib/router'
1111import { NETWORK_NAMES } from './lib/network'
12−import { WALLET_NAMES } from './lib/wallet'
12+import { WALLET_NAMES, DEFAULT_HD_PATH } from './lib/wallet'
1313import { BRIDGE_ERROR } from './lib/error'
1414
1515class Bridge extends React.Component {
@@ -27,6 +27,7 @@ class Bridge extends React.Component {
2727 // wallet
2828 walletType: WALLET_NAMES.MNEMONIC,
2929 wallet: Maybe.Nothing(),
30+ walletHdPath: DEFAULT_HD_PATH,
3031 // urbit wallet-related
3132 urbitWallet: Maybe.Nothing(),
3233 authMnemonic: Maybe.Nothing(),
@@ -44,6 +45,7 @@ class Bridge extends React.Component {
4445 this.setNetwork = this.setNetwork.bind(this)
4546 this.setWalletType = this.setWalletType.bind(this)
4647 this.setWallet = this.setWallet.bind(this)
48+ this.setWalletHdPath = this.setWalletHdPath.bind(this)
4749 this.setUrbitWallet = this.setUrbitWallet.bind(this)
4850 this.setAuthMnemonic = this.setAuthMnemonic.bind(this)
4951 this.setPointCursor = this.setPointCursor.bind(this)
@@ -142,9 +144,11 @@ class Bridge extends React.Component {
142144 }
143145
144146 setWallet(wallet) {
145− this.setState({
146− wallet: wallet
147− })
147+ this.setState({ wallet })
148+ }
149+
150+ setWalletHdPath(walletHdPath) {
151+ this.setState({ walletHdPath })
148152 }
149153
150154 setUrbitWallet(urbitWallet) {
@@ -176,6 +180,7 @@ class Bridge extends React.Component {
176180 routeCrumbs,
177181 networkType,
178182 walletType,
183+ walletHdPath,
179184 wallet,
180185 urbitWallet,
181186 authMnemonic,
@@ -214,8 +219,10 @@ class Bridge extends React.Component {
214219 contracts={ contracts }
215220 // wallet
216221 setWalletType={ this.setWalletType }
222+ setWalletHdPath={ this.setWalletHdPath }
217223 setWallet={ this.setWallet }
218224 walletType={ walletType }
225+ walletHdPath={ walletHdPath }
219226 wallet={ wallet }
220227 // urbit wallet
221228 urbitWallet={ urbitWallet }
src/components/StatelessTransaction.js+1
@@ -113,6 +113,7 @@ const StatelessTransaction = props => {
113113 onClick={ () => signTransaction({
114114 wallet: props.wallet,
115115 walletType: props.walletType,
116+ walletHdPath: props.walletHdPath,
116117 txn: props.txn,
117118 setStx: props.setStx,
118119 nonce: props.nonce,
src/components/Transaction/Signer.js+3−2
@@ -76,7 +76,8 @@ class Signer extends React.Component {
7676 }
7777
7878 async signTransaction(signingArgs) {
79− const { wallet, walletType, txn, setSignedTransaction } = this.props
79+ const { wallet, walletType, txn, walletHdPath } = this.props
80+ const { setSignedTransaction } = this.props
8081
8182 let { nonce, chainId, gasPrice, gasLimit } = signingArgs
8283 nonce = this.toHex(nonce)
@@ -106,7 +107,7 @@ class Signer extends React.Component {
106107 if (walletType === WALLET_NAMES.LEDGER) {
107108 await ledgerSignTransaction(stx)
108109 } else if (walletType === WALLET_NAMES.TREZOR) {
109− await trezorSignTransaction(stx)
110+ await trezorSignTransaction(stx, walletHdPath)
110111 } else {
111112 stx.sign(sec)
112113 }
src/components/Transaction/StatelessSigner.jsdeleted−194
@@ -1,194 +0,0 @@
1−import Tx from 'ethereumjs-tx'
2−import React from 'react'
3−import Web3 from 'web3'
4−import { Button } from '../Base'
5−import { Input, InnerLabel } from '../Base'
6−
7−import { ledgerSignTransaction } from '../../lib/ledger'
8−import { WALLET_NAMES, addressFromSecp256k1Public } from '../../lib/wallet'
9−import { BRIDGE_ERROR } from '../../lib/error'
10−
11−class Signer extends React.Component {
12− constructor(props) {
13− super(props)
14−
15− // this.state = {
16− // nonce: '',
17− // gasPrice: '5',
18− // chainId: '',
19− // gasLimit: '600000'
20− // }
21−
22− const dummy = new Web3()
23− this.toHex = dummy.utils.toHex
24− this.toWei = dummy.utils.toWei
25− this.fromWei = dummy.utils.fromWei
26−
27− this.handleNonceInput = this.handleNonceInput.bind(this)
28− this.handleChainIdInput = this.handleChainIdInput.bind(this)
29− this.handleGasPriceInput = this.handleGasPriceInput.bind(this)
30− this.handleGasLimitInput = this.handleGasLimitInput.bind(this)
31− this.signTransaction = this.signTransaction.bind(this)
32− }
33−
34− componentDidMount() {
35− const { web3, wallet } = this.props
36−
37− const addr = wallet.matchWith({
38− Just: (wal) => addressFromSecp256k1Public(wal.value.publicKey),
39− Nothing: () => {
40− throw BRIDGE_ERROR.MISSING_WALLET
41− }
42− })
43−
44− web3.map(w3 =>
45− this.getTxnInfo(w3, addr)
46− .then(txInfo => this.setState(txInfo))
47− )
48− }
49−
50− getTxnInfo = async (web3, addr) => {
51− let nonce = await web3.eth.getTransactionCount(addr)
52− let chainId = await web3.eth.net.getId()
53− let gasPrice = await web3.eth.getGasPrice()
54− return {
55− nonce: nonce,
56− chainId: chainId,
57− gasPrice: this.fromWei(gasPrice, 'gwei')
58− }
59− }
60−
61− handleNonceInput(nonce) {
62− this.setState({ nonce })
63− }
64−
65− handleChainIdInput(chainId) {
66− this.setState({ chainId })
67− }
68−
69− handleGasPriceInput(gasPrice) {
70− this.setState({ gasPrice })
71− }
72−
73− handleGasLimitInput(gasLimit) {
74− this.setState({ gasLimit })
75− }
76−
77− async signTransaction(signingArgs) {
78− const { wallet, walletType, txn, setSignedTransaction } = this.props
79−
80− let { nonce, chainId, gasPrice, gasLimit } = signingArgs
81− nonce = this.toHex(nonce)
82− chainId = this.toHex(chainId)
83− gasPrice = this.toHex(this.toWei(gasPrice, 'gwei'))
84− gasLimit = this.toHex(gasLimit)
85−
86− const wal = wallet.matchWith({
87− Just: (w) => w.value,
88− Nothing: () => {
89− throw BRIDGE_ERROR.MISSING_WALLET
90− }
91− })
92−
93− const sec = wal.privateKey
94−
95− const utx = txn.matchWith({
96− Just: (tx) =>
97− Object.assign(tx.value, { nonce, chainId, gasPrice, gasLimit }),
98− Nothing: () => {
99− throw BRIDGE_ERROR.MISSING_TXN
100− }
101− })
102−
103− const stx = new Tx(utx)
104−
105− if (walletType === WALLET_NAMES.LEDGER) {
106− await ledgerSignTransaction(stx)
107− } else {
108− stx.sign(sec)
109− }
110−
111− setSignedTransaction(stx)
112− }
113−
114− render() {
115−
116− const { state, props } = this
117−
118− const signingArgs = {
119− nonce: props.nonce,
120− gasPrice: props.gasPrice,
121− gasLimit: props.gasLimit,
122− chainId: props.chainId,
123− }
124−
125−
126−
127− return (
128− <React.Fragment>
129− <Input
130− className='mono mt-4'
131− prop-size='md'
132− prop-format='innerLabel'
133− value={ props.gasPrice }
134− onChange={ v => this.handleGasPriceInput(v) }>
135− <InnerLabel>
136− { 'Gas Price (gwei)' }
137− </InnerLabel>
138− </Input>
139−
140− <Input
141− className='mono mt-4'
142− prop-size='md'
143− prop-format='innerLabel'
144− value={ props.gasLimit }
145− onChange={ v => this.handleGasLimitInput(v) }>
146− <InnerLabel>
147− { 'Gas Limit' }
148− </InnerLabel>
149− </Input>
150−
151− {
152− props.web3.matchWith({
153− Just: () => <div />,
154− Nothing: () =>
155− <React.Fragment>
156− <Input
157− className='mono'
158− prop-size='md'
159− prop-format='innerLabel'
160− value={ props.nonce }
161− onChange={ v => this.handleNonceInput(v) }>
162− <InnerLabel>
163− { 'Nonce' }
164− </InnerLabel>
165− </Input>
166−
167− <Input
168− className='mono mt-4 mb-8'
169− prop-size='md'
170− prop-format='innerLabel'
171− value={ props.chainId }
172− onChange={ v => this.handleChainIdInput(v) }>
173− <InnerLabel>
174− { 'Chain ID' }
175− </InnerLabel>
176− </Input>
177−
178− </React.Fragment>
179− })
180− }
181−
182− <Button
183− disabled={ props.disabled }
184− prop-size={'lg wide'}
185− prop-color={this.props['prop-color']}
186− onClick={ () => this.signTransaction(signingArgs) }>
187− { 'Sign Transaction' }
188− </Button>
189− </React.Fragment>
190− )
191− }
192−}
193−
194−export default Signer
src/lib/trezor.js+2−2
@@ -7,7 +7,7 @@ const formatChainId = val =>
77 ? val
88 : parseInt(val.slice(2), 16) // assume 0x-prefixed hex string
99
10−const trezorSignTransaction = async txn => {
10+const trezorSignTransaction = async (txn, hdpath) => {
1111 const trezorFormattedTxn = {
1212 to: txn.to.toString('hex'),
1313 value: txn.value.toString('hex'),
@@ -19,7 +19,7 @@ const trezorSignTransaction = async txn => {
1919 }
2020
2121 const sig = await TrezorConnect.ethereumSignTransaction({
22− path: TREZOR_BASE_PATH,
22+ path: hdpath,
2323 transaction: trezorFormattedTxn
2424 })
2525
src/lib/txn.js+4
@@ -6,6 +6,7 @@ import Web3 from 'web3'
66
77import { BRIDGE_ERROR } from '../lib/error'
88import { ledgerSignTransaction } from '../lib/ledger'
9+import { trezorSignTransaction } from '../lib/trezor'
910import {
1011 WALLET_NAMES,
1112 addHexPrefix
@@ -76,6 +77,7 @@ const signTransaction = async config => {
7677 let {
7778 wallet,
7879 walletType,
80+ walletHdPath,
7981 txn,
8082 setStx,
8183 nonce,
@@ -110,6 +112,8 @@ const signTransaction = async config => {
110112
111113 if (walletType === WALLET_NAMES.LEDGER) {
112114 await ledgerSignTransaction(stx)
115+ } else if (walletType === WALLET_NAMES.TREZOR) {
116+ await trezorSignTransaction(stx, walletHdPath)
113117 } else {
114118 stx.sign(sec)
115119 }
src/views/AcceptTransfer.js+1
@@ -303,6 +303,7 @@ class AcceptTransfer extends React.Component {
303303 contracts={props.contracts}
304304 wallet={props.wallet}
305305 walletType={props.walletType}
306+ walletHdPath={props.walletHdPath}
306307 // Tx
307308 txn={state.txn}
308309 stx={state.stx}
src/views/CancelTransfer.js+1
@@ -234,6 +234,7 @@ class CancelTransfer extends React.Component {
234234 contracts={props.contracts}
235235 wallet={props.wallet}
236236 walletType={props.walletType}
237+ walletHdPath={props.walletHdPath}
237238 // Tx
238239 txn={state.txn}
239240 stx={state.stx}
src/views/CreateGalaxy.js+1
@@ -250,6 +250,7 @@ class CreateGalaxy extends React.Component {
250250 contracts={props.contracts}
251251 wallet={props.wallet}
252252 walletType={props.walletType}
253+ walletHdPath={props.walletHdPath}
253254 // Tx
254255 txApproval={state.txApproval}
255256 txn={state.txn}
src/views/IssueChild.js+1
@@ -461,6 +461,7 @@ class IssueChild extends React.Component {
461461 contracts={props.contracts}
462462 wallet={props.wallet}
463463 walletType={props.walletType}
464+ walletHdPath={props.walletHdPath}
464465 // Tx
465466 txn={state.txn}
466467 stx={state.stx}
src/views/Mnemonic.js+2−1
@@ -59,10 +59,11 @@ class Mnemonic extends React.Component {
5959 }
6060
6161 attemptWalletDerivation(mnemonic, hdpath) {
62− const { setWallet, setAuthMnemonic } = this.props
62+ const { setWallet, setAuthMnemonic, setWalletHdPath } = this.props
6363 const wallet = walletFromMnemonic(mnemonic, hdpath)
6464 setWallet(wallet)
6565 setAuthMnemonic(Maybe.Just(mnemonic))
66+ setWalletHdPath(hdpath)
6667 }
6768
6869 render() {
src/views/SetProxy.js+1
@@ -320,6 +320,7 @@ class SetProxy extends React.Component {
320320 contracts={props.contracts}
321321 wallet={props.wallet}
322322 walletType={props.walletType}
323+ walletHdPath={props.walletHdPath}
323324 // Tx
324325 txn={state.txn}
325326 stx={state.stx}
src/views/Transfer.js+1
@@ -292,6 +292,7 @@ class Transfer extends React.Component {
292292 contracts={props.contracts}
293293 wallet={props.wallet}
294294 walletType={props.walletType}
295+ walletHdPath={props.walletHdPath}
295296 // Tx
296297 txn={state.txn}
297298 stx={state.stx}
src/views/Trezor.js+65−34
@@ -3,6 +3,7 @@ import React from 'react'
33import Maybe from 'folktale/maybe'
44import { Button } from '../components/Base'
55import { Row, Col, H1, P } from '../components/Base'
6+import { Form, InnerLabel, Input } from '../components/Base'
67import TrezorConnect from 'trezor-connect'
78import * as secp256k1 from 'secp256k1'
89
@@ -11,57 +12,87 @@ import { ROUTE_NAMES } from '../lib/router'
1112
1213class Trezor extends React.Component {
1314
14− componentDidMount() {
15− this.pollDevice()
15+ constructor(props) {
16+ super(props)
17+
18+ this.state = {
19+ hdpath: TREZOR_BASE_PATH
20+ }
21+
22+ this.handleHdPathInput = this.handleHdPathInput.bind(this)
23+ this.pollDevice = this.pollDevice.bind(this)
24+ }
25+
26+ handleHdPathInput(hdpath) {
27+ this.setState({ hdpath })
1628 }
1729
1830 async pollDevice() {
19− const { wallet, setWallet } = this.props
20−
21− if (Maybe.Nothing.hasInstance(wallet)) {
22− TrezorConnect.getPublicKey({ path: TREZOR_BASE_PATH })
23− .then(info => {
24− if (info.success === true) {
25− const payload = info.payload
26− const publicKey = Buffer.from(payload.publicKey, 'hex')
27− const chainCode = Buffer.from(payload.chainCode, 'hex')
28− const pub = secp256k1.publicKeyConvert(publicKey, true)
29− const hd = bip32.fromPublicKey(pub, chainCode)
30− setWallet(Maybe.Just(hd))
31− } else {
32− setWallet(Maybe.Nothing())
33− }
34− })
35− }
31+ const { setWallet, setWalletHdPath } = this.props
32+ const { hdpath } = this.state
33+
34+ TrezorConnect.getPublicKey({ path: hdpath })
35+ .then(info => {
36+ if (info.success === true) {
37+ const payload = info.payload
38+ const publicKey = Buffer.from(payload.publicKey, 'hex')
39+ const chainCode = Buffer.from(payload.chainCode, 'hex')
40+ const pub = secp256k1.publicKeyConvert(publicKey, true)
41+ const hd = bip32.fromPublicKey(pub, chainCode)
42+ setWallet(Maybe.Just(hd))
43+ setWalletHdPath(hdpath)
44+ } else {
45+ setWallet(Maybe.Nothing())
46+ }
47+ })
3648 }
3749
3850 render() {
3951 const { pushRoute, popRoute, wallet } = this.props
52+ const { hdpath } = this.state
4053
4154 return (
42−
4355 <Row>
4456 <Col>
4557 <H1>{ 'Authenticate With Your Trezor' }</H1>
4658
4759 <P>
48− { 'Connect and authenticate to your Trezor. If the Trezor ' +
49− 'popup window does not display, reconnect your Trezor and ' +
50− 'navigate back to this page.'
60+ { `Connect and authenticate to your Trezor. If you'd like
61+ to use a custom derivation path, you may enter it below.`
5162 }
5263 </P>
5364
54− <Button
55− disabled={ Maybe.Nothing.hasInstance(wallet) }
56− onClick={
57− () => {
58− popRoute()
59− pushRoute(ROUTE_NAMES.SHIPS)
60− }
61− }
62− >
63− { 'Continue →' }
64− </Button>
65+ <Form>
66+ <Input
67+ className='pt-8'
68+ prop-size='md'
69+ prop-format='innerLabel'
70+ name='hdpath'
71+ value={ hdpath }
72+ autocomplete='off'
73+ onChange={ this.handleHdPathInput }>
74+ <InnerLabel>{'HD Path'}</InnerLabel>
75+ </Input>
76+
77+ <Button
78+ className={'mt-8'}
79+ onClick={ this.pollDevice }>
80+ { 'Authenticate →' }
81+ </Button>
82+
83+ <Button
84+ className={'mt-8'}
85+ disabled={ Maybe.Nothing.hasInstance(wallet) }
86+ onClick={
87+ () => {
88+ popRoute()
89+ pushRoute(ROUTE_NAMES.SHIPS)
90+ }
91+ }>
92+ { 'Continue →' }
93+ </Button>
94+
95+ </Form>
6596
6697 </Col>
6798 </Row>