diff --git a/package.json b/package.json index 9c51379ce2..dd05bce273 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "build:apps": "cd packages/apps && react-scripts build", "check": "eslint packages && flow check", "deploy:ghpages": "gh-pages -d packages/apps/build", + "generator": "node packages/app-vanitygen/build/generator/cli.js", "start": "cd packages/apps && react-scripts start", "test": "echo 'no tests executed'" }, diff --git a/packages/app-accounts/src/Creator.js b/packages/app-accounts/src/Creator.js index e39cd97914..ad921682d5 100644 --- a/packages/app-accounts/src/Creator.js +++ b/packages/app-accounts/src/Creator.js @@ -50,6 +50,8 @@ function addressFromSeed (seed: string): string { } class Creator extends React.PureComponent { + state: State; + constructor (props: Props) { super(props); diff --git a/packages/app-accounts/src/Editor.js b/packages/app-accounts/src/Editor.js index d2e2313a2d..956758c4c2 100644 --- a/packages/app-accounts/src/Editor.js +++ b/packages/app-accounts/src/Editor.js @@ -30,6 +30,8 @@ type State = { } class Editor extends React.PureComponent { + state: State; + constructor (props: Props) { super(props); diff --git a/packages/app-vanitygen/LICENSE b/packages/app-vanitygen/LICENSE new file mode 100644 index 0000000000..3d150a620a --- /dev/null +++ b/packages/app-vanitygen/LICENSE @@ -0,0 +1,15 @@ +ISC License (ISC) + +Copyright 2017-2018 Jaco Greeff + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. diff --git a/packages/app-vanitygen/README.md b/packages/app-vanitygen/README.md new file mode 100644 index 0000000000..45b503fad8 --- /dev/null +++ b/packages/app-vanitygen/README.md @@ -0,0 +1 @@ +# @polkadot/app-vanitygen diff --git a/packages/app-vanitygen/package.json b/packages/app-vanitygen/package.json new file mode 100644 index 0000000000..57c2b8908a --- /dev/null +++ b/packages/app-vanitygen/package.json @@ -0,0 +1,18 @@ +{ + "name": "@polkadot/app-vanitygen", + "version": "0.2.1", + "main": "index.js", + "repository": "https://github.com/polkadot-js/apps.git", + "author": "Jaco Greeff ", + "license": "ISC", + "scripts": { + "build": "polkadot-dev-build-babel", + "check": "stylelint 'src/**/*.css' && eslint src && flow check", + "test": "jest --coverage" + }, + "devDependencies": { + "@polkadot/ui-react-app": "^0.2.1", + "chalk": "^2.4.1", + "yargs": "10.1.2" + } +} diff --git a/packages/app-vanitygen/src/Match.js b/packages/app-vanitygen/src/Match.js new file mode 100644 index 0000000000..46b144479f --- /dev/null +++ b/packages/app-vanitygen/src/Match.js @@ -0,0 +1,51 @@ +// Copyright 2017-2018 Jaco Greeff +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. +// @flow + +import type { BareProps } from '@polkadot/ui-react-app/types'; + +import React from 'react'; +import Button from 'semantic-ui-react/dist/es/elements/Button'; +import IdentityIcon from '@polkadot/ui-react/IdentityIcon'; +import u8aToHex from '@polkadot/util/u8a/toHex'; + +type Props = BareProps & { + address: string; + count: number; + offset: number; + onRemove: (address: string) => void, + seed: Uint8Array; +} + +export default function Match ({ address, className, count, offset, onRemove, seed, style }: Props): React$Node { + const _onRemove = (): void => + onRemove(address); + + return ( +
+
+ +
+ {address.slice(0, offset)}{address.slice(offset, count + offset)}{address.slice(count + offset)} +
+
+ {u8aToHex(seed)} +
+
+
+ ); +} diff --git a/packages/app-vanitygen/src/generator/calculate.js b/packages/app-vanitygen/src/generator/calculate.js new file mode 100644 index 0000000000..5b9368a751 --- /dev/null +++ b/packages/app-vanitygen/src/generator/calculate.js @@ -0,0 +1,47 @@ +// Copyright 2017-2018 Jaco Greeff +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. +// @flow + +import type { Generator$Calculation, Generator$Options } from './types'; + +module.exports = function calculate (test: Array, _address: string, { atOffset = -1, withCase = false }: Generator$Options): Generator$Calculation { + const address = withCase + ? _address + : _address.toLowerCase(); + + const calculateAt = (atOffset: number): Generator$Calculation => { + return { + count: test.reduce((count, c, index) => { + if (index === count) { + count += (c === '?' || c === address.charAt(index + atOffset)) ? 1 : 0; + } + + return count; + }, 0), + offset: atOffset + }; + }; + + if (atOffset > 0) { + return calculateAt(atOffset); + } + + const maxOffset = address.length - test.length - 1; + let bestCount = 0; + let bestOffset = 1; + + for (let index = 1; index < maxOffset; index++) { + const { count, offset } = calculateAt(index); + + if (count > bestCount) { + bestCount = count; + bestOffset = offset; + } + } + + return { + count: bestCount, + offset: bestOffset + }; +}; diff --git a/packages/app-vanitygen/src/generator/cli.js b/packages/app-vanitygen/src/generator/cli.js new file mode 100644 index 0000000000..d120ce6454 --- /dev/null +++ b/packages/app-vanitygen/src/generator/cli.js @@ -0,0 +1,65 @@ +#!/usr/bin/env node +// Copyright 2017-2018 Jaco Greeff +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. + +const yargs = require('yargs'); +const chalk = require('chalk'); +const u8aToHex = require('@polkadot/util/u8a/toHex'); + +const generator = require('./index.js'); + +const { match } = yargs + .option('match', { + alias: 'm', + default: 'EEEEE' + }) + .argv; + +const INDICATORS = ['|', '/', '-', '\\']; + +const options = { + match, + runs: 100 +}; +const startAt = Date.now(); +let best = { count: -1 }; +let total = 0; +let indicator = -1; + +function showProgress () { + const elapsed = Date.now() - startAt; + + indicator++; + + if (indicator === INDICATORS.length) { + indicator = 0; + } + + process.stdout.write(`\r[${INDICATORS[indicator]}] ${total} keys in ${(elapsed / 1000).toFixed(2)}s, ${(elapsed / total).toFixed(3)}ms/key`); +} + +function showBest () { + const { address, count, offset, seed } = best; + + console.log(`\r::: ${address.slice(0, offset)}${chalk.cyan(address.slice(offset, count + offset))}${address.slice(count + offset)} <= ${u8aToHex(seed)} (count=${count}, offset=${offset})`); +} + +while (true) { + const nextBest = generator(options).found.reduce((best, match) => { + if ((match.count > best.count) || ((match.count === best.count) && (match.offset < best.offset))) { + return match; + } + + return best; + }, best); + + total += options.runs; + + if (nextBest.address !== best.address) { + best = nextBest; + showBest(); + } + + showProgress(); +} diff --git a/packages/app-vanitygen/src/generator/generate.js b/packages/app-vanitygen/src/generator/generate.js new file mode 100644 index 0000000000..cfb8a0be57 --- /dev/null +++ b/packages/app-vanitygen/src/generator/generate.js @@ -0,0 +1,27 @@ +// Copyright 2017-2018 Jaco Greeff +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. +// @flow + +import type { Generator$Match, Generator$Options } from './types'; + +const keypairFromSeed = require('@polkadot/util-crypto/nacl/keypair/fromSeed'); +const randomBytes = require('@polkadot/util-crypto/random/asU8a'); +const addressEncode = require('@polkadot/util-keyring/address/encode'); + +const calculate = require('./calculate'); + +module.exports = function generator (test: Array, options: Generator$Options): Generator$Match { + const seed = randomBytes(); + const address = addressEncode( + keypairFromSeed(seed).publicKey + ); + const { count, offset } = calculate(test, address, options); + + return { + address, + count, + offset, + seed + }; +}; diff --git a/packages/app-vanitygen/src/generator/index.js b/packages/app-vanitygen/src/generator/index.js new file mode 100644 index 0000000000..dd353c3a6f --- /dev/null +++ b/packages/app-vanitygen/src/generator/index.js @@ -0,0 +1,26 @@ +// Copyright 2017-2018 Jaco Greeff +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. +// @flow + +import type { Generator$Matches, Generator$Result, Generator$Options } from './types'; + +const generate = require('./generate'); + +module.exports = function generator (options: Generator$Options): Generator$Result { + const { match, runs = 10, withCase = false } = options; + const test = withCase + ? match.split('') + : match.toLowerCase().split(''); + const startAt = Date.now(); + const found: Generator$Matches = []; + + while (found.length !== runs) { + found.push(generate(test, options)); + } + + return { + elapsed: Date.now() - startAt, + found + }; +}; diff --git a/packages/app-vanitygen/src/generator/sort.js b/packages/app-vanitygen/src/generator/sort.js new file mode 100644 index 0000000000..9f432b49c6 --- /dev/null +++ b/packages/app-vanitygen/src/generator/sort.js @@ -0,0 +1,32 @@ +// Copyright 2017-2018 Jaco Greeff +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. +// @flow + +import type { Generator$Match } from './types'; + +function numberSort (a: number, b: number): number { + if (a > b) { + return -1; + } else if (a < b) { + return 1; + } + + return 0; +} + +module.exports = function sort (a: Generator$Match, b: Generator$Match): number { + const countResult = numberSort(a.count, b.count); + + if (countResult !== 0) { + return countResult; + } + + const positionResult = numberSort(b.offset, a.offset); + + if (positionResult !== 0) { + return positionResult; + } + + return a.address.localeCompare(b.address); +}; diff --git a/packages/app-vanitygen/src/generator/types.js b/packages/app-vanitygen/src/generator/types.js new file mode 100644 index 0000000000..d00764c568 --- /dev/null +++ b/packages/app-vanitygen/src/generator/types.js @@ -0,0 +1,28 @@ +// Copyright 2017-2018 Jaco Greeff +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. +// @flow + +export type Generator$Calculation = { + count: number, + offset: number +}; + +export type Generator$Match = Generator$Calculation & { + address: string, + seed: Uint8Array +}; + +export type Generator$Matches = Array; + +export type Generator$Options = { + atOffset?: number, + match: string, + runs?: number, + withCase?: boolean +}; + +export type Generator$Result = { + elapsed: number, + found: Generator$Matches +} diff --git a/packages/app-vanitygen/src/index.css b/packages/app-vanitygen/src/index.css new file mode 100644 index 0000000000..220e4f5242 --- /dev/null +++ b/packages/app-vanitygen/src/index.css @@ -0,0 +1,65 @@ +/* Copyright 2017-2018 Jaco Greeff +/* This software may be modified and distributed under the terms +/* of the ISC license. See the LICENSE file for details. */ + +.vanity--App { + padding: 1em 2em; +} + +.vanity--App-matches { + padding: 1em 0; +} + +.vanity--App-stats { + padding: 1em 0 0 0; + opacity: 0.45; + text-align: center; +} + +.vanity--Match { + text-align: center; +} + +.vanity--Match:hover { + background: #f9f9f9; +} + +.vanity--Match-item { + display: inline-block; + font-family: monospace; + margin: 0.5em auto; + padding: 0.5em; + position: relative; +} + +.vanity--Match-icon { + position: absolute; + top: 0; + left: 1rem; +} + +.vanity--Match-addr, +.vanity--Match-seed { + padding: 0 5rem; +} + +.vanity--Match-addr { + font-size: 1.5rem; +} + +.vanity--Match-addr .no { +} + +.vanity--Match-addr .yes { + color: red; +} + +.vanity--Match-remove { + position: absolute; + top: 0; + right: 1rem; +} + +.vanity--Match-seed { + opacity: 0.45; +} diff --git a/packages/app-vanitygen/src/index.js b/packages/app-vanitygen/src/index.js new file mode 100644 index 0000000000..8782daf88f --- /dev/null +++ b/packages/app-vanitygen/src/index.js @@ -0,0 +1,237 @@ +// Copyright 2017-2018 Jaco Greeff +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. +// @flow + +import type { I18nProps } from '@polkadot/ui-react-app/types'; +import type { Generator$Matches, Generator$Result } from './generator/types'; + +import './index.css'; + +import React from 'react'; +import Button from 'semantic-ui-react/dist/es/elements/Button'; +import Input from 'semantic-ui-react/dist/es/elements/Input'; +import Label from 'semantic-ui-react/dist/es/elements/Label'; + +import Match from './Match'; +import generator from './generator'; +import generatorSort from './generator/sort'; +import translate from './translate'; + +type Props = I18nProps & {}; + +type State = { + elapsed: number, + isMatchValid: boolean, + isRunning: boolean, + keyCount: 0, + keyTime: 0, + match: string, + matches: Generator$Matches, + startAt: number +} + +const DEFAULT_MATCH = 'S?me'; +const MATCH_REGEX = /[1-9A-Za-z?]*$/; + +class App extends React.PureComponent { + results: Array; + state: State; + + constructor (props: Props) { + super(props); + + this.results = []; + this.state = { + elapsed: 0, + isMatchValid: true, + isRunning: false, + keyCount: 0, + keyTime: 0, + match: DEFAULT_MATCH, + matches: [], + startAt: 0 + }; + } + + render (): React$Node { + const { className, style, t } = this.props; + const { elapsed, isMatchValid, isRunning, match, matches, keyCount } = this.state; + + return ( +
+
+
+ + +
+
+ +
+ {t('vanity.case.off', { + defaultValue: 'No' + })} +
+
+
+ +
+ {t('vanity.offset.off', { + defaultValue: 'No' + })} +
+
+
+
+ +
+ { + keyCount + ? ( +
+ {t('vanity.stats', { + defaultValue: 'Generated {{count}} keys in {{elapsed}}s ({{avg}}ms/key)', + replace: { + avg: (elapsed / keyCount).toFixed(3), + count: keyCount, + elapsed: (elapsed / 1000).toFixed(2) + } + })} +
+ ) + : null + } +
+ {matches.map((match) => ( + + ))} +
+
+ ); + } + + checkMatches (): void { + const results = this.results; + + this.results = []; + + if (results.length === 0) { + return; + } + + this.setState( + (prevState: State) => { + let newKeyCount = prevState.keyCount; + let newKeyTime = prevState.keyTime; + + const matches = results + .reduce((result, { elapsed, found }) => { + newKeyCount += found.length; + newKeyTime += elapsed; + + return result.concat(found); + }, prevState.matches) + .sort(generatorSort) + .slice(0, 25); + const elapsed = Date.now() - prevState.startAt; + + return { + elapsed, + matches, + keyCount: newKeyCount, + keyTime: newKeyTime + }; + } + ); + } + + executeGeneration = (): void => { + if (!this.state.isRunning) { + this.checkMatches(); + + return; + } + + setTimeout(() => { + if (this.results.length === 5) { + this.checkMatches(); + } + + this.results.push( + generator({ + match: this.state.match, + runs: 10 + }) + ); + + this.executeGeneration(); + }, 0); + } + + // eslint-disable-next-line no-unused-vars + onChangeMatch = (event: SyntheticEvent<*>, { value }): void => { + const isMatchValid = MATCH_REGEX.test(value) && value.length < 31; + + this.setState({ + isMatchValid, + match: value + }); + } + + onRemove = (address: string): void => { + this.setState( + (prevState: State) => ({ + matches: prevState.matches.filter((item) => + item.address !== address + ) + }) + ); + } + + toggleStart = (): void => { + this.setState( + (prevState: State) => { + const { isRunning, keyCount, keyTime, startAt } = prevState; + + return { + isRunning: !isRunning, + keyCount: !isRunning ? 0 : keyCount, + keyTime: !isRunning ? 0 : keyTime, + startAt: !isRunning ? Date.now() : startAt + }; + }, + this.executeGeneration + ); + } +} + +export default translate(App); diff --git a/packages/app-vanitygen/src/translate.js b/packages/app-vanitygen/src/translate.js new file mode 100644 index 0000000000..b4e459ec21 --- /dev/null +++ b/packages/app-vanitygen/src/translate.js @@ -0,0 +1,8 @@ +// Copyright 2017-2018 Jaco Greeff +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. +// @flow + +import { translate } from 'react-i18next'; + +export default translate(['vanitygen', 'ui']); diff --git a/packages/apps/src/SideBar/index.js b/packages/apps/src/SideBar/index.js index 717852adcc..c6cb7ec8e9 100644 --- a/packages/apps/src/SideBar/index.js +++ b/packages/apps/src/SideBar/index.js @@ -29,7 +29,7 @@ function SideBar ({ className, style, t }: Props): React$Node { vertical > { - routing.routes.map((route) => ( + routing.routes.map((route, index) => ( route ? ( ) - :