on master help sign in
Merge pull request #85 from urbit/gen-keyfile-improvements

remove networkSeed input from setKeys / genKeyfile flows

commit153508be4cfdc1611ccb0746433fac69d51e830f authorChristopher Johnson <chris@cjohnson.io> dateMon 1 Apr 2019, 10:28 (8 years ago) parentee3c56db7de3ed
5 files changed+189−137
src/Bridge.js+21−4 src/style/extend.css+7 src/views/GenKeyfile.js+75−92 src/views/SentTransaction.js+26−1 src/views/SetKeys.js+60−40
src/Bridge.js+21−4
@@ -40,6 +40,7 @@ class Bridge extends React.Component {
4040 this.state = {
4141 // routes
4242 routeCrumbs: Stack([ ROUTE_NAMES.LANDING ]),
43+ routeData: {},
4344 // network
4445 networkType: networkType,
4546 web3: Maybe.Nothing(),
@@ -51,6 +52,7 @@ class Bridge extends React.Component {
5152 // urbit wallet-related
5253 urbitWallet: Maybe.Nothing(),
5354 authMnemonic: Maybe.Nothing(),
55+ networkSeedCache: null,
5456 // point
5557 pointCursor: Maybe.Nothing(),
5658 pointCache: {},
@@ -70,6 +72,7 @@ class Bridge extends React.Component {
7072 this.setAuthMnemonic = this.setAuthMnemonic.bind(this)
7173 this.setPointCursor = this.setPointCursor.bind(this)
7274 this.setTxnHashCursor = this.setTxnHashCursor.bind(this)
75+ this.setNetworkSeedCache = this.setNetworkSeedCache.bind(this)
7376 this.addToPointCache = this.addToPointCache.bind(this)
7477 }
7578
@@ -88,7 +91,7 @@ class Bridge extends React.Component {
8891
8992 // if (process.env.NODE_ENV === 'development') {
9093 //
91− // const socket = 'wss://localhost:8545'
94+ // const socket = 'ws://localhost:8545'
9295 // const provider = new Web3.providers.WebsocketProvider(socket)
9396 // const web3 = new Web3(provider)
9497 // const contracts = azimuth.initContracts(web3, CONTRACT_ADDRESSES.DEV)
@@ -98,7 +101,8 @@ class Bridge extends React.Component {
98101 //
99102 // this.setState({
100103 // routeCrumbs: Stack([
101− // ROUTE_NAMES.CREATE_GALAXY,
104+ // // ROUTE_NAMES.CREATE_GALAXY,
105+ // // ROUTE_NAMES.SHIP,
102106 // ROUTE_NAMES.SHIPS,
103107 // // ROUTE_NAMES.MNEMONIC,
104108 // ROUTE_NAMES.WALLET,
@@ -106,20 +110,22 @@ class Bridge extends React.Component {
106110 // ROUTE_NAMES.LANDING
107111 // ]),
108112 // networkType: NETWORK_NAMES.LOCAL,
113+ // pointCursor: Maybe.Just(0),
109114 // web3: Maybe.Just(web3),
110115 // contracts: Maybe.Just(contracts),
111116 // walletType: WALLET_NAMES.MNEMONIC,
112117 // wallet: walletFromMnemonic(mnemonic, hdpath),
113118 // urbitWallet: Maybe.Nothing(),
114− // authMnemonic: Maybe.Nothing()
119+ // authMnemonic: Maybe.Just('benefit crew supreme gesture quantum web media hazard theory mercy wing kitten')
115120 // })
116121 // }
117122}
118123
119− pushRoute(symbol) {
124+ pushRoute(symbol, routeData) {
120125 if (lodash.includes(ROUTE_NAMES, symbol)) {
121126 this.setState((state, _) => ({
122127 routeCrumbs: state.routeCrumbs.push(symbol),
128+ routeData: routeData
123129 }));
124130
125131 // Scroll to top of page with each route transition.
@@ -152,6 +158,12 @@ class Bridge extends React.Component {
152158 }
153159 }
154160
161+ setNetworkSeedCache(networkSeed) {
162+ this.setState({
163+ networkSeedCache: networkSeed
164+ })
165+ }
166+
155167 setNetwork(web3, contracts) {
156168 this.setState({
157169 web3: web3,
@@ -202,12 +214,14 @@ class Bridge extends React.Component {
202214 render() {
203215 const {
204216 routeCrumbs,
217+ routeData,
205218 networkType,
206219 walletType,
207220 walletHdPath,
208221 wallet,
209222 urbitWallet,
210223 authMnemonic,
224+ networkSeedCache,
211225 pointCursor,
212226 pointCache,
213227 txnHashCursor,
@@ -235,6 +249,7 @@ class Bridge extends React.Component {
235249 // router
236250 pushRoute={ this.pushRoute }
237251 popRoute={ this.popRoute }
252+ routeData={ routeData }
238253 // network
239254 setNetworkType={ this.setNetworkType }
240255 setNetwork={ this.setNetwork }
@@ -258,6 +273,8 @@ class Bridge extends React.Component {
258273 addToPointCache={ this.addToPointCache }
259274 pointCursor={ pointCursor }
260275 pointCache={ pointCache }
276+ networkSeedCache= { networkSeedCache }
277+ setNetworkSeedCache= { this.setNetworkSeedCache }
261278 // txn
262279 setTxnHashCursor={ this.setTxnHashCursor }
263280 txnHashCursor={ txnHashCursor } />
src/style/extend.css+7
@@ -463,3 +463,10 @@ code {
463463.clickable {
464464 cursor: pointer;
465465}
466+
467+.keyfile {
468+ white-space: pre-wrap;
469+ word-break: break-word;
470+ padding: 1rem 2rem;
471+ margin-bottom: 4rem;
472+}
src/views/GenKeyfile.js+75−92
@@ -1,4 +1,5 @@
11import React from 'react'
2+import Maybe from 'folktale/maybe'
23
34import { Button } from '../components/Base'
45import { RequiredInput, InnerLabel } from '../components/Base'
@@ -21,39 +22,14 @@ class GenKeyfile extends React.Component {
2122
2223 this.state = {
2324 keyfile: '',
24− networkSeed: ''
25+ loaded: false
2526 }
26−
27− this.handleNetworkSeedInput = this.handleNetworkSeedInput.bind(this)
28− this.handleKeyfileChange = this.handleKeyfileChange.bind(this)
29− }
30−
31− componentDidMount() {
32− this.deriveSeed()
33− }
34−
35− async deriveSeed() {
36− const next = false
37− const seed = await attemptSeedDerivation(next, this.props)
38− this.setState({
39− networkSeed: seed.getOrElse('')
40− })
41− }
42−
43− handleNetworkSeedInput = (networkSeed) => {
44− this.setState({ networkSeed })
4527 }
4628
47− handleKeyfileChange = (keyfile) => {
48− this.setState({ keyfile })
49− }
50−
51− render() {
29+ getPointDetails() {
5230 const { pointCache } = this.props
5331 const { pointCursor } = this.props
5432
55− const { keyfile, networkSeed } = this.state
56−
5733 const point = pointCursor.matchWith({
5834 Just: (pt) => pt.value,
5935 Nothing: () => {
@@ -68,6 +44,34 @@ class GenKeyfile extends React.Component {
6844
6945 const revision = parseInt(pointDetails.keyRevisionNumber)
7046
47+ return {
48+ point,
49+ pointDetails,
50+ revision
51+ }
52+ }
53+
54+ async componentDidMount() {
55+ const { point, pointDetails, revision } = this.getPointDetails();
56+ let keyfile = ''
57+
58+ const hexRegExp = /[0-9A-Fa-f]{64}/g
59+ const networkSeed = await this.deriveSeed()
60+
61+ const keysmatch = this.checkKeysMatch(networkSeed, pointDetails)
62+ const seedValid = hexRegExp.test(networkSeed)
63+
64+ if (keysmatch && seedValid) {
65+ keyfile = genKey(networkSeed, point, revision)
66+ }
67+
68+ this.setState({
69+ keyfile: keyfile,
70+ loaded: true
71+ });
72+ }
73+
74+ checkKeysMatch(networkSeed, pointDetails) {
7175 const crypub = pointDetails.encryptionKey
7276 const sgnpub = pointDetails.authenticationKey
7377
@@ -77,29 +81,23 @@ class GenKeyfile extends React.Component {
7781 crypub === addHexPrefix(crypt.public)
7882 && sgnpub === addHexPrefix(auth.public)
7983
80− const warning =
81− keyfile !== '' && keysmatch === false
82− ? <Row>
83− <Col>
84− <b>{ 'WARNING' }</b>{ ": derived key doesn't match Azimuth keys!" }
85− </Col>
86− </Row>
87− : <div />
84+ return keysmatch;
85+ }
8886
89− const hexRegExp = /[0-9A-Fa-f]{64}/g
87+ async deriveSeed() {
88+ const next = false
89+ let seed = await attemptSeedDerivation(next, this.props)
9090
91− const download =
92− keyfile !== ''
93− ? <Button
94− onClick={
95− () => {
96− let blob = new Blob([keyfile], {type:"text/plain;charset=utf-8"});
97− saveAs(blob, `${ob.patp(point).slice(1)}-${revision}.key`)
98− }
99− }>
100− Download →
101− </Button>
102− : ''
91+ if (seed.getOrElse('') === '' && this.props.networkSeedCache) {
92+ seed = Maybe.Just(this.props.networkSeedCache)
93+ }
94+
95+ return seed.getOrElse('')
96+ }
97+
98+ render() {
99+ const { point, pointDetails, revision } = this.getPointDetails();
100+ const { keyfile, loaded } = this.state
103101
104102 return (
105103 <Row>
@@ -107,55 +105,40 @@ class GenKeyfile extends React.Component {
107105 <H1>{ 'Generate keyfile' }</H1>
108106
109107 <P>
110− { "Generate a private key file for booting this point in Arvo." }
108+ { "Download a private key file for booting this point in Arvo." }
111109 </P>
112110
113− <P>
114− {
115− `Enter a network seed below for generating your key file. Your
116− network seed must be a string of 64 characters (containing 0-9, A-Z, a-z)`
111+ { keyfile === '' && !loaded &&
112+ <P>
113+ { "Generating keyfile..." }
114+ </P>
117115 }
118− </P>
119− <P>
120− {
121− `If you've authenticated with either a master ticket or a
122− management proxy mnemonic, a seed will be generated for you
123− automatically.`
116+
117+ { keyfile === '' && loaded &&
118+ <P>
119+ <b>Warning: </b>
120+ { `No valid network seed detected. To generate a keyfile, you
121+ must reset your networking keys, or try logging in with your
122+ master ticket or management mnemonic` }
123+ </P>
124124 }
125− </P>
126125
127− <RequiredInput
128− className='mono'
129− prop-size='lg'
130− prop-format='innerLabel'
131− autoFocus
132− value={ networkSeed }
133− onChange={ this.handleNetworkSeedInput }>
134− <InnerLabel>{ 'Network seed' }</InnerLabel>
135− </RequiredInput>
136−
137− <Button
138− className={'mt-8'}
139− color={'blue'}
140− onClick={
141− () => {
142− if (hexRegExp.test(networkSeed)) {
143− const keyfile = genKey(networkSeed, point, revision)
144− this.setState({ keyfile })
145− }
146− }
147− }
148− >
149− { 'Generate →' }
150− </Button>
151−
152− <Code>
153− { keyfile }
154− </Code>
155−
156− { warning }
157−
158− { download }
126+ { keyfile !== '' &&
127+ <React.Fragment>
128+ <div className="pb-5 text-code keyfile">
129+ { keyfile }
130+ </div>
131+ <Button
132+ onClick={
133+ () => {
134+ let blob = new Blob([keyfile], {type:"text/plain;charset=utf-8"});
135+ saveAs(blob, `${ob.patp(point).slice(1)}-${revision}.key`)
136+ }
137+ }>
138+ Download →
139+ </Button>
140+ </React.Fragment>
141+ }
159142
160143 </Col>
161144 </Row>
src/views/SentTransaction.js+26−1
@@ -2,6 +2,7 @@ import React from 'react'
22import { Row, Col, H1, H3, P, Warning, Anchor } from '../components/Base'
33import { Button } from '../components/Base'
44
5+import { ROUTE_NAMES } from '../lib/router'
56import { BRIDGE_ERROR, renderTxnError } from '../lib/error'
67import { NETWORK_NAMES } from '../lib/network'
78
@@ -75,9 +76,11 @@ const Failure = (props) =>
7576 </Row>
7677
7778const SentTransaction = (props) => {
78− const { web3, txnHashCursor, networkType, popRoute } = props
79+ const { web3, txnHashCursor, networkType, popRoute, pushRoute } = props
7980 const { setPointCursor, pointCursor } = props
8081
82+ const promptKeyfile = props.routeData && props.routeData.promptKeyfile
83+
8184 const w3 = web3.matchWith({
8285 Nothing: _ => { throw BRIDGE_ERROR.MISSING_WEB3 },
8386 Just: res => res.value
@@ -113,10 +116,32 @@ const SentTransaction = (props) => {
113116 </Col>
114117 </Row>
115118
119+ let keyfile;
120+
121+ if (promptKeyfile) {
122+ keyfile = (
123+ <Row>
124+ <Col>
125+ <Button
126+ prop-type={'link'}
127+ onClick={
128+ () => {
129+ popRoute()
130+ pushRoute(ROUTE_NAMES.GEN_KEYFILE)
131+ }
132+ }>
133+ { 'Download Keyfile →' }
134+ </Button>
135+ </Col>
136+ </Row>
137+ )
138+ }
139+
116140 return (
117141 <div>
118142 { body }
119143 { ok }
144+ { keyfile }
120145 </div>
121146 )
122147}
src/views/SetKeys.js+60−40
@@ -8,7 +8,9 @@ import { RequiredInput, InnerLabel } from '../components/Base'
88import StatelessTransaction from '../components/StatelessTransaction'
99import { BRIDGE_ERROR } from '../lib/error'
1010import { ROUTE_NAMES } from '../lib/router'
11−import { attemptSeedDerivation } from '../lib/keys'
11+import { attemptSeedDerivation, genKey } from '../lib/keys'
12+
13+import saveAs from 'file-saver'
1214
1315import { WALLET_NAMES } from '../lib/wallet'
1416
@@ -39,6 +41,7 @@ class SetKeys extends React.Component {
3941 auth: '',
4042 encr: '',
4143 networkSeed: '',
44+ nondeterministicSeed: false,
4245 point: point,
4346 cryptoSuiteVersion: 1,
4447 continuity: false,
@@ -122,22 +125,38 @@ class SetKeys extends React.Component {
122125 })
123126 }
124127
128+ //TODO use web3.utils.randomHex when it gets fixed, see web3.js#1490
129+ randomHex(len) {
130+ let hex = "";
131+
132+ for (var i = 0; i < len; i++) {
133+ hex = hex + ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B',
134+ 'C', 'D', 'E', 'F'][Math.floor(Math.random() * 16)]
135+ }
136+
137+ return hex;
138+ }
139+
125140 async deriveSeed() {
126141 const next = true
127− const seed = await attemptSeedDerivation(next, this.props)
142+ let seed = await attemptSeedDerivation(next, this.props)
143+ let nondeterministicSeed = false;
144+
145+ if (seed.getOrElse('') === '') {
146+ seed = Maybe.Just(this.randomHex(64));
147+ nondeterministicSeed = true;
148+ }
149+
128150 this.setState({
129− networkSeed: seed.getOrElse('')
151+ networkSeed: seed.getOrElse(''),
152+ nondeterministicSeed: nondeterministicSeed
130153 })
131154 }
132155
133−
134−
135156 handleNetworkSeedInput(networkSeed) {
136157 this.setState({ networkSeed })
137158 }
138159
139−
140−
141160 handleCreateUnsignedTxn() {
142161 const txn = this.createUnsignedTxn()
143162 this.setState({ txn })
@@ -222,23 +241,42 @@ class SetKeys extends React.Component {
222241 })
223242 }
224243
244+ downloadKeyfile(networkSeed) {
245+ const { pointCache } = this.props
246+ const { pointCursor } = this.props
225247
248+ const point = pointCursor.matchWith({
249+ Just: (pt) => pt.value,
250+ Nothing: () => {
251+ throw BRIDGE_ERROR.MISSING_POINT
252+ }
253+ })
254+
255+ const pointDetails =
256+ point in pointCache
257+ ? pointCache[point]
258+ : (() => { throw BRIDGE_ERROR.MISSING_POINT })()
259+
260+ const revision = parseInt(pointDetails.keyRevisionNumber)
261+ const keyfile = genKey(networkSeed, point, revision)
262+ let blob = new Blob([keyfile], {type:"text/plain;charset=utf-8"});
263+ saveAs(blob, `${ob.patp(point).slice(1)}-${revision}.key`)
264+ }
226265
227266 handleSubmit(){
228267 const { props, state } = this
229268 sendSignedTransaction(props.web3.value, state.stx)
230269 .then(sent => {
270+ props.setNetworkSeedCache(this.state.networkSeed)
231271 props.setTxnHashCursor(sent)
232272 props.popRoute()
233− props.pushRoute(ROUTE_NAMES.SENT_TRANSACTION)
273+ props.pushRoute(ROUTE_NAMES.SENT_TRANSACTION, {promptKeyfile: true})
234274 })
235275 .catch(err => {
236276 this.setState({ txError: err.map(val => val.merge()) })
237277 })
238278 }
239279
240−
241−
242280 createUnsignedTxn() {
243281 const { state, props } = this
244282
@@ -299,16 +337,6 @@ class SetKeys extends React.Component {
299337 ? props.pointCache[state.point]
300338 : (() => { throw BRIDGE_ERROR.MISSING_POINT })()
301339
302− const isManagementMnemonic =
303− this.state.isManagementSeed &&
304− props.walletType === WALLET_NAMES.MNEMONIC
305−
306− const isMasterTicket =
307− props.walletType === WALLET_NAMES.TICKET ||
308− props.walletType === WALLET_NAMES.SHARD
309−
310− const networkSeedDisabled = isManagementMnemonic || isMasterTicket;
311−
312340 return (
313341 <Row>
314342 <Col>
@@ -316,19 +344,22 @@ class SetKeys extends React.Component {
316344 { 'Set Network Keys For ' } <code>{ `${ob.patp(state.point)}` }</code>
317345 </H1>
318346
319− <P>
347+ <P className="mt-10">
320348 {
321− `Please enter a network seed for generating and setting your public
322− network authentication and encryption keys. Your network seed
323− must be a string of 64 characters (containing 0-9, A-Z, a-z).`
349+ `Set new authentication and encryption keys for your Arvo ship.`
324350 }
325351 </P>
326− <P>
327− {
328− `If you've authenticated with a master ticket or management proxy
329− mnemonic, a seed will be generated for you automatically.`
352+
353+ { state.nondeterministicSeed &&
354+ <Warning>
355+ <h3 className={'mb-2'}>{'Warning'}</h3>
356+ {
357+ `Your network seed could not be derived automatically. We've
358+ generated a random one for you, so you must download your Arvo
359+ keyfile during this session after setting your keys.`
360+ }
361+ </Warning>
330362 }
331− </P>
332363
333364 { pointDetails.keyRevisionNumber === '0'
334365 ? <Warning>
@@ -340,17 +371,6 @@ class SetKeys extends React.Component {
340371 </Warning>
341372 : <div /> }
342373
343− <RequiredInput
344− className='mono'
345− prop-size='lg'
346− prop-format='innerLabel'
347− autoFocus
348− disabled= { networkSeedDisabled }
349− value={ state.networkSeed }
350− onChange={ this.handleNetworkSeedInput }>
351− <InnerLabel>{ 'Network seed' }</InnerLabel>
352− </RequiredInput>
353−
354374 <StatelessTransaction
355375 // Upper scope
356376 web3={props.web3}