on 1ec514b help sign in
bridge/src/walletgen/views/Generate.js
497 lines 15 kB history raw
1import React from 'react'
2import * as more from 'more-entropy'
3import lodash from 'lodash'
4import * as sigil from 'sigil-js'
5import ob from 'urbit-ob'
6import classnames from 'classnames'
7// temporarily use keygen-js with argon2u that is configured with 520MB
8import * as kg from '../../../node_modules/urbit-key-generation/dist/index'
11import Button from '../components/Button'
12import ReactSVGComponents from '../components/ReactSVGComponents'
13import PaperCollateralRenderer from 'PaperCollateralRenderer'
15import { MAX_GALAXY, MIN_STAR, MAX_STAR, MIN_PLANET,
16 GALAXY_ENTROPY_BITS, STAR_ENTROPY_BITS, PLANET_ENTROPY_BITS,
17 SEED_ENTROPY_BITS,
18 GEN_STATES
19 } from '../lib/constants'
21const SEED_LENGTH_BYTES = SEED_ENTROPY_BITS / 8
22const NEXT_STEP_NUM = 6;
25const shipInfo = ship => {
26 const name = ob.patp(ship)
27 const sig = sigil.pour({
28 colorway: ['black', 'white'],
29 patp: name,
30 renderer: ReactSVGComponents,
31 size: 233
32 })
34 return ship < MAX_GALAXY
35 ? galaxyInfo(name, sig)
36 : ship < MAX_STAR
37 ? starInfo(name, sig)
38 : planetInfo(name, sig)
41const infoHeader = ship =>
42 <div className={'row text-600 text-mono'} key={ ship + '-header' }>
43 {ship + ' Wallet'}
44 </div>
46const infoSigil = (ship, sig) => {
47 return (
48 <div className={ '' } key={ 'sigil-' + ship }>
49 { sig }
50 </div>
51 )
54const getColor = (classOf, seedType) => {
55 if (classOf === 'galaxy') {
56 if (seedType === 'manage') return 'bg-medium';
57 if (seedType === 'voting') return 'bg-medium';
58 if (seedType === 'spawn') return 'bg-high';
59 if (seedType === 'master') return 'bg-very-high';
60 if (seedType === 'masterShard') return 'bg-very-high';
61 }
62 if (classOf === 'star') {
63 if (seedType === 'manage') return 'bg-low';
64 if (seedType === 'spawn') return 'bg-medium';
65 if (seedType === 'master') return 'bg-high';
66 }
67 if (classOf === 'planet') {
68 if (seedType === 'manage') return 'bg-low';
69 if (seedType === 'master') return 'bg-medium';
70 }
76const infoManagementSeed = ship => {
77 const classOf = ob.clan(ship)
78 const colorClass = getColor(classOf, 'manage')
79 return <Tile colorClass={colorClass} key={`info-${ship}-manage`} col1={`Management Proxy`} col2={''} />
83const infoVotingSeed = ship => {
84 const classOf = ob.clan(ship)
85 const colorClass = getColor(classOf, 'voting')
86 return <Tile colorClass={colorClass} key={`info-${ship}-voting`} col1={`Voting Proxy`} col2={''} />
91const infoSpawnSeed = ship => {
92 const classOf = ob.clan(ship)
93 const colorClass = getColor(classOf, 'spawn')
94 return <Tile colorClass={colorClass} key={`info-${ship}-spawn`} col1={`Spawn Proxy`} col2={''} />
99const infoMasterTicket = ship => {
100 const classOf = ob.clan(ship);
101 const colorClass = getColor(classOf, 'master');
102 return <Tile colorClass={colorClass} key={`info-${ship}-master`} col1={`Master Ticket`} col2={''} />;
103};
107const infoMasterTicketShard = ship => {
108 const classOf = ob.clan(ship);
109 const colorClass = getColor(classOf, 'master');
110 return [
111 <Tile colorClass={colorClass} key={`info-${ship}-master-1`} col1={`Master Ticket Shard 1 of 3`} col2={''} />,
112 <Tile colorClass={colorClass} key={`info-${ship}-master-2`} col1={`Master Ticket Shard 2 of 3`} col2={''} />,
113 <Tile colorClass={colorClass} key={`info-${ship}-master-3`} col1={`Master Ticket Shard 3 of 3`} col2={''} />,
114 ];
115};
119const Tile = (props) => {
120 return (
121 <div
122 className={ `${props.colorClass} white row pl-4 text-mono` }
123 style={{borderBottom:'2px solid black', height: '39px' }}
124 key={props.key}>
126 <div className={'col-md-8 flex align-center text-600'}>{props.col1}</div>
127 <div className={'col-md-4 flex align-center text-500'}>{props.col2}</div>
128 </div>
129 )
133const galaxyInfo = (ship, sig) =>
134 <div className={'mt-8'} key={ 'wallet-' + ship }>
135 { infoHeader(ship) }
136 <div className={'row'} key={ ship + '-body' }>
137 { infoSigil(ship, sig) }
138 <div className={ 'col-md-7 ml-2' } key={ 'info-' + ship }>
139 { infoMasterTicketShard(ship) }
140 { infoSpawnSeed(ship) }
141 { infoManagementSeed(ship) }
142 { infoVotingSeed(ship) }
143 </div>
144 </div>
145 </div>
147const starInfo = (ship, sig) =>
148 <div className={'mt-8'} key={ 'wallet-' + ship }>
149 { infoHeader(ship) }
150 <div className={'row'} key={ ship + '-body' }>
151 { infoSigil(ship, sig) }
152 <div className={ 'col-md-7 ml-2' } key={ 'info-' + ship }>
153 { infoMasterTicket(ship) }
154 { infoSpawnSeed(ship) }
155 { infoManagementSeed(ship) }
156 </div>
157 </div>
158 </div>
160const planetInfo = (ship, sig) =>
161 <div className={'mt-8'} key={ 'wallet-' + ship }>
162 { infoHeader(ship) }
163 <div className={'row'} key={ ship + '-body' }>
164 { infoSigil(ship, sig) }
165 <div className={ 'col-md-7 ml-2' } key={ 'info-' + ship }>
166 { infoMasterTicket(ship) }
167 { infoManagementSeed(ship) }
168 </div>
169 </div>
170 </div>
174const makeTicket = ship => {
176 const bits = ship < MIN_STAR
177 ? GALAXY_ENTROPY_BITS
178 : ship < MIN_PLANET
179 ? STAR_ENTROPY_BITS
180 : PLANET_ENTROPY_BITS
182 const bytes = bits / 8
183 const some = new Uint8Array(bytes)
184 window.crypto.getRandomValues(some)
186 const gen = new more.Generator()
188 return new Promise((resolve, reject) => {
189 gen.generate(bits, result => {
190 const chunked = lodash.chunk(result, 2)
191 const desired = chunked.slice(0, bytes) // only take required entropy
192 const more = lodash.flatMap(desired, arr => arr[0] ^ arr[1])
193 const entropy = lodash.zipWith(some, more, (x, y) => x ^ y)
194 const buf = Buffer.from(entropy)
195 const patq = ob.hex2patq(buf.toString('hex'))
196 resolve(patq)
197 reject('Entropy generation failed')
198 })
199 })
205const genWallet = async (ship, ticket, cb) => {
207 const boot = ship < MIN_STAR || ship > MAX_STAR;
209 const config = {
210 ticket: ticket,
211 seedSize: SEED_LENGTH_BYTES,
212 ship: ship,
213 password: '',
214 revisions: {},
215 boot: boot
216 };
218 const wallet = await kg.generateWallet(config);
220 // This is here to notify the anyone who opens console because the thread
221 // hangs, blocking UI updates so this cannot be doen in the UI
222 console.log('Generating Wallet for ship address: ', ship);
224 return wallet;
231class Generate extends React.Component {
232 constructor(props) {
233 super(props)
234 this.handleGenerate = this.handleGenerate.bind(this);
235 }
237 handleGenerate = async () => {
238 const { props } = this;
239 const { setGlobalState } = this.props;
241 setGlobalState({ 'eny.state': GEN_STATES.ENY_PENDING });
243 const ships = [
244 ...props['profile.value'].galaxies,
245 ...props['profile.value'].stars,
246 ...props['profile.value'].planets,
247 ];
249 const enyPromises = ships.map(async ship => {
250 return {
251 ship,
252 ticket: await makeTicket(ship),
253 };
254 });
256 const enyTable = await Promise.all(enyPromises);
258 setGlobalState({
259 'eny.value': enyTable,
260 'eny.state': GEN_STATES.ENY_SUCCESS,
261 'wallets.state': GEN_STATES.DERIVATION_PENDING,
262 });
264 await this.forciblyExecuteArgon2Once(enyTable);
266 const walletPromises = enyTable.map(async ({ ship, ticket }) => {
267 return {
268 ship,
269 wallet: await genWallet(ship, ticket),
270 };
271 });
273 const wallets = await Promise.all(walletPromises);
275 const reshapedWallet = wallets.reduce((acc, {ship, wallet}) => ({...acc, [ship]: wallet }), {})
277 setGlobalState({
278 'wallets.value': reshapedWallet,
279 'wallets.state': GEN_STATES.DERIVATION_SUCCESS,
280 'paperCollateral.state': GEN_STATES.PAPER_PENDING,
281 });
282 }
286 forciblyExecuteArgon2Once = async enyTable => {
287 // NB (jtobin):
288 //
289 // following is some insane thing required so as not to run into bizarre
290 // emscripten errors
291 if (enyTable.length === 0) {
292 throw new Error('no ship information provided')
293 } else {
294 let throwawayEntry = enyTable[0]
295 // eslint-disable-next-line
296 let throwaway = await genWallet(throwawayEntry.ship, throwawayEntry.ticket, () => {})
297 };
298 }
302 render() {
303 const { props } = this;
304 const { setGlobalState } = this.props;
306 const {
307 ENY_NOSTART,
308 ENY_PENDING,
309 ENY_SUCCESS,
310 ENY_FAILURE,
311 DERIVATION_NOSTART,
312 DERIVATION_PENDING,
313 DERIVATION_SUCCESS,
314 DERIVATION_FAILURE,
315 PAPER_NOSTART,
316 PAPER_PENDING,
317 PAPER_SUCCESS,
318 PAPER_FAILURE,
319 } = GEN_STATES;
321 const es = props['eny.state'];
322 const ws = props['wallets.state'];
323 const ps = props['paperCollateral.state'];
326 const ALL_SUCCESS =
327 es === ENY_SUCCESS &&
328 ws === DERIVATION_SUCCESS &&
329 ps === PAPER_SUCCESS;
331 const NONE_START =
332 es === ENY_NOSTART &&
333 ws === DERIVATION_NOSTART &&
334 ps === PAPER_NOSTART;
336 const ANY_ERROR =
337 es === ENY_FAILURE ||
338 ws === DERIVATION_FAILURE ||
339 ps === PAPER_FAILURE;
341 const ANY_PENDING =
342 es === ENY_PENDING ||
343 ws === DERIVATION_PENDING ||
344 ps === PAPER_PENDING;
346 const genButtonClass = classnames({
347 'bg-blue white': true,
348 'bg-green noClick black': ALL_SUCCESS,
349 });
351 const entropyBarClassnames = classnames({
352 'bg-blue': es === ENY_SUCCESS,
353 'bg-lightBlue': es === ENY_PENDING,
354 'bg-gray': es === ENY_NOSTART,
355 'bg-orange': es === ENY_FAILURE,
356 'bg-green': ALL_SUCCESS,
357 });
359 const walletsBarClassnames = classnames({
360 'bg-blue': ws === DERIVATION_SUCCESS,
361 'bg-lightBlue': ws === DERIVATION_PENDING,
362 'bg-gray': ws === DERIVATION_NOSTART,
363 'bg-orange': ws === DERIVATION_FAILURE,
364 'bg-green': ALL_SUCCESS,
365 });
367 const paperBarClassnames = classnames({
368 'bg-blue': ps === PAPER_SUCCESS,
369 'bg-lightBlue': ps === PAPER_PENDING,
370 'bg-gray': ps === PAPER_NOSTART,
371 'bg-orange': ps === PAPER_FAILURE,
372 'bg-green': ALL_SUCCESS,
373 });
375 const isGenerateButtonDisabled = () => {
376 if (NONE_START === true) return false;
377 if (ANY_PENDING === true) return true;
378 if (ANY_ERROR === true) return false;
379 if (ALL_SUCCESS === true) return true;
380 };
382 const getGenButtonText = () => {
383 // const N = props['wallets.genCounter'] + 1;
384 // const X = props['eny.value'].length;
385 let r = '';
386 if (NONE_START === true) r = 'Generate All';
387 if (es === ENY_PENDING) r = 'Generating Entropy';
388 // if (ws === DERIVATION_PENDING) r = `Generating Wallet ${N} / ${X}`;
389 if (ws === DERIVATION_PENDING) r = `Generating Wallets`;
390 if (ps === PAPER_PENDING) r = 'Making Paper Collateral';
391 if (ALL_SUCCESS === true) r = 'Success.';
392 if (ANY_ERROR === true) r = 'Try Again';
393 return r;
394 }
396 const disableGenerateButton = isGenerateButtonDisabled();
397 const genText = getGenButtonText();
399 const nextButtonClass = classnames({
400 'bg-blue white': ALL_SUCCESS === true,
401 'bg-gray black': ALL_SUCCESS !== true,
402 })
404 const nextButtonDisabled = ws !== ALL_SUCCESS;
406 return(
407 <div className={'row'}>
408 <div className={'col-md-8'}>
409 <div className={'col-md-8'}>
410 <h2>{`Review your paper wallet storage recommendations.`}</h2>
411 <p>
412 {`Each ship is placed into its own wallet with proxies that differ in
413 importance and security requirements. These are listed as examples below. A key to our security recommendations can be found to the right of the page.`}
414 </p>
415 </div>
417 { props['profile.value'].galaxies.map(ship => shipInfo(ship)) }
418 { props['profile.value'].stars.map(ship => shipInfo(ship)) }
419 { props['profile.value'].planets.map(ship => shipInfo(ship)) }
421 </div>
423 <div className="col-md-3 col-md-offset-1 mt-9">
424 <div className="fixed" style={{width: '230px'}}>
425 <div className="col-md-12 bg-very-high h-2 mt-4"></div>
426 <h4 className="text-600 mt-2">Very High Friction</h4>
427 <p className="text-sm" style={{lineHeight: '16px'}}>
428 Extremely valuable. Cold storage on a 3 part sharded paper wallet. Store each shard in a separate geographic location with protection against fire, water and other elements.
429 </p>
430 <div className="col-md-12 bg-high h-2 mt-4"></div>
431 <h4 className="text-600 mt-2">High Friction</h4>
432 <p className="text-sm" style={{lineHeight: '16px'}}>
433 Very valuable. Cold storage on a paper wallet in a different location than your computer, such as a safe.
434 </p>
435 <div className="col-md-12 bg-medium h-2 mt-4"></div>
436 <h4 className="text-600 mt-2">Medium Friction</h4>
437 <p className="text-sm" style={{lineHeight: '16px'}}>
438 Store cold in a paper wallet; can be stored in a desk drawer in a trusted environment.
439 </p>
440 <div className="col-md-12 bg-low h-2 mt-4"></div>
441 <h4 className="text-600 mt-2">Low Friction</h4>
442 <p className="text-sm" style={{lineHeight: '16px'}}>
443 Ok to store hot on a trusted networked computer.
444 </p>
445 </div>
446 </div>
448 <div className={'col-md-9 btn-tray col-'}>
449 <h3>{"Got it? Then it's time to generate your wallets."}</h3>
450 <p className={'col-md-8'}>{"If you have a large number of ships, this may take several minutes and the page may hang or become unresponsive. If you close this browser window, you will have to start from the beginning."}</p>
452 <div className={'flex h-12 mt-8 mb-4'}>
453 <Button
454 className={ `${genButtonClass} b-r-1-black w-50 m-0` }
455 text={genText}
456 onClick={ this.handleGenerate }
457 disable={disableGenerateButton} />
458 <div className={`${entropyBarClassnames} f-1 b-r-1-black`} />
459 <div className={`${walletsBarClassnames} f-3 b-r-1-black`} />
460 <div className={`${paperBarClassnames} f-3 b-r-1-black`} />
461 <Button
462 className={ `${nextButtonClass} m-0` }
463 text={'View wallets →'}
464 disable={ nextButtonDisabled }
465 onClick= { () => setGlobalState({ route: '/Download' , 'currentStep': NEXT_STEP_NUM })} />
466 </div>
467 </div>
469 <div className={'extremely-hidden'}>
470 <div className={'text-300'}>{'text-300'}</div>
471 <div className={'text-400'}>{'text-400'}</div>
472 <div className={'text-500'}>{'text-500'}</div>
473 <div className={'text-600'}>{'text-600'}</div>
474 <div className={'text-400 text-mono'}>{'text-400 text-mono'}</div>
475 <div className={'text-600 text-mono'}>{'text-600 text-mono'}</div>
476 <div className={'text-500 h-font'}>{'text-500 h-font'}</div>
477 <div className={'text-600 h-font'}>{'text-600 h-font'}</div>
478 </div>
480 { props['wallets.state'] === GEN_STATES.DERIVATION_SUCCESS
481 ? <PaperCollateralRenderer
482 wallet={props['wallets.value']}
483 className={'extremely-hidden'}
484 callback={data => setGlobalState({
485 'paperCollateral.value': data,
486 'paperCollateral.state': GEN_STATES.PAPER_SUCCESS
487 })}
488 mode={'REGISTRATION'} />
489 : <div />
490 }
492 </div>
493 )
494 }
497export default Generate