├── public ├── CNAME ├── meta.json ├── og.png ├── robots.txt ├── favicon │ ├── favicon.ico │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── mstile-150x150.png │ ├── apple-touch-icon.png │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── browserconfig.xml │ ├── site.webmanifest │ └── safari-pinned-tab.svg ├── sitemap.xml └── index.html ├── .github └── FUNDING.yml ├── src ├── img │ ├── logo.png │ ├── donation.png │ ├── modal-bg.png │ ├── modal-bg-main.png │ └── nano.svg ├── fonts │ ├── Nunito-Light.eot │ ├── Nunito-Light.ttf │ ├── NotoSans-Bold.eot │ ├── NotoSans-Bold.ttf │ ├── NotoSans-Bold.woff │ ├── NotoSans-Bold.woff2 │ ├── Nunito-Light.woff │ ├── Nunito-Light.woff2 │ ├── NotoSans-Regular.eot │ ├── NotoSans-Regular.ttf │ ├── NotoSans-Regular.woff │ └── NotoSans-Regular.woff2 ├── setupTests.js ├── App.test.js ├── index.css ├── tools │ ├── components │ │ ├── calculator │ │ │ ├── calc.css │ │ │ ├── button.jsx │ │ │ ├── screen.jsx │ │ │ ├── screenRow.jsx │ │ │ └── frame.jsx │ │ └── qrImageStyle.js │ ├── index.js │ ├── PaperWalletTool.css │ ├── Terms.js │ ├── Start.js │ ├── PaperWalletTool.js │ ├── ConvertTool.js │ ├── KeyGeneratorTool.js │ ├── DifficultyTool.js │ ├── PaymentTool.js │ ├── AddressExtractorTool.js │ ├── QRTool.js │ └── WorkGeneratorTool.js ├── print.css ├── index.js ├── build-or-validate_musig_wasm.sh ├── modules │ ├── check.js │ ├── conversion.js │ ├── find.worker.js │ ├── seed.worker.js │ └── nano-webgl-pow.js ├── App.js ├── serviceWorker.js ├── base32.js ├── App.css └── helpers.js ├── .gitignore ├── .vscode └── launch.json ├── generate-build-version.js ├── config-overrides.js ├── LICENSE ├── package.json ├── README.md └── dist └── main.js /public/CNAME: -------------------------------------------------------------------------------- 1 | tools.nanos.cc 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [Joohansson] -------------------------------------------------------------------------------- /public/meta.json: -------------------------------------------------------------------------------- 1 | {"version":"e55f30ab-25b2-4f3a-80da-c861015be826"} -------------------------------------------------------------------------------- /public/og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Joohansson/keytools/HEAD/public/og.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /src/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Joohansson/keytools/HEAD/src/img/logo.png -------------------------------------------------------------------------------- /src/img/donation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Joohansson/keytools/HEAD/src/img/donation.png -------------------------------------------------------------------------------- /src/img/modal-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Joohansson/keytools/HEAD/src/img/modal-bg.png -------------------------------------------------------------------------------- /public/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Joohansson/keytools/HEAD/public/favicon/favicon.ico -------------------------------------------------------------------------------- /src/fonts/Nunito-Light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Joohansson/keytools/HEAD/src/fonts/Nunito-Light.eot -------------------------------------------------------------------------------- /src/fonts/Nunito-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Joohansson/keytools/HEAD/src/fonts/Nunito-Light.ttf -------------------------------------------------------------------------------- /src/img/modal-bg-main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Joohansson/keytools/HEAD/src/img/modal-bg-main.png -------------------------------------------------------------------------------- /src/fonts/NotoSans-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Joohansson/keytools/HEAD/src/fonts/NotoSans-Bold.eot -------------------------------------------------------------------------------- /src/fonts/NotoSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Joohansson/keytools/HEAD/src/fonts/NotoSans-Bold.ttf -------------------------------------------------------------------------------- /src/fonts/NotoSans-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Joohansson/keytools/HEAD/src/fonts/NotoSans-Bold.woff -------------------------------------------------------------------------------- /src/fonts/NotoSans-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Joohansson/keytools/HEAD/src/fonts/NotoSans-Bold.woff2 -------------------------------------------------------------------------------- /src/fonts/Nunito-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Joohansson/keytools/HEAD/src/fonts/Nunito-Light.woff -------------------------------------------------------------------------------- /src/fonts/Nunito-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Joohansson/keytools/HEAD/src/fonts/Nunito-Light.woff2 -------------------------------------------------------------------------------- /src/fonts/NotoSans-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Joohansson/keytools/HEAD/src/fonts/NotoSans-Regular.eot -------------------------------------------------------------------------------- /src/fonts/NotoSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Joohansson/keytools/HEAD/src/fonts/NotoSans-Regular.ttf -------------------------------------------------------------------------------- /src/fonts/NotoSans-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Joohansson/keytools/HEAD/src/fonts/NotoSans-Regular.woff -------------------------------------------------------------------------------- /public/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Joohansson/keytools/HEAD/public/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Joohansson/keytools/HEAD/public/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicon/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Joohansson/keytools/HEAD/public/favicon/mstile-150x150.png -------------------------------------------------------------------------------- /src/fonts/NotoSans-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Joohansson/keytools/HEAD/src/fonts/NotoSans-Regular.woff2 -------------------------------------------------------------------------------- /public/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Joohansson/keytools/HEAD/public/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /public/favicon/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Joohansson/keytools/HEAD/public/favicon/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/favicon/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Joohansson/keytools/HEAD/public/favicon/android-chrome-512x512.png -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /public/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | https://tools.nanos.cc/index.html 5 | weekly 6 | 1.0 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/favicon/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #2b5797 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /src/tools/components/calculator/calc.css: -------------------------------------------------------------------------------- 1 | .input-button { 2 | width: 35px; 3 | height: 30px; 4 | margin: 1px; 5 | color: #000034; 6 | } 7 | 8 | .action-button { 9 | width: 50px; 10 | height: 30px; 11 | margin: 1px; 12 | color: #000034; 13 | } 14 | 15 | .dummy-button { 16 | width: 35px; 17 | margin: 1px; 18 | display: inline-block; 19 | } 20 | 21 | .calc-input { 22 | margin-bottom: 6px !important; 23 | } 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | # rpc creds 26 | /src/rpc.js 27 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "chrome", 9 | "request": "launch", 10 | "name": "Launch Chrome against localhost", 11 | "url": "http://localhost:3000", 12 | "webRoot": "${workspaceFolder}" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /public/favicon/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/img/favicon/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/img/favicon/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /src/print.css: -------------------------------------------------------------------------------- 1 | @media print { 2 | .noprint, 3 | .noprint * { 4 | display: none; 5 | } 6 | html, body, .QR-container, .QR-container-2x, .QR-container-4x, .QR-container-paper, .QR-container-paper-inner, .QR-container-payment, 7 | .QR-container-general-1x, .QR-container-general-2x, .QR-container-general-4x { 8 | background-color: #FFF !important; 9 | } 10 | 11 | .QR-container-payment { 12 | width: 276px !important; 13 | height: 276px !important; 14 | } 15 | 16 | .QR-img-payment { 17 | width: 256px !important; 18 | height: 256px !important; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import 'bootstrap/dist/css/bootstrap.css'; 5 | import '@fortawesome/fontawesome-free/css/all.min.css'; 6 | import App from './App'; 7 | import * as serviceWorker from './serviceWorker'; 8 | 9 | ReactDOM.render(, document.getElementById('root')); 10 | 11 | // If you want your app to work offline and load faster, you can change 12 | // unregister() to register() below. Note this comes with some pitfalls. 13 | // Learn more about service workers: https://bit.ly/CRA-PWA 14 | serviceWorker.register(); 15 | -------------------------------------------------------------------------------- /generate-build-version.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const fs = require('fs'); 3 | const packageJson = require('./package.json'); 4 | 5 | const appVersion = packageJson.version; 6 | 7 | const jsonData = { 8 | version: appVersion 9 | }; 10 | 11 | var jsonContent = JSON.stringify(jsonData); 12 | 13 | fs.writeFile('./public/meta.json', jsonContent, 'utf8', function(err) { 14 | if (err) { 15 | console.log('An error occured while writing JSON Object to meta.json'); 16 | return console.log(err); 17 | } 18 | 19 | console.log('meta.json file has been saved with latest version number'); 20 | }); 21 | 22 | /* eslint-enable */ 23 | -------------------------------------------------------------------------------- /src/tools/components/calculator/button.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; // import react module 2 | import { PropTypes } from 'prop-types'; 3 | 4 | // create our Button component as a functional component 5 | const Button = (props) => { 6 | return ( 7 | 13 | ); 14 | } 15 | 16 | // describe our expected props types 17 | Button.propTypes = { 18 | type: PropTypes.string.isRequired, 19 | handleClick: PropTypes.func.isRequired, 20 | label: PropTypes.string.isRequired 21 | } 22 | 23 | // export our button component. 24 | export default Button; 25 | -------------------------------------------------------------------------------- /config-overrides.js: -------------------------------------------------------------------------------- 1 | module.exports = function override(config, env) { 2 | //allow the use of web workers in chrome when running from a file system by using inline: true 3 | config.module.rules.push({ 4 | test: /\.worker\.js$/, 5 | use: { 6 | loader: 'worker-loader', 7 | options: { 8 | inline: true 9 | } 10 | } 11 | }) 12 | //ignore bip39 word lists that are not english. TODO: Make webpack work with non-ejected react 13 | //config.plugins.push( new webpack.IgnorePlugin(/^\.\/(?!english)/, /bip39\/src\/wordlists$/) ); 14 | return config; 15 | 16 | //just return the original config and pretend this never happened 17 | //return config; 18 | } 19 | -------------------------------------------------------------------------------- /src/build-or-validate_musig_wasm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Produces a musig_nano.wasm.b64 file. This can be placed in the index.html wasmBase64 script tag. 3 | docker run --rm -i ubuntu:focal > musig_nano.wasm.b64 <&2 < { 9 | return ( 10 |
11 | 12 | 13 |
14 | ); 15 | } 16 | 17 | // Define our props expected from the parent component 18 | Screen.propTypes = { 19 | question: PropTypes.string.isRequired, 20 | answer: PropTypes.string.isRequired 21 | } 22 | 23 | // export our component 24 | export default Screen; 25 | -------------------------------------------------------------------------------- /src/modules/check.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * nanocurrency-js: A toolkit for the Nano cryptocurrency. 3 | * Copyright (c) 2019 Marvin ROGER 4 | * Licensed under GPL-3.0 (https://git.io/vAZsK) 5 | */ 6 | /** @hidden */ 7 | export function checkString(candidate: {}): boolean { 8 | return typeof candidate === 'string' 9 | } 10 | 11 | /** @hidden */ 12 | export function checkNumber(candidate: {}): boolean { 13 | if (!checkString(candidate)) return false 14 | if ( 15 | String(candidate).startsWith('.') || 16 | String(candidate).endsWith('.') 17 | ) 18 | return false 19 | 20 | const numberWithoutDot = String(candidate).replace('.', '') 21 | // more than one '.' 22 | if (String(candidate).length - numberWithoutDot.length > 1) return false 23 | for (const char of numberWithoutDot) { 24 | if (char < '0' || char > '9') return false 25 | } 26 | 27 | return true 28 | } 29 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import './App.css' 3 | import './print.css'; 4 | import MainPage from './mainPage' 5 | import ClearCache from 'react-clear-cache'; 6 | 7 | const App: React.FC<{}> = () => { 8 | return ( 9 |
10 |
11 | 12 | {({ isLatestVersion, emptyCacheStorage }) => ( 13 | 28 | )} 29 | 30 |
31 | 32 |
33 | ); 34 | }; 35 | 36 | export default App; 37 | -------------------------------------------------------------------------------- /src/tools/components/calculator/screenRow.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { PropTypes } from 'prop-types'; 3 | import * as helpers from '../../../helpers' 4 | import { InputGroup, FormControl, Button} from 'react-bootstrap' 5 | 6 | // Screen row component is written as a functional component 7 | // it receives and displays (in an input field) a props (property) of value from 8 | // it's parent component 9 | const ScreenRow = (props) => { 10 | return ( 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | ) 20 | } 21 | 22 | // we describe the props (property) that the parent element is required to pass 23 | // into this component 24 | ScreenRow.propTypes = { 25 | value: PropTypes.string.isRequired 26 | } 27 | 28 | export default ScreenRow; 29 | -------------------------------------------------------------------------------- /src/tools/index.js: -------------------------------------------------------------------------------- 1 | import Start from './Start.js' 2 | import Terms from './Terms.js' 3 | import ConvertTool from './ConvertTool.js' 4 | import SeedTool from './SeedTool.js' 5 | import FindAddressTool from './FindAddressTool.js' 6 | import KeyGeneratorTool from './KeyGeneratorTool.js' 7 | import AddressExtractorTool from './AddressExtractorTool.js' 8 | import PaperWalletTool from './PaperWalletTool.js' 9 | import PaymentTool from './PaymentTool.js' 10 | import SigningTool from './SigningTool.js' 11 | import WorkGeneratorTool from './WorkGeneratorTool.js' 12 | import QRTool from './QRTool.js' 13 | import MessengerTool from './MessengerTool.js' 14 | import VanityTool from './VanityTool.js' 15 | import InspectTool from './InspectTool.js' 16 | import SweepTool from './SweepTool.js' 17 | import DifficultyTool from './DifficultyTool.js' 18 | import MultisigTool from './MultisigTool.js' 19 | 20 | export {Start, Terms, ConvertTool, SeedTool, FindAddressTool, KeyGeneratorTool, AddressExtractorTool, PaperWalletTool, PaymentTool, 21 | SigningTool, WorkGeneratorTool, QRTool, MessengerTool, VanityTool, InspectTool, SweepTool, DifficultyTool, MultisigTool} 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Joohansson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/img/nano.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 10 | 11 | 15 | 16 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/tools/PaperWalletTool.css: -------------------------------------------------------------------------------- 1 | .QR-container-paper { 2 | width: 100%; 3 | height: 460px; 4 | padding: 50px 0; 5 | font-size: 16px; 6 | font-weight: bold; 7 | text-align: center; 8 | } 9 | 10 | .QR-container-paper-inner { 11 | background-color: #EEE; 12 | border: #4a90e2 4px solid; 13 | width: 600px; 14 | height: 100%; 15 | padding: 10px 40px; 16 | text-align: center; 17 | } 18 | 19 | .QR-img-paper { 20 | width: 180px; 21 | height: 180px; 22 | } 23 | 24 | .QR-img-paper-left { 25 | float: left; 26 | } 27 | 28 | .QR-img-paper-right { 29 | float: right; 30 | } 31 | 32 | .paper-wallet-text-container { 33 | width: 100%; 34 | position: relative; 35 | display: flex; 36 | justify-content: space-between; 37 | margin-bottom: 8px; 38 | color: #222; 39 | } 40 | 41 | .paper-wallet-text { 42 | width: 180px; 43 | text-align: center; 44 | } 45 | 46 | .paper-wallet-logo { 47 | width: 100px; 48 | margin-top: 68px; 49 | } 50 | 51 | .address { 52 | color: #222; 53 | font-size: 14px; 54 | text-align: center; 55 | font-weight: normal; 56 | position: relative; 57 | display: flex; 58 | flex-direction: column; 59 | width: 100%; 60 | padding-top: 10px; 61 | } 62 | 63 | .mnemonic { 64 | color: #222; 65 | font-size: 17px; 66 | font-weight: normal; 67 | position: relative; 68 | display: flex; 69 | flex-direction: column; 70 | width: 100%; 71 | padding-top: 10px; 72 | } 73 | -------------------------------------------------------------------------------- /src/tools/components/qrImageStyle.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import QrCode from './qr-code.js'; 3 | 4 | export default class QrImageStyle extends React.Component { 5 | constructor(props) { 6 | super(props); 7 | this.state = { 8 | image: null, 9 | prevContent: this.props.qrContent 10 | }; 11 | } 12 | 13 | static getDerivedStateFromProps(props, state) { 14 | if (props.content !== state.prevContent) { 15 | const qr = document.createElement('canvas'); 16 | QrCode.render({ 17 | text: props.content, 18 | radius: 0.5, // 0.0 to 0.5 19 | ecLevel: 'Q', // L, M, Q, H 20 | fill: { 21 | type: 'radial-gradient', // or 'linear-gradient' 22 | position: [ 0.5,0.5,0, 0.5,0.5,0.75 ], //xPos,yPos,radius of inner and outer circle where position is 0-1 of full dimension 23 | colorStops: [ 24 | [ 0, '#376ab4' ], //from 0 to 100% (0-1) 25 | [ 1, '#000034' ], 26 | ] 27 | }, // foreground color 28 | background: null, // color or null for transparent 29 | size: props.size // in pixels 30 | }, qr); 31 | 32 | return { 33 | prevContent: props.content, 34 | image: qr.toDataURL('image/png') 35 | }; 36 | } 37 | return null; 38 | } 39 | 40 | render() { 41 | return QR; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /public/favicon/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/modules/conversion.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * nanocurrency-js: A toolkit for the Nano cryptocurrency. 3 | * Copyright (c) 2019 Marvin ROGER 4 | * Licensed under GPL-3.0 (https://git.io/vAZsK) 5 | */ 6 | import BigNumber from 'bignumber.js' 7 | 8 | import { checkNumber, checkString } from './check' 9 | 10 | const TunedBigNumber = BigNumber.clone({ EXPONENTIAL_AT: 1e9, DECIMAL_PLACES: 1e9 }) 11 | 12 | const ZEROES: { [index: string]: number | undefined } = { 13 | hex: 0, 14 | raw: 0, 15 | nano: 24, 16 | knano: 27, 17 | Nano: 30, 18 | NANO: 30, 19 | KNano: 33, 20 | MNano: 36, 21 | } 22 | 23 | /** 24 | * Convert a value from one Nano unit to another. 25 | * 26 | * @param value - The value to convert 27 | * @param params - Params 28 | * @returns Converted number 29 | */ 30 | export function convert(value: string, params: ConvertParams): string { 31 | const paramsNotValid = new Error('From or to is not valid') 32 | if (!params) throw paramsNotValid 33 | 34 | const fromZeroes: number | undefined = ZEROES[params.from] 35 | const toZeroes: number | undefined = ZEROES[params.to] 36 | 37 | if (typeof fromZeroes === 'undefined' || typeof toZeroes === 'undefined') { 38 | throw new Error('From or to is not valid') 39 | } 40 | 41 | const valueNotValid = new Error('Value is not valid') 42 | if (!checkString) throw valueNotValid 43 | if (params.from === 'hex') { 44 | if (!/^[0-9a-fA-F]{32}$/.test(value)) throw valueNotValid 45 | } else { 46 | if (!checkNumber(value)) throw valueNotValid 47 | } 48 | 49 | const difference = fromZeroes - toZeroes 50 | 51 | let bigNumber 52 | if (params.from === 'hex') { 53 | bigNumber = new TunedBigNumber(`0x${value}`) 54 | } else { 55 | bigNumber = new TunedBigNumber(value) 56 | } 57 | 58 | if (difference < 0) { 59 | for (let i = 0; i < -difference; i++) { 60 | bigNumber = bigNumber.dividedBy(10) 61 | } 62 | } else if (difference > 0) { 63 | for (let i = 0; i < difference; i++) { 64 | bigNumber = bigNumber.multipliedBy(10) 65 | } 66 | } 67 | 68 | if (params.to === 'hex') { 69 | return bigNumber.toString(16).padStart(32, '0') 70 | } 71 | 72 | return bigNumber.toString() 73 | } 74 | -------------------------------------------------------------------------------- /src/tools/Terms.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import * as helpers from '../helpers' 3 | const toolParam = 'terms' 4 | 5 | class Terms extends Component { 6 | componentDidMount = () => { 7 | this.setParams() 8 | } 9 | 10 | // Defines the url params 11 | setParams(type) { 12 | helpers.setURLParams('?tool='+toolParam) 13 | } 14 | 15 | render() { 16 | return ( 17 |
18 |

TERMS

19 |
    20 |
  • By using KeyTools, you are agreeing to be bound by these Website Terms and Conditions of Use and agree that you are responsible for the agreement with any applicable local laws.
  • 21 |
  • KeyTools may revise these Terms of Use for its Website at any time without prior notice. By using this Website, you are agreeing to be bound by the current version of these Terms and Conditions of Use.
  • 22 |
23 |

LINKS

24 |
    25 |
  • KeyTools are not responsible for any external contents that are linked from this site.
  • 26 |
  • Your access to and use of any external site is entirely at your own risk. The presence of links should not be understood to be approval, endorsement or guarantee to function as advertised.
  • 27 |
28 |

LIMITATIONS & REVISIONS

29 |
    30 |
  • KeyTools and its tools is a work in progress and may include technical errors. It will not be hold accountable for any damages that will arise with the use or inability to use the tools.
  • 31 |
  • It's the users responsibility to understand and correctly handle accounts and corresponding funds.
  • 32 |
  • KeyTools may change the tools contained on its Website at any time without notice. KeyTools does not make any commitment to update the content of the Website or source code repository.
  • 33 |
34 |

IP BLOCK

35 |
    36 |
  • KeyTools have the right to slow down or block access to RPC node server if any malicious or unreasonable high amount of requests are done.
  • 37 |
  • The IP number of the client is temporary stored for this purpose as a pure access log and nothing else.
  • 38 |
39 |
40 | ) 41 | } 42 | } 43 | export default Terms 44 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | KeyTools 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/tools/Start.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import * as helpers from '../helpers' 3 | import Logo from '../img/logo.png' 4 | 5 | class Start extends Component { 6 | componentDidMount = () => { 7 | this.setParams() 8 | } 9 | 10 | // Defines the url params 11 | setParams(type) { 12 | helpers.setURLParams('') 13 | } 14 | 15 | render() { 16 | return ( 17 |
18 | logo 19 |

KeyTools is a set of high performance web tools for Nano Currency

20 |
21 |

SECURE

22 |
    23 |
  • Be cautious when entering secret keys on untrusted devices. The recommended way is to download the site and run on your own trusted machine. Most of the tools works without a webserver by opening index.html from your filesystem directly in a browser. Chrome is recommended.
  • 24 |
  • Secret keys are generated by the trusted cryptographic library TweetNaCl
  • 25 |
  • No cookies or analytics trackers exists and no data entered on the site are stored remotely
  • 26 |
27 |

ACCESSIBLE

28 |
    29 |
  • Quick access to tools with hotkeys SHIFT+ALT+0-9 (CTRL+SHIFT+ALT+0-9 for 10-20)
  • 30 |
  • Memo field at the bottom for copying data between tools
  • 31 |
  • Each tool support URL params which can be bookmarked, shared or linked from other tools
  • 32 |
  • NOTE: The address bar won't be updated if running from a file system. However, URL parameters will still work. A valid workaround is to use this chrome webserver extension and launch from localhost:8887.
    33 | The same is needed for the Audio Messenger to work when running from the file system.
  • 34 |
35 |

AUDITABLE

36 |
    37 |
  • Open sourced on Github
  • 38 |
  • This site is directly hosted on Github Pages and protected by Cloudflare
  • 39 |
40 |

FREE

41 |
    42 |
  • Contributions to this project can be done via Github PRs or by a Nano Donation below
  • 43 |
44 |
45 | ) 46 | } 47 | } 48 | export default Start 49 | -------------------------------------------------------------------------------- /src/modules/find.worker.js: -------------------------------------------------------------------------------- 1 | import * as nano from 'nanocurrency' 2 | import * as nano_old from 'nanocurrency174' //must be used for high performance with derivePublicKey, including nano_old.init() 3 | import nacl from 'tweetnacl/nacl'; 4 | 5 | let running = false 6 | let currentAddressesCount = 0 7 | let nextReport = 0 8 | var searchAddress = '' 9 | var searchSeed = '' 10 | var indexStart = 0 11 | var indexEnd = 0 12 | var batchSize = 5000 13 | 14 | self.addEventListener("message", getMessage) // eslint-disable-line no-restricted-globals 15 | 16 | // Report back to main thread with stats 17 | function reportStats(addresses) { 18 | postMessage({ 19 | type: 'stats', 20 | payload: { 21 | addresses, 22 | }, 23 | }) 24 | } 25 | 26 | function search(index) { 27 | currentAddressesCount += 1 28 | const secretKey = nano_old.deriveSecretKey(searchSeed, index) 29 | const publicKey = nano_old.derivePublicKey(secretKey) 30 | const address = nano_old.deriveAddress(publicKey) 31 | 32 | if (address === searchAddress) { 33 | running = false 34 | postMessage({ 35 | type: 'match', 36 | payload: { 37 | index, 38 | }, 39 | }) 40 | } 41 | } 42 | 43 | async function searching() { 44 | setTimeout(() => { 45 | if (running) { 46 | const end = indexStart + batchSize 47 | for (let k = indexStart; k <= end; k+=1) { 48 | search(k); 49 | const now = Date.now(); 50 | if (now > nextReport) { 51 | reportStats(currentAddressesCount); 52 | currentAddressesCount = 0; 53 | nextReport = now + 1000; 54 | } 55 | indexStart++ 56 | } 57 | if (indexStart < indexEnd) { 58 | searching(); 59 | } 60 | else { 61 | running = false 62 | postMessage({ 63 | type: 'end', 64 | }) 65 | } 66 | } 67 | }, 0); 68 | } 69 | 70 | async function getMessage(event) { 71 | let message = event.data 72 | switch (message.type) { 73 | case 'start': { 74 | if (!running) { 75 | running = true 76 | await nano_old.init() 77 | nextReport = Date.now() + 1000 78 | currentAddressesCount = 0 79 | searchSeed = message.payload.seed 80 | searchAddress = message.payload.address.replace('nano','xrb') 81 | indexStart = parseInt(message.payload.indexStart) 82 | indexEnd = parseInt(message.payload.indexEnd) 83 | 84 | // make sure the batch size is not larger than the actual interval 85 | if (indexEnd - indexStart < 10000) { 86 | batchSize = indexEnd - indexStart 87 | } 88 | 89 | searching() 90 | } 91 | break; 92 | } 93 | 94 | case 'stop': { 95 | running = false 96 | postMessage({ 97 | type: 'stopped' 98 | }) 99 | break 100 | } 101 | 102 | default: { 103 | throw new Error(`Unknown message ${String(message.type)}`) 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "keytools", 3 | "version": "1.2.5", 4 | "private": true, 5 | "homepage": ".", 6 | "license": "MIT", 7 | "author": "Joohansson", 8 | "repository": "https://github.com/joohansson/keytools", 9 | "keywords": [ 10 | "nano", 11 | "raiblocks", 12 | "xrb", 13 | "address", 14 | "account", 15 | "seed", 16 | "private", 17 | "public", 18 | "key", 19 | "wallet", 20 | "search", 21 | "generator", 22 | "qr", 23 | "reader", 24 | "vanity", 25 | "sign" 26 | ], 27 | "scripts": { 28 | "start-old": "react-scripts start", 29 | "build-old": "react-scripts build", 30 | "test-old": "react-scripts test", 31 | "generate-build-version": "./node_modules/react-clear-cache/bin/cli.js", 32 | "prebuild": "npm run generate-build-version", 33 | "start": "react-app-rewired start", 34 | "build": "react-app-rewired build", 35 | "test": "react-app-rewired test --env=jsdom", 36 | "eject": "react-scripts eject", 37 | "deploy": "npm run build && gh-pages -d build" 38 | }, 39 | "eslintConfig": { 40 | "extends": "react-app" 41 | }, 42 | "browserslist": { 43 | "production": [ 44 | ">0.2%", 45 | "not dead", 46 | "not op_mini all" 47 | ], 48 | "development": [ 49 | "last 1 chrome version", 50 | "last 1 firefox version", 51 | "last 1 safari version" 52 | ] 53 | }, 54 | "dependencies": { 55 | "@fortawesome/fontawesome-free": "^5.15.1", 56 | "@testing-library/jest-dom": "^4.2.4", 57 | "@testing-library/react": "^9.5.0", 58 | "@testing-library/user-event": "^7.1.2", 59 | "big-integer": "^1.6.48", 60 | "bigdecimal": "^0.6.1", 61 | "bignumber": "^1.1.0", 62 | "bip39": "^3.0.3", 63 | "bootstrap": "^4.5.3", 64 | "canvas-filters": "^1.0.1", 65 | "chirpsdk": "^3.2.0", 66 | "concat-typed-array": "^1.0.2", 67 | "core-js": "^3.8.1", 68 | "dom-to-image": "^2.6.0", 69 | "fibers": "^4.0.3", 70 | "file-saver": "^2.0.5", 71 | "glamor": "^2.20.40", 72 | "hermes-channel": "0.0.6", 73 | "hsimp-named-number": "^0.1.3", 74 | "jquery": "^3.5.1", 75 | "js-base64": "^2.6.4", 76 | "jsqr": "^1.3.1", 77 | "nano-webgl-pow": "^1.1.1", 78 | "nanocurrency": "npm:nanocurrency@^2.5.0", 79 | "nanocurrency-web": "^1.2.2", 80 | "nanocurrency174": "npm:nanocurrency@1.7.4", 81 | "node-sass": "^4.14.1", 82 | "prop-types": "^15.7.2", 83 | "react": "^17.0.1", 84 | "react-bootstrap": "^1.4.0", 85 | "react-clear-cache": "^1.4.12", 86 | "react-confirm-alert": "^2.6.2", 87 | "react-dom": "^17.0.1", 88 | "react-hot-keys": "^2.6.0", 89 | "react-scripts": "^3.4.3", 90 | "react-toastify": "^5.4.1", 91 | "remove-trailing-zeros": "^1.0.3", 92 | "sass": "^1.30.0", 93 | "simple-encryptor": "^3.0.0", 94 | "tweetnacl": "^1.0.3", 95 | "typescript": "^3.9.7" 96 | }, 97 | "devDependencies": { 98 | "gh-pages": "^2.1.1", 99 | "react-app-rewired": "^2.1.8", 100 | "worker-loader": "^2.0.0" 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/modules/seed.worker.js: -------------------------------------------------------------------------------- 1 | import * as nano from 'nanocurrency' 2 | import * as nano_old from 'nanocurrency174' //must be used for high performance with derivePublicKey, including nano_old.init() 3 | import nacl from 'tweetnacl/nacl' 4 | 5 | const BATCH_SIZE = 5000 6 | let running = false 7 | let currentAddressesCount = 0 8 | let nextReport = 0 9 | var initChar = '' 10 | var prefix = '' 11 | var suffix = '' 12 | var initCharInt = 0 //used to subtract the match start point 13 | 14 | self.addEventListener("message", getMessage) // eslint-disable-line no-restricted-globals 15 | 16 | // Report back to main thread with stats 17 | function reportStats(addresses) { 18 | postMessage({ 19 | type: 'stats', 20 | payload: { 21 | addresses, 22 | }, 23 | }) 24 | } 25 | 26 | // Check if address (without nano_) matches the prefix 27 | function isMatch(address) { 28 | if (address.substr(5-initCharInt).startsWith(prefix) && address.endsWith(suffix)) { 29 | return true 30 | } 31 | return false 32 | } 33 | 34 | function search() { 35 | currentAddressesCount += 1 36 | const rand = nacl.randomBytes(32) 37 | const seed = rand.reduce((hex, idx) => hex + (`0${idx.toString(16)}`).slice(-2), '') 38 | const secretKey = nano_old.deriveSecretKey(seed, 0) 39 | const publicKey = nano_old.derivePublicKey(secretKey) 40 | const address = nano_old.deriveAddress(publicKey) 41 | 42 | if (isMatch(address)) { 43 | const wallet = { 44 | seed, 45 | secretKey, 46 | publicKey, 47 | address: address.replace('xrb','nano'), 48 | } 49 | postMessage({ 50 | type: 'match', 51 | payload: { 52 | wallet, 53 | }, 54 | }) 55 | } 56 | } 57 | 58 | async function searching() { 59 | setTimeout(() => { 60 | if (running) { 61 | for (let i = 0; i < BATCH_SIZE; i += 1) { 62 | search(); 63 | const now = Date.now(); 64 | if (now > nextReport) { 65 | reportStats(currentAddressesCount); 66 | currentAddressesCount = 0; 67 | nextReport = now + 1000; 68 | } 69 | } 70 | searching(); 71 | } 72 | }, 0); 73 | } 74 | 75 | async function getMessage(event) { 76 | let message = event.data 77 | switch (message.type) { 78 | case 'start': { 79 | if (!running) { 80 | running = true 81 | await nano_old.init() 82 | nextReport = Date.now() + 1000 83 | currentAddressesCount = 0 84 | initChar = message.payload.initChar 85 | 86 | // if the init char is 1 or 3 87 | if (initChar !== '') { 88 | initCharInt = 1 // change starting point of match 89 | prefix = initChar + message.payload.prefix 90 | } 91 | else { 92 | prefix = message.payload.prefix 93 | } 94 | 95 | suffix = message.payload.suffix 96 | searching() 97 | } 98 | break; 99 | } 100 | 101 | case 'stop': { 102 | running = false 103 | postMessage({ 104 | type: 'stopped' 105 | }) 106 | break 107 | } 108 | 109 | default: { 110 | throw new Error(`Unknown message ${String(message.type)}`) 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KeyTools - High performance Nano web tools written with reactJS 2 | * Can be run from local file system offline by downloading [the gh-pages branch zip package](https://codeload.github.com/Joohansson/keytools/zip/gh-pages) 3 | * Hosted by [github pages](https://github.com/Joohansson/keytools/tree/gh-pages) via https://tools.nanos.cc 4 | 5 | ## Developer instructions 6 | 7 | ### Prepare for build (Ubuntu or Windows) 8 | Install nodejs and npm 9 | * [Ubuntu](https://tecadmin.net/install-latest-nodejs-npm-on-ubuntu/) 10 | * [Windows](https://www.guru99.com/download-install-node-js.html) 11 | 12 | `git clone https://github.com/Joohansson/keytools`\ 13 | `cd keytools`\ 14 | `npm install` 15 | 16 | Create an empty file in the src folder called rpc.js. It usually contains credentials for connecting to the RPC server and that is not shared publicly. However, you will not be able to use the Network Inspector tool or read RPC data in off-chain signing without these credentials or setting up your own RPC server. The file is needed to build the app. 17 | Syntax of the file: 18 | 19 | `export const RPC_SERVER = 'https://myApiServer/'`\ 20 | `export const RPC_LIMIT = 'Message when blocked by the API.'`\ 21 | `export const RPC_CREDS = 'username:password'` 22 | 23 | ### Test application 24 | `npm start` 25 | 26 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 27 | 28 | ### Build application 29 | 30 | `npm run build` 31 | 32 | Builds the app for production to the `build` folder.
33 | It correctly bundles React in production mode and optimizes the build for the best performance. 34 | 35 | Your app is ready to be deployed! 36 | 37 | ### Get started adding a new tool 38 | 39 | 1. Copy any tool.js file and rename it to MyTool.js 40 | 2. Alter the row at the top: 41 | 3. Alter the row at the bottom: 42 | 4. Change tools/index.js: Add 43 | 5. Change tools/index.js: Add the new class to 44 | 6. Change mainPage.js: Add the new class to 45 | 7. Change mainPage.js: Add the new entry to 46 | 8. Change mainPage.js: Add a tool dropdown title to 47 | 9. Change mainPage.js: Add tool at <{(active === 'CONVERT') && }...> 48 | 10. Change mainPage.js: Add a section to control URL params at 49 | 11. For hotkeys, search for this and add appropriate key: shift+alt+0 50 | 12. The new tool should now be available from the main dropdown selector. Happy coding! 51 | 52 | ## Some Nice Libraries Used 53 | 54 | * [Nano library - Nano-currency-js](https://github.com/marvinroger/nanocurrency-js) 55 | * [Nano library - Nano-currency-web-js](https://github.com/numsu/nanocurrency-web-js) 56 | * [PoW Generation - Nano webGL](https://github.com/numtel/nano-webgl-pow) 57 | * [BIP39 Mnemonics](https://www.npmjs.com/package/bip39) 58 | * [Notifications](https://github.com/fkhadra/react-toastify) 59 | * [Fancy QR generation - jsQR](https://github.com/cozmo/jsQR) 60 | * [Auditable high-security cryptographic library - Tweetnacl](https://tweetnacl.js.org/) 61 | * [Messaging over sound - Chirp](https://developers.chirp.io/) 62 | * [Musig - Multisig library](https://github.com/PlasmaPower/musig-nano) 63 | 64 | ## Inspiration Hall of Fame 65 | 66 | * Nanoo tools: https://nanoo.tools/ 67 | * Nanoaddr: https://nanoaddr.io 68 | 69 | ## Contribution 70 | 71 | Find this useful? Send me a Nano donation at `nano_1gur37mt5cawjg5844bmpg8upo4hbgnbbuwcerdobqoeny4ewoqshowfakfo` 72 | -------------------------------------------------------------------------------- /src/tools/components/calculator/frame.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; // import react module 2 | import Screen from './screen'; // Import our screen component from this directory 3 | import Button from './button'; // Import our button component from this directory 4 | import './calc.css'; 5 | 6 | // create a class which extends react component 7 | class Calculator extends React.Component { 8 | constructor() { 9 | super(); 10 | // set our default state 11 | this.state = { 12 | question: '', 13 | answer: '' 14 | } 15 | // Bind our handleClick method (sets 'this' explicitly to refer to this componenent) 16 | // We did this because 'this' would refer to the source of the click events 17 | this.handleClick = this.handleClick.bind(this); 18 | } 19 | 20 | // Render function to creat component to be rendered on the DOM. 21 | // This method must return a single parent element as you can see here. 22 | // The component is wrapped around () to make it a single expression. 23 | render() { 24 | return ( 25 |
26 | 27 |
28 |
35 |
36 |
43 |
44 |
51 |
52 | ); 53 | } 54 | 55 | // our method to handle all click events from our buttons 56 | handleClick(event){ 57 | const value = event.target.value; // get the value from the target element (button) 58 | switch (value) { 59 | case '=': { // if it's an equal sign, use the eval module to evaluate the question 60 | // convert the answer (in number) to String 61 | var answer 62 | try { 63 | // eslint-disable-next-line no-eval 64 | var res = (0, eval)(this.state.question) 65 | if (res !== undefined) { 66 | answer = res.toString(); 67 | } 68 | else { 69 | return //no value to calculate 70 | } 71 | } 72 | catch(err) { 73 | answer = '' 74 | } 75 | // update answer in our state. 76 | this.setState({ answer }); 77 | break; 78 | } 79 | case 'Cls': { 80 | // if it's the Cls sign, just clean our question and answer in the state 81 | this.setState({ question: '', answer: '' }); 82 | break; 83 | } 84 | default: { 85 | // for every other commmand, update the answer in the state 86 | let val = this.state.question 87 | val += value 88 | this.setState({ 89 | question: val 90 | }) 91 | break; 92 | } 93 | } 94 | } 95 | } 96 | 97 | // export our frame component. To be used in our client/index.js file 98 | export default Calculator; 99 | -------------------------------------------------------------------------------- /dist/main.js: -------------------------------------------------------------------------------- 1 | /******/ (function(modules) { // webpackBootstrap 2 | /******/ // The module cache 3 | /******/ var installedModules = {}; 4 | /******/ 5 | /******/ // The require function 6 | /******/ function __webpack_require__(moduleId) { 7 | /******/ 8 | /******/ // Check if module is in cache 9 | /******/ if(installedModules[moduleId]) { 10 | /******/ return installedModules[moduleId].exports; 11 | /******/ } 12 | /******/ // Create a new module (and put it into the cache) 13 | /******/ var module = installedModules[moduleId] = { 14 | /******/ i: moduleId, 15 | /******/ l: false, 16 | /******/ exports: {} 17 | /******/ }; 18 | /******/ 19 | /******/ // Execute the module function 20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 21 | /******/ 22 | /******/ // Flag the module as loaded 23 | /******/ module.l = true; 24 | /******/ 25 | /******/ // Return the exports of the module 26 | /******/ return module.exports; 27 | /******/ } 28 | /******/ 29 | /******/ 30 | /******/ // expose the modules object (__webpack_modules__) 31 | /******/ __webpack_require__.m = modules; 32 | /******/ 33 | /******/ // expose the module cache 34 | /******/ __webpack_require__.c = installedModules; 35 | /******/ 36 | /******/ // define getter function for harmony exports 37 | /******/ __webpack_require__.d = function(exports, name, getter) { 38 | /******/ if(!__webpack_require__.o(exports, name)) { 39 | /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); 40 | /******/ } 41 | /******/ }; 42 | /******/ 43 | /******/ // define __esModule on exports 44 | /******/ __webpack_require__.r = function(exports) { 45 | /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { 46 | /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); 47 | /******/ } 48 | /******/ Object.defineProperty(exports, '__esModule', { value: true }); 49 | /******/ }; 50 | /******/ 51 | /******/ // create a fake namespace object 52 | /******/ // mode & 1: value is a module id, require it 53 | /******/ // mode & 2: merge all properties of value into the ns 54 | /******/ // mode & 4: return value when already ns object 55 | /******/ // mode & 8|1: behave like require 56 | /******/ __webpack_require__.t = function(value, mode) { 57 | /******/ if(mode & 1) value = __webpack_require__(value); 58 | /******/ if(mode & 8) return value; 59 | /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; 60 | /******/ var ns = Object.create(null); 61 | /******/ __webpack_require__.r(ns); 62 | /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); 63 | /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); 64 | /******/ return ns; 65 | /******/ }; 66 | /******/ 67 | /******/ // getDefaultExport function for compatibility with non-harmony modules 68 | /******/ __webpack_require__.n = function(module) { 69 | /******/ var getter = module && module.__esModule ? 70 | /******/ function getDefault() { return module['default']; } : 71 | /******/ function getModuleExports() { return module; }; 72 | /******/ __webpack_require__.d(getter, 'a', getter); 73 | /******/ return getter; 74 | /******/ }; 75 | /******/ 76 | /******/ // Object.prototype.hasOwnProperty.call 77 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 78 | /******/ 79 | /******/ // __webpack_public_path__ 80 | /******/ __webpack_require__.p = ""; 81 | /******/ 82 | /******/ 83 | /******/ // Load entry module and return exports 84 | /******/ return __webpack_require__(__webpack_require__.s = "./src/index.js"); 85 | /******/ }) 86 | /************************************************************************/ 87 | /******/ ({ 88 | 89 | /***/ "./src/index.js": 90 | /*!**********************!*\ 91 | !*** ./src/index.js ***! 92 | \**********************/ 93 | /*! no static exports found */ 94 | /***/ (function(module, exports) { 95 | 96 | eval("throw new Error(\"Module parse failed: Unexpected token (9:16)\\nYou may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders\\n| //import * as serviceWorker from './serviceWorker';\\n| \\n> ReactDOM.render(, document.getElementById('root'));\\n| \\n| // If you want your app to work offline and load faster, you can change\");\n\n//# sourceURL=webpack:///./src/index.js?"); 97 | 98 | /***/ }) 99 | 100 | /******/ }); -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl, { 104 | headers: { 'Service-Worker': 'script' } 105 | }) 106 | .then(response => { 107 | // Ensure service worker exists, and that we really are getting a JS file. 108 | const contentType = response.headers.get('content-type'); 109 | if ( 110 | response.status === 404 || 111 | (contentType != null && contentType.indexOf('javascript') === -1) 112 | ) { 113 | // No service worker found. Probably a different app. Reload the page. 114 | navigator.serviceWorker.ready.then(registration => { 115 | registration.unregister().then(() => { 116 | window.location.reload(); 117 | }); 118 | }); 119 | } else { 120 | // Service worker found. Proceed as normal. 121 | registerValidSW(swUrl, config); 122 | } 123 | }) 124 | .catch(() => { 125 | console.log( 126 | 'No internet connection found. App is running in offline mode.' 127 | ); 128 | }); 129 | } 130 | 131 | export function unregister() { 132 | if ('serviceWorker' in navigator) { 133 | navigator.serviceWorker.ready.then(registration => { 134 | registration.unregister(); 135 | }); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/tools/PaperWalletTool.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import * as nano from 'nanocurrency' 3 | import * as nano_old from 'nanocurrency174' 4 | import { wallet } from 'nanocurrency-web' 5 | import { InputGroup, FormControl, Button} from 'react-bootstrap' 6 | import { ReactComponent as NanoLogo } from '../img/nano.svg'; 7 | import './PaperWalletTool.css'; 8 | import * as helpers from '../helpers' 9 | import { saveAs } from 'file-saver' 10 | import domtoimage from 'dom-to-image' 11 | import QrImageStyle from './components/qrImageStyle' 12 | import {toast } from 'react-toastify' 13 | const toolParam = 'paper' 14 | 15 | class PaperWalletTool extends Component { 16 | constructor(props) { 17 | super(props) 18 | 19 | this.inputToast = null //disallow duplicates 20 | 21 | this.state = { 22 | seed: '', 23 | mnemonic: '', 24 | qrSeedContent: '', 25 | qrAddressContent: '', 26 | qrSize: 720, 27 | } 28 | 29 | this.componentDidMount = this.componentDidMount.bind(this) 30 | this.handleSeedChange = this.handleSeedChange.bind(this) 31 | this.generate = this.generate.bind(this) 32 | this.clearText = this.clearText.bind(this) 33 | this.download = this.download.bind(this) 34 | } 35 | 36 | async componentDidMount() { 37 | await nano_old.init() 38 | this.generate() 39 | 40 | // Read URL params from parent and construct new quick path 41 | this.setParams() 42 | } 43 | 44 | // Defines the url params 45 | setParams() { 46 | helpers.setURLParams('?tool='+toolParam) 47 | } 48 | 49 | //Clear text from input field 50 | clearText(event) { 51 | this.setState({ 52 | seed: '', 53 | mnemonic: '', 54 | qrSeedContent: '', 55 | qrAddressContent: '', 56 | }) 57 | } 58 | 59 | handleSeedChange(event) { 60 | this.seedChange(event.target.value) 61 | } 62 | 63 | seedChange(seed) { 64 | if (!nano.checkSeed(seed)) { 65 | this.setState({ 66 | seed: seed, 67 | }) 68 | if (seed !== '') { 69 | if (! toast.isActive(this.inputToast)) { 70 | this.inputToast = toast("Not a valid seed", helpers.getToast(helpers.toastType.ERROR_AUTO)) 71 | } 72 | } 73 | return 74 | } 75 | 76 | let nanowallet = wallet.generate(seed) 77 | let mnemonic = nanowallet.mnemonic 78 | let privKey = nano_old.deriveSecretKey(seed, 0) 79 | let pubKey = nano_old.derivePublicKey(privKey) 80 | this.setState({ 81 | seed: seed, 82 | mnemonic: mnemonic, 83 | qrSeedContent: seed, 84 | qrAddressContent: nano.deriveAddress(pubKey, {useNanoPrefix: true}), 85 | }) 86 | } 87 | 88 | async generate() { 89 | var seed = helpers.genSecureKey() 90 | seed = seed.toUpperCase() 91 | this.seedChange(seed) 92 | } 93 | 94 | print() { 95 | window.print() 96 | } 97 | 98 | /* Download card */ 99 | download(event) { 100 | var imageWidth = 600 101 | var imageHeight = 360 102 | var node = null 103 | 104 | node = this.refs.QRContainerPaperInner 105 | 106 | domtoimage.toPng(node, { 107 | width: imageWidth, 108 | height: imageHeight, 109 | }).then(function (dataUrl) { 110 | /* Filesaver has better cross browser support */ 111 | saveAs(dataUrl, "paperwallet--" + this.state.qrAddressContent.slice(0,9) + "..." + this.state.qrAddressContent.slice(61,65) +".png"); 112 | }.bind(this)) 113 | .catch(function (error) { 114 | console.error('Error, something went wrong downloading paperwallet!', error); 115 | }); 116 | } 117 | 118 | render() { 119 | return ( 120 |
121 |
122 |

Generate simple paper wallets with a SEED, ADDRESS & MNEMONIC

123 |
    124 |
  • The address is derived using seed index 0
  • 125 |
  • The seed from the Vanity Address Generator works here too!
  • 126 |
127 | 128 | 129 | 130 | 131 | Seed 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | Address 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 |
158 | 159 |
160 |
161 |
162 |
SEED
163 |
ADDRESS
164 |
165 | 166 | 167 | 168 |
169 | {this.state.qrAddressContent} 170 |
171 |
172 | {this.state.mnemonic} 173 |
174 |
175 |
176 |
177 | ) 178 | } 179 | } 180 | export default PaperWalletTool 181 | -------------------------------------------------------------------------------- /src/tools/ConvertTool.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { InputGroup, FormControl, Button} from 'react-bootstrap' 3 | import * as helpers from '../helpers' 4 | import {toast } from 'react-toastify' 5 | import * as nano from 'nanocurrency' 6 | import Calculator from './components/calculator/frame.jsx' 7 | import QrImageStyle from './components/qrImageStyle' 8 | const toolParam = 'convert' 9 | 10 | class ConvertTool extends Component { 11 | constructor(props) { 12 | super(props) 13 | 14 | this.inputToast = null //disallow duplicates 15 | 16 | this.state = { 17 | raw: '1000000000000000000000000000000', 18 | Mnano: '1', 19 | qrContent: '1', 20 | qrSize: 512, 21 | activeQR: '', 22 | qrState: 0, //qr size 23 | qrHidden: true, 24 | } 25 | 26 | this.handleRawChange = this.handleRawChange.bind(this) 27 | this.handleMnanoChange = this.handleMnanoChange.bind(this) 28 | this.updateQR = this.updateQR.bind(this) 29 | this.clearText = this.clearText.bind(this) 30 | this.double = this.double.bind(this) 31 | this.setParams = this.setParams.bind(this) 32 | } 33 | 34 | componentDidMount = () => { 35 | // Read URL params from parent and construct new quick path 36 | var raw = this.props.state.raw 37 | var nano = this.props.state.nano 38 | 39 | if (raw) { 40 | this.rawChange(raw) 41 | } 42 | else if (nano) { 43 | 44 | this.MnanoChange(nano) 45 | } 46 | else { 47 | this.setParams() 48 | } 49 | } 50 | 51 | // Defines the url params 52 | setParams(type) { 53 | switch (type) { 54 | case 'raw': 55 | helpers.setURLParams('?tool='+toolParam + '&raw=' + this.state.raw) 56 | break 57 | 58 | case 'nano': 59 | helpers.setURLParams('?tool='+toolParam + '&nano=' + this.state.Mnano) 60 | break 61 | 62 | default: 63 | helpers.setURLParams('?tool='+toolParam + '&nano=' + this.state.Mnano) 64 | break 65 | } 66 | } 67 | 68 | //Clear text from input field 69 | clearText(event) { 70 | this.setState({ 71 | raw: '', 72 | Mnano: '' 73 | }, 74 | function() { 75 | this.updateQR() 76 | this.setParams() 77 | }) 78 | } 79 | 80 | // loop qr state 1x, 2x, 4x 81 | double() { 82 | var state = this.state.qrState 83 | state = state + 1 84 | if (state >= helpers.qrClassesContainer.length) { 85 | state = 0 86 | } 87 | this.setState({ 88 | qrState: state 89 | }) 90 | } 91 | 92 | // Any QR button is pressed. Handle active button. 93 | // Any QR button is pressed 94 | handleQRChange = changeEvent => { 95 | let val = changeEvent.target.value 96 | // deselect button if clicking on the same button 97 | if (this.state.qrActive === val) { 98 | this.setState({ 99 | qrActive: '', 100 | qrHidden: true 101 | }) 102 | } 103 | else { 104 | this.setState({ 105 | qrActive: val, 106 | qrHidden: false, 107 | }, 108 | function() { 109 | this.updateQR() 110 | }) 111 | } 112 | } 113 | 114 | updateQR() { 115 | switch(this.state.qrActive) { 116 | case 'raw': 117 | this.setState({ 118 | qrContent: this.state.raw, 119 | }) 120 | break 121 | case 'Mnano': 122 | this.setState({ 123 | qrContent: this.state.Mnano, 124 | }) 125 | break 126 | default: 127 | this.setState({ 128 | qrContent: '', 129 | qrHidden: true, 130 | }) 131 | break 132 | } 133 | } 134 | 135 | handleRawChange(event) { 136 | this.rawChange(event.target.value) 137 | } 138 | rawChange(val) { 139 | if (!nano.checkAmount(val)) { 140 | if (val !== '' && val.slice(-1) !== '.') { 141 | if (! toast.isActive(this.inputToast)) { 142 | this.inputToast = toast("Invalid amount", helpers.getToast(helpers.toastType.ERROR_AUTO)) 143 | } 144 | } 145 | else { 146 | helpers.setURLParams('?tool='+toolParam + '&nano=') 147 | } 148 | this.setState({ 149 | raw: val, 150 | Mnano: '' 151 | }) 152 | return 153 | } 154 | this.setState({ 155 | raw: val, 156 | Mnano: helpers.rawToMnano(val) 157 | }, 158 | function() { 159 | this.updateQR() 160 | this.setParams('raw') 161 | }) 162 | } 163 | 164 | handleMnanoChange(event) { 165 | this.MnanoChange(event.target.value) 166 | } 167 | MnanoChange(val) { 168 | let raw = helpers.MnanoToRaw(val) 169 | if (!nano.checkAmount(raw)) { 170 | if (val !== '' && val.slice(-1) !== '.') { 171 | if (! toast.isActive(this.inputToast)) { 172 | this.inputToast = toast("Invalid amount", helpers.getToast(helpers.toastType.ERROR_AUTO)) 173 | } 174 | } 175 | else { 176 | helpers.setURLParams('?tool='+toolParam + '&nano=') 177 | } 178 | this.setState({ 179 | raw: '', 180 | Mnano: val 181 | }) 182 | return 183 | } 184 | this.setState({ 185 | raw: raw, 186 | Mnano: val 187 | }, 188 | function() { 189 | this.updateQR() 190 | this.setParams('nano') 191 | }) 192 | } 193 | 194 | render() { 195 | return ( 196 |
197 |

Convert between Nano units

198 |
    199 |
  • raw is the smallest possible unit
  • 200 |
  • Nano (previous Mnano) is used in wallets and exchanges
  • 201 |
  • 1 Nano = 10^30 raw
  • 202 |
203 | 204 | 205 | 206 | 207 | raw 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | Nano 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 |

Calculator

233 | 234 | 235 |
236 |
237 | 238 |
239 |
240 |
241 | ) 242 | } 243 | } 244 | export default ConvertTool 245 | -------------------------------------------------------------------------------- /src/tools/KeyGeneratorTool.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import * as nano from 'nanocurrency' 3 | import * as nano_old from 'nanocurrency174' //must be used for high performance with derivePublicKey, including nano_old.init() 4 | import { InputGroup, FormControl, Button} from 'react-bootstrap' 5 | import * as helpers from '../helpers' 6 | import {toast } from 'react-toastify' 7 | const toolParam = 'keygen' 8 | 9 | class KeyGeneratorTool extends Component { 10 | constructor(props) { 11 | super(props) 12 | 13 | this.inputToast = null //disallow duplicates 14 | 15 | this.state = { 16 | count: '10', 17 | generating: false, 18 | validCount: true, 19 | walletNoChecked: true, 20 | privKeyChecked: true, 21 | addressChecked: true, 22 | pubKeyChecked: true, 23 | output: '', 24 | } 25 | 26 | this.setMax = this.setMax.bind(this) 27 | this.handleCountChange = this.handleCountChange.bind(this) 28 | this.handleWalletNoCheck = this.handleWalletNoCheck.bind(this) 29 | this.handlePrivKeyCheck = this.handlePrivKeyCheck.bind(this) 30 | this.handlePubKeyCheck = this.handlePubKeyCheck.bind(this) 31 | this.handleAddressCheck = this.handleAddressCheck.bind(this) 32 | this.generate = this.generate.bind(this) 33 | } 34 | 35 | componentDidMount() { 36 | // Read URL params from parent and construct new quick path 37 | var count = this.props.state.count 38 | var priv = this.props.state.priv 39 | var pub = this.props.state.pub 40 | var addr = this.props.state.addr 41 | if (count) { 42 | this.countChange(count) 43 | } 44 | if (typeof priv !== 'undefined') { 45 | this.setState({ 46 | privKeyChecked: (priv === 'true') 47 | }) 48 | } 49 | if (typeof pub !== 'undefined') { 50 | this.setState({ 51 | pubKeyChecked: (pub === 'true') 52 | }) 53 | } 54 | if (typeof addr !== 'undefined') { 55 | this.setState({ 56 | addressChecked: (addr === 'true') 57 | }) 58 | } 59 | if(!count && !priv && !pub && !addr) { 60 | this.setParams() 61 | } 62 | } 63 | 64 | // Defines the url params 65 | setParams() { 66 | helpers.setURLParams('?tool='+toolParam + '&count='+this.state.count + '&priv='+this.state.privKeyChecked + '&pub='+this.state.pubKeyChecked + '&addr='+this.state.addressChecked) 67 | } 68 | 69 | setMax() { 70 | this.setState({ 71 | count: helpers.constants.KEYS_MAX 72 | },function() { 73 | this.setParams() 74 | }) 75 | } 76 | 77 | handleCountChange(event) { 78 | this.countChange(event.target.value) 79 | } 80 | 81 | countChange(count) { 82 | if (count > helpers.constants.KEYS_MAX) { 83 | count = helpers.constants.KEYS_MAX 84 | } 85 | this.setState({ 86 | count: count 87 | }, 88 | function() { 89 | if (!helpers.isNumeric(count)) { 90 | this.setState({ 91 | validCount: false 92 | }) 93 | if (count !== '') { 94 | if (! toast.isActive(this.inputToast)) { 95 | this.inputToast = toast("Not a valid count", helpers.getToast(helpers.toastType.ERROR_AUTO)) 96 | } 97 | } 98 | } 99 | else { 100 | this.setParams() 101 | this.setState({ 102 | validCount: true 103 | }) 104 | } 105 | }) 106 | } 107 | 108 | handleWalletNoCheck(event) { 109 | this.setState({ 110 | walletNoChecked: event.target.checked 111 | }, 112 | function() { 113 | this.setParams() 114 | }) 115 | } 116 | 117 | handlePrivKeyCheck(event) { 118 | this.setState({ 119 | privKeyChecked: event.target.checked 120 | }, 121 | function() { 122 | this.setParams() 123 | }) 124 | } 125 | 126 | handlePubKeyCheck(event) { 127 | this.setState({ 128 | pubKeyChecked: event.target.checked 129 | }, 130 | function() { 131 | this.setParams() 132 | }) 133 | } 134 | 135 | handleAddressCheck(event) { 136 | this.setState({ 137 | addressChecked: event.target.checked 138 | }, 139 | function() { 140 | this.setParams() 141 | }) 142 | } 143 | 144 | /* Start generation of keypairs */ 145 | async generate() { 146 | this.setState({ 147 | generating: true 148 | }) 149 | 150 | var i 151 | var output = [] 152 | if (this.state.validCount) { 153 | await nano_old.init() 154 | for (i=0; i < parseInt(this.state.count); i++) { 155 | var seed = helpers.genSecureKey() 156 | seed = seed.toUpperCase() 157 | let privKey = nano_old.deriveSecretKey(seed, 0) 158 | let pubKey = nano_old.derivePublicKey(privKey) 159 | let address = nano.deriveAddress(pubKey, {useNanoPrefix: true}) 160 | 161 | // save result in array 162 | var obj = {} 163 | 164 | if (this.state.walletNoChecked) { 165 | obj.wallet = i+1 166 | } 167 | obj.seed = seed 168 | if (this.state.privKeyChecked) { 169 | obj.privKey = privKey 170 | } 171 | if (this.state.pubKeyChecked) { 172 | obj.pubKey = pubKey 173 | } 174 | if (this.state.addressChecked) { 175 | obj.address = address 176 | } 177 | 178 | output.push(obj) 179 | 180 | } 181 | this.setState({ 182 | output: JSON.stringify(output, null, 2) 183 | }) 184 | } 185 | else { 186 | if (! toast.isActive(this.inputToast)) { 187 | this.inputToast = toast("Not a valid count", helpers.getToast(helpers.toastType.ERROR_AUTO)) 188 | } 189 | } 190 | 191 | this.setState({ 192 | generating: false 193 | }) 194 | } 195 | 196 | render() { 197 | return ( 198 |
199 |

Mass generate wallets using seed index 0

200 | 201 | 202 | 203 | 204 | Pair Count 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 |
215 | 216 | 217 |
218 |
219 | 220 | 221 |
222 |
223 | 224 | 225 |
226 |
227 | 228 | 229 |
230 |
231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | JSON 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 |
248 | ) 249 | } 250 | } 251 | export default KeyGeneratorTool 252 | -------------------------------------------------------------------------------- /src/base32.js: -------------------------------------------------------------------------------- 1 | //nano-base32 from https://github.com/termhn/nano-base32/blob/d6b160aba47595180b67cc8a30096a11525e4010/index.js 2 | //blake2b from https://github.com/emilbayes/blake2b/blob/f0a7c7b550133eca5f5fc3b751ccfd2335ce736f/index.js 3 | 4 | const alphabet = '13456789abcdefghijkmnopqrstuwxyz' 5 | 6 | /** 7 | * Encode provided Uint8Array using the Nano-specific Base-32 implementeation. 8 | * @param {Uint8Array} view Input buffer formatted as a Uint8Array 9 | * @returns {string} 10 | */ 11 | export function encode(view) { 12 | if (view.constructor !== Uint8Array) { 13 | throw new Error('View must be a Uint8Array!') 14 | } 15 | const length = view.length 16 | const leftover = (length * 8) % 5 17 | const offset = leftover === 0 ? 0 : 5 - leftover 18 | 19 | let value = 0 20 | let output = '' 21 | let bits = 0 22 | 23 | for (var i = 0; i < length; i++) { 24 | value = (value << 8) | view[i] 25 | bits += 8 26 | 27 | while (bits >= 5) { 28 | output += alphabet[(value >>> (bits + offset - 5)) & 31] 29 | bits -= 5 30 | } 31 | } 32 | 33 | if (bits > 0) { 34 | output += alphabet[(value << (5 - (bits + offset))) & 31] 35 | } 36 | 37 | return output 38 | } 39 | 40 | export function readChar(char) { 41 | var idx = alphabet.indexOf(char) 42 | 43 | if (idx === -1) { 44 | throw new Error('Invalid character found: ' + char) 45 | } 46 | 47 | return idx 48 | } 49 | 50 | /** 51 | * Decodes a Nano-implementation Base32 encoded string into a Uint8Array 52 | * @param {string} input A Nano-Base32 encoded string 53 | * @returns {Uint8Array} 54 | */ 55 | export function decode(input) { 56 | if (typeof input !== 'string') { 57 | throw new Error('Input must be a string!') 58 | } 59 | var length = input.length 60 | const leftover = (length * 5) % 8 61 | const offset = leftover === 0 ? 0 : 8 - leftover 62 | 63 | var bits = 0 64 | var value = 0 65 | 66 | var index = 0 67 | var output = new Uint8Array(Math.ceil(length * 5 / 8)) 68 | 69 | for (var i = 0; i < length; i++) { 70 | value = (value << 5) | readChar(input[i]) 71 | bits += 5 72 | 73 | if (bits >= 8) { 74 | output[index++] = (value >>> (bits + offset - 8)) & 255 75 | bits -= 8 76 | } 77 | } 78 | if (bits > 0) { 79 | output[index++] = (value << (bits + offset - 8)) & 255 80 | } 81 | 82 | if (leftover !== 0) { 83 | output = output.slice(1) 84 | } 85 | return output 86 | } 87 | 88 | // 64-bit unsigned addition 89 | // Sets v[a,a+1] += v[b,b+1] 90 | // v should be a Uint32Array 91 | export function ADD64AA(v, a, b) { 92 | var o0 = v[a] + v[b] 93 | var o1 = v[a + 1] + v[b + 1] 94 | if (o0 >= 0x100000000) { 95 | o1++ 96 | } 97 | v[a] = o0 98 | v[a + 1] = o1 99 | } 100 | 101 | // 64-bit unsigned addition 102 | // Sets v[a,a+1] += b 103 | // b0 is the low 32 bits of b, b1 represents the high 32 bits 104 | export function ADD64AC(v, a, b0, b1) { 105 | var o0 = v[a] + b0 106 | if (b0 < 0) { 107 | o0 += 0x100000000 108 | } 109 | var o1 = v[a + 1] + b1 110 | if (o0 >= 0x100000000) { 111 | o1++ 112 | } 113 | v[a] = o0 114 | v[a + 1] = o1 115 | } 116 | 117 | // Little-endian byte access 118 | export function B2B_GET32(arr, i) { 119 | return (arr[i] ^ 120 | (arr[i + 1] << 8) ^ 121 | (arr[i + 2] << 16) ^ 122 | (arr[i + 3] << 24)) 123 | } 124 | 125 | // G Mixing function 126 | // The ROTRs are inlined for speed 127 | export function B2B_G(a, b, c, d, ix, iy) { 128 | var x0 = m[ix] 129 | var x1 = m[ix + 1] 130 | var y0 = m[iy] 131 | var y1 = m[iy + 1] 132 | 133 | ADD64AA(v, a, b) // v[a,a+1] += v[b,b+1] ... in JS we must store a uint64 as two uint32s 134 | ADD64AC(v, a, x0, x1) // v[a, a+1] += x ... x0 is the low 32 bits of x, x1 is the high 32 bits 135 | 136 | // v[d,d+1] = (v[d,d+1] xor v[a,a+1]) rotated to the right by 32 bits 137 | var xor0 = v[d] ^ v[a] 138 | var xor1 = v[d + 1] ^ v[a + 1] 139 | v[d] = xor1 140 | v[d + 1] = xor0 141 | 142 | ADD64AA(v, c, d) 143 | 144 | // v[b,b+1] = (v[b,b+1] xor v[c,c+1]) rotated right by 24 bits 145 | xor0 = v[b] ^ v[c] 146 | xor1 = v[b + 1] ^ v[c + 1] 147 | v[b] = (xor0 >>> 24) ^ (xor1 << 8) 148 | v[b + 1] = (xor1 >>> 24) ^ (xor0 << 8) 149 | 150 | ADD64AA(v, a, b) 151 | ADD64AC(v, a, y0, y1) 152 | 153 | // v[d,d+1] = (v[d,d+1] xor v[a,a+1]) rotated right by 16 bits 154 | xor0 = v[d] ^ v[a] 155 | xor1 = v[d + 1] ^ v[a + 1] 156 | v[d] = (xor0 >>> 16) ^ (xor1 << 16) 157 | v[d + 1] = (xor1 >>> 16) ^ (xor0 << 16) 158 | 159 | ADD64AA(v, c, d) 160 | 161 | // v[b,b+1] = (v[b,b+1] xor v[c,c+1]) rotated right by 63 bits 162 | xor0 = v[b] ^ v[c] 163 | xor1 = v[b + 1] ^ v[c + 1] 164 | v[b] = (xor1 >>> 31) ^ (xor0 << 1) 165 | v[b + 1] = (xor0 >>> 31) ^ (xor1 << 1) 166 | } 167 | 168 | // Initialization Vector 169 | var BLAKE2B_IV32 = new Uint32Array([ 170 | 0xF3BCC908, 0x6A09E667, 0x84CAA73B, 0xBB67AE85, 171 | 0xFE94F82B, 0x3C6EF372, 0x5F1D36F1, 0xA54FF53A, 172 | 0xADE682D1, 0x510E527F, 0x2B3E6C1F, 0x9B05688C, 173 | 0xFB41BD6B, 0x1F83D9AB, 0x137E2179, 0x5BE0CD19 174 | ]) 175 | 176 | var SIGMA8 = [ 177 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 178 | 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3, 179 | 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4, 180 | 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8, 181 | 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13, 182 | 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9, 183 | 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11, 184 | 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10, 185 | 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5, 186 | 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0, 187 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 188 | 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 189 | ] 190 | 191 | // These are offsets into a uint64 buffer. 192 | // Multiply them all by 2 to make them offsets into a uint32 buffer, 193 | // because this is Javascript and we don't have uint64s 194 | var SIGMA82 = new Uint8Array(SIGMA8.map(function (x) { return x * 2 })) 195 | 196 | // Compression function. 'last' flag indicates last block. 197 | // Note we're representing 16 uint64s as 32 uint32s 198 | var v = new Uint32Array(32) 199 | var m = new Uint32Array(32) 200 | export function blake2bCompress(ctx, last) { 201 | var i = 0 202 | 203 | // init work variables 204 | for (i = 0; i < 16; i++) { 205 | v[i] = ctx.h[i] 206 | v[i + 16] = BLAKE2B_IV32[i] 207 | } 208 | 209 | // low 64 bits of offset 210 | v[24] = v[24] ^ ctx.t 211 | v[25] = v[25] ^ (ctx.t / 0x100000000) 212 | // high 64 bits not supported, offset may not be higher than 2**53-1 213 | 214 | // last block flag set ? 215 | if (last) { 216 | v[28] = ~v[28] 217 | v[29] = ~v[29] 218 | } 219 | 220 | // get little-endian words 221 | for (i = 0; i < 32; i++) { 222 | m[i] = B2B_GET32(ctx.b, 4 * i) 223 | } 224 | 225 | // twelve rounds of mixing 226 | for (i = 0; i < 12; i++) { 227 | B2B_G(0, 8, 16, 24, SIGMA82[i * 16 + 0], SIGMA82[i * 16 + 1]) 228 | B2B_G(2, 10, 18, 26, SIGMA82[i * 16 + 2], SIGMA82[i * 16 + 3]) 229 | B2B_G(4, 12, 20, 28, SIGMA82[i * 16 + 4], SIGMA82[i * 16 + 5]) 230 | B2B_G(6, 14, 22, 30, SIGMA82[i * 16 + 6], SIGMA82[i * 16 + 7]) 231 | B2B_G(0, 10, 20, 30, SIGMA82[i * 16 + 8], SIGMA82[i * 16 + 9]) 232 | B2B_G(2, 12, 22, 24, SIGMA82[i * 16 + 10], SIGMA82[i * 16 + 11]) 233 | B2B_G(4, 14, 16, 26, SIGMA82[i * 16 + 12], SIGMA82[i * 16 + 13]) 234 | B2B_G(6, 8, 18, 28, SIGMA82[i * 16 + 14], SIGMA82[i * 16 + 15]) 235 | } 236 | 237 | for (i = 0; i < 16; i++) { 238 | ctx.h[i] = ctx.h[i] ^ v[i] ^ v[i + 16] 239 | } 240 | } 241 | 242 | // reusable parameter_block 243 | var parameter_block = new Uint8Array([ 244 | 0, 0, 0, 0, // 0: outlen, keylen, fanout, depth 245 | 0, 0, 0, 0, // 4: leaf length, sequential mode 246 | 0, 0, 0, 0, // 8: node offset 247 | 0, 0, 0, 0, // 12: node offset 248 | 0, 0, 0, 0, // 16: node depth, inner length, rfu 249 | 0, 0, 0, 0, // 20: rfu 250 | 0, 0, 0, 0, // 24: rfu 251 | 0, 0, 0, 0, // 28: rfu 252 | 0, 0, 0, 0, // 32: salt 253 | 0, 0, 0, 0, // 36: salt 254 | 0, 0, 0, 0, // 40: salt 255 | 0, 0, 0, 0, // 44: salt 256 | 0, 0, 0, 0, // 48: personal 257 | 0, 0, 0, 0, // 52: personal 258 | 0, 0, 0, 0, // 56: personal 259 | 0, 0, 0, 0 // 60: personal 260 | ]) 261 | 262 | // Creates a BLAKE2b hashing context 263 | // Requires an output length between 1 and 64 bytes 264 | // Takes an optional Uint8Array key 265 | export function Blake2b(outlen, key, salt, personal) { 266 | // zero out parameter_block before usage 267 | parameter_block.fill(0) 268 | // state, 'param block' 269 | 270 | this.b = new Uint8Array(128) 271 | this.h = new Uint32Array(16) 272 | this.t = 0 // input count 273 | this.c = 0 // pointer within buffer 274 | this.outlen = outlen // output length in bytes 275 | 276 | parameter_block[0] = outlen 277 | if (key) parameter_block[1] = key.length 278 | parameter_block[2] = 1 // fanout 279 | parameter_block[3] = 1 // depth 280 | 281 | if (salt) parameter_block.set(salt, 32) 282 | if (personal) parameter_block.set(personal, 48) 283 | 284 | // initialize hash state 285 | for (var i = 0; i < 16; i++) { 286 | this.h[i] = BLAKE2B_IV32[i] ^ B2B_GET32(parameter_block, i * 4) 287 | } 288 | 289 | // key the hash, if applicable 290 | if (key) { 291 | blake2bUpdate(this, key) 292 | // at the end 293 | this.c = 128 294 | } 295 | } 296 | 297 | Blake2b.prototype.update = function (input) { 298 | this.assert(input instanceof Uint8Array, 'input must be Uint8Array or Buffer') 299 | blake2bUpdate(this, input) 300 | return this 301 | } 302 | 303 | Blake2b.prototype.digest = function (out) { 304 | var buf = (!out || out === 'binary' || out === 'hex') ? new Uint8Array(this.outlen) : out 305 | this.assert(buf instanceof Uint8Array, 'out must be "binary", "hex", Uint8Array, or Buffer') 306 | this.assert(buf.length >= this.outlen, 'out must have at least outlen bytes of space') 307 | blake2bFinal(this, buf) 308 | if (out === 'hex') return this.hexSlice(buf) 309 | return buf 310 | } 311 | 312 | Blake2b.prototype.final = Blake2b.prototype.digest 313 | 314 | Blake2b.ready = function (cb) { 315 | this.b2wasm.ready(function () { 316 | cb() // ignore the error 317 | }) 318 | } 319 | 320 | // Updates a BLAKE2b streaming hash 321 | // Requires hash context and Uint8Array (byte array) 322 | export function blake2bUpdate(ctx, input) { 323 | for (var i = 0; i < input.length; i++) { 324 | if (ctx.c === 128) { // buffer full ? 325 | ctx.t += ctx.c // add counters 326 | blake2bCompress(ctx, false) // compress (not last) 327 | ctx.c = 0 // counter to zero 328 | } 329 | ctx.b[ctx.c++] = input[i] 330 | } 331 | } 332 | 333 | // Completes a BLAKE2b streaming hash 334 | // Returns a Uint8Array containing the message digest 335 | export function blake2bFinal(ctx, out) { 336 | ctx.t += ctx.c // mark last block offset 337 | 338 | while (ctx.c < 128) { // fill up with zeros 339 | ctx.b[ctx.c++] = 0 340 | } 341 | blake2bCompress(ctx, true) // final block flag = 1 342 | 343 | for (var i = 0; i < ctx.outlen; i++) { 344 | out[i] = ctx.h[i >> 2] >> (8 * (i & 3)) 345 | } 346 | return out 347 | } -------------------------------------------------------------------------------- /src/tools/DifficultyTool.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { InputGroup, FormControl, Button} from 'react-bootstrap' 3 | import * as helpers from '../helpers' 4 | import removeTrailingZeros from 'remove-trailing-zeros' 5 | import bigDec from 'bigdecimal' 6 | import {toast } from 'react-toastify' 7 | const toolParam = 'difficulty' 8 | 9 | class DifficultyTool extends Component { 10 | constructor(props) { 11 | super(props) 12 | 13 | this.inputToast = null //disallow duplicates 14 | 15 | this.state = { 16 | baseDifficulty: 'fffffff800000000', 17 | newDifficulty: '', 18 | multiplier: '', 19 | validBase: true, 20 | validNew: false, 21 | validMultiplier: false, 22 | } 23 | 24 | this.handleBaseChange = this.handleBaseChange.bind(this) 25 | this.handleNewChange = this.handleNewChange.bind(this) 26 | this.handleMultiplierChange = this.handleMultiplierChange.bind(this) 27 | this.clearText = this.clearText.bind(this) 28 | this.clearAll = this.clearAll.bind(this) 29 | this.setParams = this.setParams.bind(this) 30 | this.invertMultiplier = this.invertMultiplier.bind(this) 31 | } 32 | 33 | componentDidMount = () => { 34 | // Read URL params from parent and construct new quick path 35 | var baseDiff = this.props.state.baseDiff 36 | var newDiff = this.props.state.newDiff 37 | var multiplier = this.props.state.multiplier 38 | 39 | if (baseDiff && newDiff) { 40 | this.setState({ 41 | baseDifficulty: baseDiff, 42 | newDifficulty: newDiff, 43 | validBase: true, 44 | validNew: true, 45 | }, 46 | function() { 47 | this.newChange(newDiff) 48 | }) 49 | } 50 | else if (baseDiff && multiplier) { 51 | this.setState({ 52 | baseDifficulty: baseDiff, 53 | multiplier: multiplier, 54 | validBase: true, 55 | validMultiplier: true, 56 | }, 57 | function() { 58 | this.multiplierChange(multiplier) 59 | }) 60 | } 61 | else if (baseDiff) { 62 | this.setState({ 63 | baseDifficulty: baseDiff, 64 | validBase: true, 65 | }) 66 | } 67 | if (!baseDiff && !newDiff && !multiplier) { 68 | this.setParams() 69 | } 70 | } 71 | 72 | // Defines the url params 73 | setParams(type) { 74 | switch (type) { 75 | case 'baseDiff': 76 | helpers.setURLParams('?tool='+toolParam + '&basediff='+this.state.baseDifficulty) 77 | break 78 | 79 | case 'newDiff': 80 | helpers.setURLParams('?tool='+toolParam + '&basediff='+this.state.baseDifficulty + '&newdiff='+this.state.newDifficulty) 81 | break 82 | 83 | case 'multiplier': 84 | helpers.setURLParams('?tool='+toolParam + '&basediff='+this.state.baseDifficulty + '&multiplier='+this.state.multiplier) 85 | break 86 | 87 | default: 88 | helpers.setURLParams('?tool='+toolParam) 89 | break 90 | } 91 | } 92 | 93 | //Clear text from input field 94 | clearText(event) { 95 | switch(event.target.value) { 96 | case 'baseDiff': 97 | this.setState({ 98 | baseDifficulty: '', 99 | validBase: false, 100 | }) 101 | break 102 | case 'newDiff': 103 | this.setState({ 104 | newDifficulty: '', 105 | validNew: false, 106 | }) 107 | break 108 | case 'multiplier': 109 | this.setState({ 110 | multiplier: '', 111 | validMultiplier: false, 112 | }) 113 | break 114 | default: 115 | break 116 | } 117 | } 118 | 119 | clearAll() { 120 | this.setState({ 121 | baseDifficulty: '', 122 | validBase: false, 123 | newDifficulty: '', 124 | validNew: false, 125 | multiplier: '', 126 | validMultiplier: false, 127 | }, 128 | function() { 129 | this.setParams() 130 | }) 131 | } 132 | 133 | invertMultiplier() { 134 | if (this.state.multiplier !== '0') { 135 | let mode = bigDec.RoundingMode.HALF_DOWN() 136 | let newMulti = removeTrailingZeros(bigDec.BigDecimal(1).divide(bigDec.BigDecimal(this.state.multiplier),32,mode).toPlainString()) 137 | this.setState({ 138 | multiplier: newMulti, 139 | }, 140 | function() { 141 | this.multiplierChange(newMulti) 142 | }) 143 | } 144 | } 145 | 146 | // Validate if a 16 char hex string 147 | checkDifficulty(diff) { 148 | return (helpers.isHex(diff) && diff.length === 16) 149 | } 150 | 151 | handleBaseChange(event) { 152 | this.baseChange(event.target.value) 153 | } 154 | baseChange(value) { 155 | let val = value.toLowerCase() 156 | if (!this.checkDifficulty(val)) { 157 | if (val !== '') { 158 | if (!toast.isActive(this.inputToast)) { 159 | this.inputToast = toast("Not a valid difficulty hex string", helpers.getToast(helpers.toastType.ERROR_AUTO)) 160 | } 161 | } 162 | this.setState({ 163 | baseDifficulty: val, 164 | validBase: false, 165 | validMultiplier: false, 166 | multiplier: '', 167 | }, 168 | function() { 169 | this.setParams('baseDiff') 170 | }) 171 | return 172 | } 173 | this.inputToast = toast("Valid threshold entered", helpers.getToast(helpers.toastType.SUCCESS_AUTO)) 174 | this.setState({ 175 | baseDifficulty: val, 176 | multiplier: this.state.validNew ? removeTrailingZeros(helpers.multiplier_from_difficulty(this.state.newDifficulty, val).toString()): '', 177 | validBase: true, 178 | validMultiplier: true, 179 | }, 180 | function() { 181 | this.setParams('baseDiff') 182 | }) 183 | } 184 | 185 | handleNewChange(event) { 186 | this.newChange(event.target.value) 187 | } 188 | newChange(value) { 189 | let val = value.toLowerCase() 190 | if (!this.checkDifficulty(val)) { 191 | if (val !== '') { 192 | if (!toast.isActive(this.inputToast)) { 193 | this.inputToast = toast("Not a valid difficulty hex string", helpers.getToast(helpers.toastType.ERROR_AUTO)) 194 | } 195 | } 196 | this.setState({ 197 | newDifficulty: val, 198 | multiplier: '', 199 | validNew: false, 200 | validMultiplier: false, 201 | }, 202 | function() { 203 | this.setParams('newDiff') 204 | }) 205 | return 206 | } 207 | this.inputToast = toast("Valid threshold entered", helpers.getToast(helpers.toastType.SUCCESS_AUTO)) 208 | this.setState({ 209 | newDifficulty: val, 210 | validNew: true, 211 | validMultiplier: true, 212 | multiplier: this.state.validBase ? removeTrailingZeros(helpers.multiplier_from_difficulty(val,this.state.baseDifficulty).toString()): '', 213 | }, 214 | function() { 215 | this.setParams('newDiff') 216 | }) 217 | } 218 | 219 | handleMultiplierChange(event) { 220 | this.multiplierChange(event.target.value) 221 | } 222 | multiplierChange(val) { 223 | if (!helpers.isValidDiffMultiplier(val)) { 224 | if (val !== '' && val.slice(-1) !== '.' && val.slice(-1) !== '0') { 225 | if (!toast.isActive(this.inputToast)) { 226 | this.inputToast = toast("Not a valid positive natural or decimal number", helpers.getToast(helpers.toastType.ERROR_AUTO)) 227 | } 228 | } 229 | this.setState({ 230 | newDifficulty: '', 231 | multiplier: val, 232 | validMultiplier: false, 233 | validNew: false, 234 | }, 235 | function() { 236 | this.setParams('multiplier') 237 | }) 238 | return 239 | } 240 | 241 | let newDiff = this.state.validBase ? helpers.difficulty_from_multiplier(val,this.state.baseDifficulty): '' 242 | if (newDiff.charAt(0) === '-' || newDiff.length > 16) { 243 | newDiff = '' 244 | if (!toast.isActive(this.inputToast)) { 245 | this.inputToast = toast("Invalid multiplier", helpers.getToast(helpers.toastType.ERROR_AUTO)) 246 | } 247 | } 248 | 249 | this.setState({ 250 | multiplier: val, 251 | validMultiplier: true, 252 | validNew: true, 253 | newDifficulty: newDiff, 254 | }, 255 | function() { 256 | this.setParams('multiplier') 257 | }) 258 | } 259 | 260 | render() { 261 | return ( 262 |
263 |

Convert PoW difficulty threshold OR multiplier from the Base

264 |
    265 |
  • The default base threshold is fffffff800000000 since node v21
  • 266 |
  • Send/change blocks are using 1x multiplier and receive blocks 1/64x or 0.0625
  • 267 |
  • More info about how this is calculated HERE
  • 268 |
269 | 270 | 271 | 272 | 273 | Base Threshold 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | New Threshold 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | Multiplier 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 |
314 | ) 315 | } 316 | } 317 | export default DifficultyTool 318 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "NotoSans Regular"; 3 | src: 4 | url("fonts/NotoSans-Regular.woff"), /* Pretty Modern Browsers */ 5 | url("fonts/NotoSans-Regular.woff2"), /* Super Modern Browsers */ 6 | url("fonts/NotoSans-Regular.eot"), 7 | url("fonts/NotoSans-Regular.ttf") format("truetype"), /* Safari, Android, iOS */ 8 | url("fonts/NotoSans-Regular.eot?#iefix") format("embedded-opentype"); /* IE6-IE8 */ 9 | } 10 | 11 | html, body { 12 | height: 100%; 13 | margin: 0; 14 | } 15 | 16 | body { 17 | background-color: #000034; 18 | } 19 | 20 | .App { 21 | /*text-align: center;*/ 22 | color: #EEE; 23 | font-size: calc(10px + 0.5vmin); 24 | line-height: calc(10px + 1.1vmin); 25 | font-family: 'NotoSans Regular', Fallback, sans-serif; 26 | height: 100%; 27 | max-height: 100%; 28 | display: flex; 29 | flex-direction: column; 30 | } 31 | 32 | .background-noise { 33 | position:fixed; 34 | left:0; 35 | top:0; 36 | width:100%; 37 | height:100%; 38 | background:url(img/modal-bg-main.png); 39 | text-align:center; 40 | z-index: -1; 41 | } 42 | 43 | p { 44 | font-size: calc(10px + 1vmin); 45 | line-height: calc(10px + 1.4vmin); 46 | margin-bottom: 0.5rem; 47 | } 48 | 49 | a { 50 | color: #4a90e2; 51 | text-decoration: none; 52 | } 53 | 54 | a:hover, a:focus { 55 | color: #377ccc; 56 | text-decoration: underline; 57 | cursor: pointer; 58 | } 59 | 60 | .new-version-snippet { 61 | position: absolute; 62 | top: 3px; 63 | right: 10px; 64 | } 65 | 66 | .new-version-snippet p { 67 | font-size: 1em; 68 | } 69 | 70 | .link-span { 71 | color: #4a90e2; 72 | text-decoration: none; 73 | } 74 | 75 | .input-title { 76 | margin-bottom: 4px; 77 | } 78 | 79 | .section-title { 80 | margin-top: 16px; 81 | margin-bottom: 4px; 82 | } 83 | 84 | .link-span:hover, .link-span:focus { 85 | color: #377ccc; 86 | text-decoration: underline; 87 | cursor: pointer; 88 | } 89 | 90 | #input, #output-area { 91 | font-size: calc(10px + 0.3vmin); 92 | } 93 | 94 | .header { 95 | height: 80px; 96 | display: flex; 97 | flex-direction: column; 98 | align-items: center; 99 | margin: 0 auto; 100 | } 101 | 102 | 103 | .header-wrapper { 104 | width: 1050px; 105 | margin: 0 auto; 106 | padding: 0 0; 107 | } 108 | 109 | .line { 110 | height:1px; 111 | background-color: #3f3c73; 112 | } 113 | 114 | .content-wrapper { 115 | position: absolute; 116 | top: 80px; 117 | overflow: auto; 118 | margin: 10px auto 0 auto; 119 | width: 100%; 120 | padding: 0 10px; 121 | } 122 | 123 | .content { 124 | max-width: 1050px; 125 | margin: 0 auto; 126 | } 127 | 128 | .tool-dropdown { 129 | margin-top: 2vh; 130 | } 131 | 132 | .tool-dropdown .dropdown-toggle { 133 | font-size: calc(10px + 1.1vmin); 134 | width: 400px; 135 | text-align: left; 136 | } 137 | 138 | .command-dropdown { 139 | margin-top: 2vh; 140 | margin-bottom: 15px; 141 | } 142 | 143 | .command-dropdown .dropdown-toggle { 144 | font-size: calc(10px + 0.8vmin); 145 | width: 300px; 146 | text-align: left; 147 | } 148 | 149 | /* Special input width */ 150 | .index-input { 151 | width: 300px; 152 | } 153 | 154 | .count-input { 155 | width: 260px; 156 | } 157 | 158 | .QR-container { 159 | width: 148px; 160 | height: 148px; 161 | padding: 10px; 162 | background-color: #EEE; 163 | text-align: center; 164 | margin-top: 20px; 165 | margin-bottom: 10px; 166 | } 167 | 168 | .QR-img { 169 | width: 128px; 170 | height: 128px; 171 | cursor: pointer; 172 | } 173 | 174 | .QR-container-2x { 175 | width: 276px; 176 | height: 276px; 177 | padding: 10px; 178 | background-color: #EEE; 179 | margin-top: 20px; 180 | margin-bottom: 10px; 181 | text-align: center; 182 | } 183 | 184 | .QR-img-2x { 185 | width: 256px; 186 | height: 256px; 187 | cursor: pointer; 188 | } 189 | 190 | .QR-container-4x { 191 | width: 532px; 192 | height: 532px; 193 | padding: 10px; 194 | background-color: #EEE; 195 | margin-top: 20px; 196 | margin-bottom: 10px; 197 | text-align: center; 198 | } 199 | 200 | .QR-img-4x { 201 | width: 512px; 202 | height: 512px; 203 | cursor: pointer; 204 | } 205 | 206 | /* Payment tool */ 207 | .QR-container-payment { 208 | margin-top: 0 !important; 209 | } 210 | 211 | /* QR reader style */ 212 | .qr-reader-wrapper { 213 | text-align: left; 214 | } 215 | 216 | .qr-canvas { 217 | max-height: 300px; 218 | max-width: 100%; 219 | margin-bottom: 20px; 220 | border: #eee 1px solid; 221 | } 222 | 223 | .btn-primary { 224 | color: #fff; 225 | background-color: #4a90e2; 226 | border-color: #4a90e2; 227 | margin-right: 20px; 228 | } 229 | 230 | .btn-primary:focus { 231 | box-shadow: none; 232 | background-color: #4a90e2; 233 | border-color: #4a90e2; 234 | } 235 | 236 | .btn-outline-secondary:focus { 237 | box-shadow: none; 238 | } 239 | 240 | .form-control:focus { 241 | box-shadow: none; 242 | } 243 | 244 | 245 | .btn-active { 246 | background-color: #6C757D; 247 | color: #FFF; 248 | } 249 | 250 | /* Long width button */ 251 | .btn-wide { 252 | width: 240px; 253 | } 254 | 255 | /* Medium width button */ 256 | .btn-medium { 257 | width: 140px; 258 | margin-bottom: 2vh; 259 | } 260 | 261 | /* Max min button */ 262 | .max-btn { 263 | width: 48px; 264 | } 265 | 266 | /* The left thing of input boxes */ 267 | .input-group-prepend, .input-group-text { 268 | width: 140px; 269 | } 270 | 271 | /* Custom width */ 272 | .narrow-prepend { 273 | width: 70px; 274 | } 275 | 276 | .narrow-prepend-2 { 277 | width: 100px; 278 | } 279 | 280 | .wide-prepend, .input-group-text { 281 | width: 160px; 282 | } 283 | 284 | /* Multiple input group on one row */ 285 | .multi-group { 286 | margin-left: 20px; 287 | } 288 | 289 | /* Disabled input */ 290 | .form-control:disabled { 291 | background-color: #cacaca; 292 | } 293 | 294 | /* Striped dropdown menu */ 295 | .dropdown-menu > a:nth-child(odd) { 296 | background: #EEE; 297 | } 298 | 299 | /* Change color on dropdown menu hover */ 300 | .dropdown-item:hover { 301 | background: #4a90e2 !important; 302 | } 303 | 304 | /* Special prepended dropdown button to resemble normal prepend text style */ 305 | .dropdown-prepend .dropdown-toggle { 306 | color: #495057; 307 | text-align: center; 308 | white-space: nowrap; 309 | background-color: #e9ecef; 310 | border: 1px solid #ced4da; 311 | display: -webkit-box; 312 | width: 140px; 313 | height: 31px; 314 | border-top-right-radius: 0; 315 | border-bottom-right-radius: 0; 316 | padding: 0.25rem 0.5rem; 317 | font-size: 0.875rem; 318 | line-height: 1.5; 319 | } 320 | 321 | .dropdown-prepend-narrow .dropdown-toggle { 322 | width: 70px; 323 | } 324 | 325 | .index-checkbox { 326 | margin-bottom: 5px; 327 | } 328 | 329 | /* Using radio buttons as title */ 330 | .header-radio { 331 | font-size: calc(10px + 1vmin); 332 | margin-top: 4px; 333 | } 334 | 335 | /* RADIO BUTTONS */ 336 | .gpu-load-title, .derivation-title { 337 | width: 100px; 338 | } 339 | 340 | /* AUDIO MESSENGER */ 341 | #drop_zone { 342 | border: 2px dashed #bbb; 343 | padding: 5px; 344 | margin-bottom: 14px; 345 | color: #bbb; 346 | } 347 | 348 | /* VANITY TOOLS */ 349 | .vanity-stats { 350 | font-size: calc(10px + 0.4vmin); 351 | margin-bottom: 10px; 352 | } 353 | 354 | .vanity-stats td { 355 | padding-right: 20px; 356 | } 357 | 358 | .vanity-number { 359 | color: #4a90e2; 360 | font-weight: bold; 361 | } 362 | 363 | /* INSPECTOR TOOL */ 364 | .command-help { 365 | margin-bottom: 15px; 366 | } 367 | 368 | /* MUSTISIG */ 369 | #output-edit { 370 | font-size: 10px; 371 | height: 32px; 372 | } 373 | 374 | #input2-area { 375 | font-size: 10px; 376 | white-space: pre-wrap; 377 | } 378 | 379 | .number-input { 380 | width: 250px; 381 | } 382 | 383 | /* Visible content to activate if no javascript */ 384 | .noscript { 385 | display: none; 386 | z-index: 10; 387 | width: 100%; 388 | padding: 0 !important; 389 | margin: 2em 0 0.5em 0 !important; 390 | color: #e36767; 391 | } 392 | 393 | .hidden { 394 | display: none; 395 | } 396 | 397 | .footer { 398 | text-align: center; 399 | position: fixed; 400 | bottom: 0; 401 | left: 0; 402 | right: 0; 403 | } 404 | 405 | .footer .line { 406 | margin-bottom: 0; 407 | } 408 | 409 | .footer .form-control { 410 | border-radius: 0; 411 | font-size: calc(10px + 0.3vmin); 412 | } 413 | 414 | .footer .input-group { 415 | margin-bottom: 5px; 416 | } 417 | 418 | .footer-text { 419 | margin-bottom: 5px; 420 | } 421 | 422 | /* Collapsible div */ 423 | .collapse-content { 424 | padding: 0 0; 425 | max-height: 0; 426 | overflow: hidden; 427 | transition: max-height 0.2s ease-out; 428 | margin: 0 0 5px 0; 429 | } 430 | 431 | @media screen and (max-width: 992px) { 432 | h1{font-size: 2rem;} 433 | .App { 434 | font-size: calc(10px + 0.5vmin); 435 | line-height: calc(10px + 1.1vmin); 436 | } 437 | } 438 | 439 | @media screen and (max-width: 812px) { 440 | .header-wrapper { 441 | padding: 0 10px; 442 | } 443 | } 444 | 445 | @media screen and (max-width: 600px) { 446 | h1{font-size: 1.8rem;} 447 | .App { 448 | font-size: calc(10px + 0.4vmin); 449 | line-height: calc(10px + 1.0vmin); 450 | } 451 | .header-wrapper { 452 | width: 100%; 453 | margin: 0 auto; 454 | padding: 0 0; 455 | } 456 | 457 | .dropdown-toggle.btn-primary { 458 | width: 100%; 459 | } 460 | } 461 | 462 | /* Confirm Alert */ 463 | .react-confirm-alert-overlay { 464 | background: rgba(0, 0, 0, 0.6) !important; 465 | opacity: 0 !important; 466 | } 467 | 468 | .react-confirm-alert { 469 | max-width: 90% !important; 470 | } 471 | 472 | .react-confirm-alert-body { 473 | font-family: inherit !important; 474 | width: 100% !important; 475 | padding: 30px !important; 476 | text-align: left !important; 477 | background: #fff !important; 478 | border-radius: 10px !important; 479 | box-shadow: 0 20px 75px rgba(0, 0, 0, 0.13) !important; 480 | color: #666 !important; 481 | overflow-wrap: break-word !important; 482 | } 483 | 484 | .react-confirm-alert-body h1 { 485 | font-size: 2.0rem; 486 | } 487 | 488 | /******************************************************************************************************************************************** 489 | Modal content 490 | ********************************************************************************************************************************************/ 491 | .public_link_copy { 492 | padding:5px; 493 | width:100%; 494 | margin:10px 0 !important; 495 | } 496 | 497 | .public_link_modal { 498 | font-family: 'NotoSans Regular'; 499 | } 500 | 501 | .public_link_modal .copied { 502 | display:none; 503 | padding:5px; 504 | margin:6px 0; 505 | color:#849F4F; 506 | background:#ECF5DA; 507 | border:1px solid #B1C786; 508 | } 509 | 510 | .public_link_modal .copied_not { 511 | display:none; 512 | padding:5px; 513 | margin:6px 0; 514 | color:#B94A48; 515 | background:#F2DEDE; 516 | border:1px solid #EED3D7; 517 | } 518 | 519 | .donation-qr { 520 | width: 150px; 521 | margin: 0; 522 | } 523 | 524 | .form-group { 525 | margin-bottom: 10px !important; 526 | } 527 | 528 | /* ---------- */ 529 | .modal_overlay { 530 | display:none; 531 | position:fixed; 532 | left:0; 533 | top:0; 534 | width:100%; 535 | height:100%; 536 | background:url(img/modal-bg.png); 537 | text-align:center; 538 | z-index:2000; 539 | opacity:.9; 540 | } 541 | 542 | .modal_psend { 543 | display:none; 544 | position: fixed; 545 | top: 50%; 546 | left: 50%; 547 | float: left; 548 | z-index:5000; 549 | 550 | width:400px; 551 | height:360px; 552 | margin:-170px 0 0 -200px; 553 | background:white; 554 | color:black; 555 | 556 | border-radius: 7px; 557 | box-shadow: 0 0 23px, rgba(0, 0, 0, .3); 558 | } 559 | 560 | .modal_content { 561 | margin:15px; 562 | } 563 | 564 | .loading-img { 565 | text-align:center; 566 | } 567 | 568 | .modal_content p.loading-img { 569 | margin:40px auto; 570 | text-align:center; 571 | } 572 | 573 | .modal_content iframe { 574 | display:none; 575 | } 576 | 577 | .modal_title { 578 | text-align:left; 579 | position:relative; 580 | padding:10px; 581 | background:#eee; 582 | background:-moz-linear-gradient(top , #f5f5f5, #eee)!important; 583 | background:-webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#eee))!important; 584 | /*text-shadow:0 1px 1px #eee;*/ 585 | color:#888; 586 | 587 | -webkit-border-top-left-radius: 7px; 588 | -webkit-border-top-right-radius: 7px; 589 | -moz-border-radius-topleft: 7px; 590 | -moz-border-radius-topright: 7px; 591 | border-top-left-radius: 7px; 592 | border-top-right-radius: 7px; 593 | 594 | } 595 | 596 | .modal_title a { 597 | position:absolute; 598 | display:inline-block; 599 | top:-2px; 600 | right:10px; 601 | } 602 | 603 | .modal_close { 604 | color:#888; 605 | font-size:2em; 606 | } 607 | -------------------------------------------------------------------------------- /src/modules/nano-webgl-pow.js: -------------------------------------------------------------------------------- 1 | // nano-webgl-pow 2 | // Nano Currency Proof of Work Value generation using WebGL2 3 | // Author: numtel 4 | // License: MIT 5 | 6 | // window.NanoWebglPow(hashHex, threshold, width, height, callback, progressCallback); 7 | // @param hashHex String Previous Block Hash as Hex String 8 | // @param threshold String Optional difficulty threshold (default=0xFFFFFFF8 since v21) 9 | // Pass undefined to use default 10 | // @param width Int Gpu load 1, must be power of 2 (default=512) 11 | // @param height Int Gpu load 2, must be power of 2 (default=512) 12 | // @param callback Function Called when work value found 13 | // Receives single string argument, work value as hex 14 | // @param progressCallback Function Optional 15 | // Receives single argument: n, number of frames so far 16 | // Return true to abort 17 | 18 | const defaultThreshold = '0xFFFFFFF8' 19 | const defaultSize = 512 20 | 21 | function array_hex(arr, index, length) { 22 | let out=''; 23 | for(let i=length - 1;i>-1;i--) { 24 | out+=(arr[i] > 15 ? '' : '0') + arr[i].toString(16); 25 | } 26 | return out; 27 | } 28 | 29 | function hex_reverse(hex) { 30 | let out=''; 31 | for(let i=hex.length;i>0;i-=2) { 32 | out+=hex.slice(i-2,i); 33 | } 34 | return out; 35 | } 36 | 37 | export function calculate(hashHex, threshold = defaultThreshold, width = defaultSize, height = defaultSize, callback, progressCallback) { 38 | const canvas = document.createElement('canvas'); 39 | 40 | canvas.width = width; 41 | canvas.height = height; 42 | 43 | const gl = canvas.getContext('webgl2'); 44 | 45 | if(!gl) 46 | throw new Error('webgl2_required'); 47 | 48 | if(!/^[A-F-a-f0-9]{64}$/.test(hashHex)) 49 | throw new Error('invalid_hash'); 50 | 51 | gl.clearColor(0, 0, 0, 1); 52 | 53 | const reverseHex = hex_reverse(hashHex); 54 | 55 | // Vertext Shader 56 | const vsSource = `#version 300 es 57 | precision highp float; 58 | layout (location=0) in vec4 position; 59 | layout (location=1) in vec2 uv; 60 | 61 | out vec2 uv_pos; 62 | 63 | void main() { 64 | uv_pos = uv; 65 | gl_Position = position; 66 | }`; 67 | 68 | // Fragment shader 69 | const fsSource = `#version 300 es 70 | precision highp float; 71 | precision highp int; 72 | 73 | in vec2 uv_pos; 74 | out vec4 fragColor; 75 | 76 | // Random work values 77 | // First 2 bytes will be overwritten by texture pixel position 78 | // Second 2 bytes will be modified if the canvas size is greater than 256x256 79 | uniform uvec4 u_work0; 80 | // Last 4 bytes remain as generated externally 81 | uniform uvec4 u_work1; 82 | 83 | // Defined separately from uint v[32] below as the original value is required 84 | // to calculate the second uint32 of the digest for threshold comparison 85 | #define BLAKE2B_IV32_1 0x6A09E667u 86 | 87 | // Both buffers represent 16 uint64s as 32 uint32s 88 | // because that's what GLSL offers, just like Javascript 89 | 90 | // Compression buffer, intialized to 2 instances of the initialization vector 91 | // The following values have been modified from the BLAKE2B_IV: 92 | // OUTLEN is constant 8 bytes 93 | // v[0] ^= 0x01010000u ^ uint(OUTLEN); 94 | // INLEN is constant 40 bytes: work value (8) + block hash (32) 95 | // v[24] ^= uint(INLEN); 96 | // It's always the "last" compression at this INLEN 97 | // v[28] = ~v[28]; 98 | // v[29] = ~v[29]; 99 | uint v[32] = uint[32]( 100 | 0xF2BDC900u, 0x6A09E667u, 0x84CAA73Bu, 0xBB67AE85u, 101 | 0xFE94F82Bu, 0x3C6EF372u, 0x5F1D36F1u, 0xA54FF53Au, 102 | 0xADE682D1u, 0x510E527Fu, 0x2B3E6C1Fu, 0x9B05688Cu, 103 | 0xFB41BD6Bu, 0x1F83D9ABu, 0x137E2179u, 0x5BE0CD19u, 104 | 0xF3BCC908u, 0x6A09E667u, 0x84CAA73Bu, 0xBB67AE85u, 105 | 0xFE94F82Bu, 0x3C6EF372u, 0x5F1D36F1u, 0xA54FF53Au, 106 | 0xADE682F9u, 0x510E527Fu, 0x2B3E6C1Fu, 0x9B05688Cu, 107 | 0x04BE4294u, 0xE07C2654u, 0x137E2179u, 0x5BE0CD19u 108 | ); 109 | // Input data buffer 110 | uint m[32]; 111 | 112 | // These are offsets into the input data buffer for each mixing step. 113 | // They are multiplied by 2 from the original SIGMA values in 114 | // the C reference implementation, which refered to uint64s. 115 | const int SIGMA82[192] = int[192]( 116 | 0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,28,20,8,16,18,30,26,12,2,24, 117 | 0,4,22,14,10,6,22,16,24,0,10,4,30,26,20,28,6,12,14,2,18,8,14,18,6,2,26, 118 | 24,22,28,4,12,10,20,8,0,30,16,18,0,10,14,4,8,20,30,28,2,22,24,12,16,6, 119 | 26,4,24,12,20,0,22,16,6,8,26,14,10,30,28,2,18,24,10,2,30,28,26,8,20,0, 120 | 14,12,6,18,4,16,22,26,22,14,28,24,2,6,18,10,0,30,8,16,12,4,20,12,30,28, 121 | 18,22,6,0,16,24,4,26,14,2,8,20,10,20,4,16,8,14,12,2,10,30,22,18,28,6,24, 122 | 26,0,0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,28,20,8,16,18,30,26,12, 123 | 2,24,0,4,22,14,10,6 124 | ); 125 | 126 | // 64-bit unsigned addition within the compression buffer 127 | // Sets v[a,a+1] += b 128 | // b0 is the low 32 bits of b, b1 represents the high 32 bits 129 | void add_uint64 (int a, uint b0, uint b1) { 130 | uint o0 = v[a] + b0; 131 | uint o1 = v[a + 1] + b1; 132 | if (v[a] > 0xFFFFFFFFu - b0) { // did low 32 bits overflow? 133 | o1++; 134 | } 135 | v[a] = o0; 136 | v[a + 1] = o1; 137 | } 138 | // Sets v[a,a+1] += v[b,b+1] 139 | void add_uint64 (int a, int b) { 140 | add_uint64(a, v[b], v[b+1]); 141 | } 142 | 143 | // G Mixing function 144 | void B2B_G (int a, int b, int c, int d, int ix, int iy) { 145 | add_uint64(a, b); 146 | add_uint64(a, m[ix], m[ix + 1]); 147 | 148 | // v[d,d+1] = (v[d,d+1] xor v[a,a+1]) rotated to the right by 32 bits 149 | uint xor0 = v[d] ^ v[a]; 150 | uint xor1 = v[d + 1] ^ v[a + 1]; 151 | v[d] = xor1; 152 | v[d + 1] = xor0; 153 | 154 | add_uint64(c, d); 155 | 156 | // v[b,b+1] = (v[b,b+1] xor v[c,c+1]) rotated right by 24 bits 157 | xor0 = v[b] ^ v[c]; 158 | xor1 = v[b + 1] ^ v[c + 1]; 159 | v[b] = (xor0 >> 24) ^ (xor1 << 8); 160 | v[b + 1] = (xor1 >> 24) ^ (xor0 << 8); 161 | 162 | add_uint64(a, b); 163 | add_uint64(a, m[iy], m[iy + 1]); 164 | 165 | // v[d,d+1] = (v[d,d+1] xor v[a,a+1]) rotated right by 16 bits 166 | xor0 = v[d] ^ v[a]; 167 | xor1 = v[d + 1] ^ v[a + 1]; 168 | v[d] = (xor0 >> 16) ^ (xor1 << 16); 169 | v[d + 1] = (xor1 >> 16) ^ (xor0 << 16); 170 | 171 | add_uint64(c, d); 172 | 173 | // v[b,b+1] = (v[b,b+1] xor v[c,c+1]) rotated right by 63 bits 174 | xor0 = v[b] ^ v[c]; 175 | xor1 = v[b + 1] ^ v[c + 1]; 176 | v[b] = (xor1 >> 31) ^ (xor0 << 1); 177 | v[b + 1] = (xor0 >> 31) ^ (xor1 << 1); 178 | } 179 | 180 | void main() { 181 | int i; 182 | uint uv_x = uint(uv_pos.x * ${canvas.width - 1}.); 183 | uint uv_y = uint(uv_pos.y * ${canvas.height - 1}.); 184 | uint x_pos = uv_x % 256u; 185 | uint y_pos = uv_y % 256u; 186 | uint x_index = (uv_x - x_pos) / 256u; 187 | uint y_index = (uv_y - y_pos) / 256u; 188 | 189 | // First 2 work bytes are the x,y pos within the 256x256 area, the next 190 | // two bytes are modified from the random generated value, XOR'd with 191 | // the x,y area index of where this pixel is located 192 | m[0] = (x_pos ^ (y_pos << 8) ^ ((u_work0.b ^ x_index) << 16) ^ ((u_work0.a ^ y_index) << 24)); 193 | // Remaining bytes are un-modified from the random generated value 194 | m[1] = (u_work1.r ^ (u_work1.g << 8) ^ (u_work1.b << 16) ^ (u_work1.a << 24)); 195 | 196 | // Block hash 197 | m[2] = 0x${reverseHex.slice(56,64)}u; 198 | m[3] = 0x${reverseHex.slice(48,56)}u; 199 | m[4] = 0x${reverseHex.slice(40,48)}u; 200 | m[5] = 0x${reverseHex.slice(32,40)}u; 201 | m[6] = 0x${reverseHex.slice(24,32)}u; 202 | m[7] = 0x${reverseHex.slice(16,24)}u; 203 | m[8] = 0x${reverseHex.slice(8,16)}u; 204 | m[9] = 0x${reverseHex.slice(0,8)}u; 205 | 206 | // twelve rounds of mixing 207 | for(i=0;i<12;i++) { 208 | B2B_G(0, 8, 16, 24, SIGMA82[i * 16 + 0], SIGMA82[i * 16 + 1]); 209 | B2B_G(2, 10, 18, 26, SIGMA82[i * 16 + 2], SIGMA82[i * 16 + 3]); 210 | B2B_G(4, 12, 20, 28, SIGMA82[i * 16 + 4], SIGMA82[i * 16 + 5]); 211 | B2B_G(6, 14, 22, 30, SIGMA82[i * 16 + 6], SIGMA82[i * 16 + 7]); 212 | B2B_G(0, 10, 20, 30, SIGMA82[i * 16 + 8], SIGMA82[i * 16 + 9]); 213 | B2B_G(2, 12, 22, 24, SIGMA82[i * 16 + 10], SIGMA82[i * 16 + 11]); 214 | B2B_G(4, 14, 16, 26, SIGMA82[i * 16 + 12], SIGMA82[i * 16 + 13]); 215 | B2B_G(6, 8, 18, 28, SIGMA82[i * 16 + 14], SIGMA82[i * 16 + 15]); 216 | } 217 | 218 | // Threshold test, first 4 bytes not significant, 219 | // only calculate digest of the second 4 bytes 220 | if((BLAKE2B_IV32_1 ^ v[1] ^ v[17]) > ` + threshold + `u) { 221 | // Success found, return pixel data so work value can be constructed 222 | fragColor = vec4( 223 | float(x_index + 1u)/255., // +1 to distinguish from 0 (unsuccessful) pixels 224 | float(y_index + 1u)/255., // Same as previous 225 | float(x_pos)/255., // Return the 2 custom bytes used in work value 226 | float(y_pos)/255. // Second custom byte 227 | ); 228 | } 229 | }`; 230 | 231 | const vertexShader = gl.createShader(gl.VERTEX_SHADER); 232 | gl.shaderSource(vertexShader, vsSource); 233 | gl.compileShader(vertexShader); 234 | 235 | if(!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) 236 | throw gl.getShaderInfoLog(vertexShader); 237 | 238 | const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); 239 | gl.shaderSource(fragmentShader, fsSource); 240 | gl.compileShader(fragmentShader); 241 | 242 | if(!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) 243 | throw gl.getShaderInfoLog(fragmentShader); 244 | 245 | const program = gl.createProgram(); 246 | gl.attachShader(program, vertexShader); 247 | gl.attachShader(program, fragmentShader); 248 | gl.linkProgram(program); 249 | 250 | if(!gl.getProgramParameter(program, gl.LINK_STATUS)) 251 | throw gl.getProgramInfoLog(program); 252 | 253 | gl.useProgram(program); 254 | 255 | // Construct simple 2D geometry 256 | const triangleArray = gl.createVertexArray(); 257 | gl.bindVertexArray(triangleArray); 258 | 259 | // Vertex Positions, 2 triangles 260 | const positions = new Float32Array([ 261 | -1,-1,0, -1,1,0, 1,1,0, 262 | 1,-1,0, 1,1,0, -1,-1,0 263 | ]); 264 | const positionBuffer = gl.createBuffer(); 265 | gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); 266 | gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW); 267 | gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0); 268 | gl.enableVertexAttribArray(0); 269 | 270 | // Texture Positions 271 | const uvPosArray = new Float32Array([ 272 | 1,1, 1,0, 0,0, 0,1, 0,0, 1,1 273 | ]); 274 | const uvBuffer = gl.createBuffer(); 275 | gl.bindBuffer(gl.ARRAY_BUFFER, uvBuffer); 276 | gl.bufferData(gl.ARRAY_BUFFER, uvPosArray, gl.STATIC_DRAW); 277 | gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 0, 0); 278 | gl.enableVertexAttribArray(1); 279 | 280 | const work0Location = gl.getUniformLocation(program, 'u_work0'); 281 | const work1Location = gl.getUniformLocation(program, 'u_work1'); 282 | 283 | // Draw output until success or progressCallback says to stop 284 | const work0 = new Uint8Array(4); 285 | const work1 = new Uint8Array(4); 286 | let n=0; 287 | 288 | function draw() { 289 | n++; 290 | window.crypto.getRandomValues(work0); 291 | window.crypto.getRandomValues(work1); 292 | 293 | gl.uniform4uiv(work0Location, Array.from(work0)); 294 | gl.uniform4uiv(work1Location, Array.from(work1)); 295 | 296 | // Check with progressCallback every 100 frames 297 | if(n%100===0 && typeof progressCallback === 'function' && progressCallback(n)) 298 | return; 299 | 300 | gl.clear(gl.COLOR_BUFFER_BIT); 301 | gl.drawArrays(gl.TRIANGLES, 0, 6); 302 | const pixels = new Uint8Array(gl.drawingBufferWidth * gl.drawingBufferHeight * 4); 303 | gl.readPixels(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight, gl.RGBA, gl.UNSIGNED_BYTE, pixels); 304 | 305 | // Check the pixels for any success 306 | for(let i=0;i { 42 | // Read URL params from parent and construct new quick path 43 | var address = this.props.state.address 44 | var amount = this.props.state.amount 45 | var message = this.props.state.message 46 | var recipient = this.props.state.recipient 47 | 48 | if (address) { 49 | this.addressChange(address) 50 | this.setState({ 51 | isPay: true, 52 | }) 53 | } 54 | if (amount) { 55 | this.amountChange(amount) 56 | } 57 | if (recipient) { 58 | this.recipientChange(recipient) 59 | } 60 | if (message) { 61 | this.messageChange(message) 62 | } 63 | 64 | if (!address && !amount && !message && !recipient) { 65 | this.setParams() 66 | } 67 | } 68 | 69 | // Defines the url params 70 | setParams() { 71 | helpers.setURLParams('?tool='+toolParam + '&address=' + this.state.address + '&amount=' + this.state.amount + '&recipient=' + this.state.recipient + '&message=' + this.state.message) 72 | } 73 | 74 | //Clear text from input field 75 | clearText(event) { 76 | switch(event.target.value) { 77 | case 'address': 78 | this.setState({ 79 | address: '', 80 | validAddress: false 81 | }, 82 | function() { 83 | this.updateQR() 84 | this.setParams() 85 | }) 86 | break 87 | 88 | case 'amount': 89 | this.setState({ 90 | amount: '', 91 | validAmount: false 92 | }, 93 | function() { 94 | this.updateQR() 95 | this.setParams() 96 | }) 97 | break 98 | 99 | case 'recipient': 100 | this.setState({ 101 | recipient: '', 102 | }, 103 | function() { 104 | this.setParams() 105 | }) 106 | break 107 | 108 | case 'message': 109 | this.setState({ 110 | message: '', 111 | }, 112 | function() { 113 | this.updateQR() 114 | this.setParams() 115 | }) 116 | break 117 | 118 | default: 119 | break 120 | } 121 | } 122 | 123 | //Clear text from input field 124 | clearAll() { 125 | this.setState({ 126 | address: '', 127 | amount: '', 128 | recipient: '', 129 | message: '', 130 | isPay: false, 131 | validAddress: false, 132 | validAmount: false, 133 | qrContent: '', 134 | }, 135 | function() { 136 | this.updateQR() 137 | this.setParams() 138 | }) 139 | } 140 | 141 | // Loop qr state 1x, 2x, 4x 142 | double() { 143 | var state = this.state.qrState 144 | state = state + 1 145 | if (state >= helpers.qrClassesContainer.length) { 146 | state = 0 147 | } 148 | this.setState({ 149 | qrState: state 150 | }) 151 | } 152 | 153 | handleAddressChange(event) { 154 | this.addressChange(event.target.value) 155 | } 156 | 157 | addressChange(address) { 158 | if (!nano.checkAddress(address)) { 159 | if (address !== '') { 160 | if (! toast.isActive(this.inputToast)) { 161 | this.inputToast = toast("Invalid Nano address", helpers.getToast(helpers.toastType.ERROR_AUTO)) 162 | } 163 | } 164 | this.setState({ 165 | address: address, 166 | validAddress: false 167 | }) 168 | return 169 | } 170 | this.setState({ 171 | address: address, 172 | validAddress: true 173 | }, 174 | function() { 175 | this.updateQR() 176 | this.setParams() 177 | }) 178 | } 179 | 180 | handleAmountChange(event) { 181 | this.amountChange(event.target.value) 182 | } 183 | 184 | amountChange(amount) { 185 | let raw = helpers.MnanoToRaw(amount) 186 | // allow no amount 187 | if (!nano.checkAmount(raw) && amount !== '') { 188 | if (! toast.isActive(this.inputToast)) { 189 | this.inputToast = toast("Invalid Nano amount", helpers.getToast(helpers.toastType.ERROR_AUTO)) 190 | } 191 | this.setState({ 192 | amount: amount, 193 | validAmount: false 194 | }) 195 | return 196 | } 197 | this.setState({ 198 | amount: amount, 199 | validAmount: true, 200 | }, 201 | function() { 202 | if (this.state.validAddress) { 203 | this.updateQR() 204 | } 205 | this.setParams() 206 | }) 207 | } 208 | 209 | handleMessageChange(event) { 210 | this.messageChange(event.target.value) 211 | } 212 | 213 | messageChange(message) { 214 | this.setState({ 215 | message: message, 216 | }, 217 | function() { 218 | this.updateQR() 219 | this.setParams() 220 | }) 221 | } 222 | 223 | handleRecipientChange(event) { 224 | this.recipientChange(event.target.value) 225 | } 226 | 227 | recipientChange(recipient) { 228 | this.setState({ 229 | recipient: recipient, 230 | }, 231 | function() { 232 | this.setParams() 233 | }) 234 | } 235 | 236 | updateQR() { 237 | let address = this.state.address 238 | let amount = this.state.amount 239 | let message = this.state.message.split(' ').join('%20') 240 | var raw = '' 241 | var url = '' 242 | 243 | // allow both address and amount, or only address 244 | if (address !== '' && amount !== '') { 245 | raw = helpers.MnanoToRaw(this.state.amount) 246 | url = 'nano:' + this.state.address + '?amount=' + raw + '&message=' + message 247 | } 248 | else if (address !== ''){ 249 | url = 'nano:' + address + '&message=' + message 250 | } 251 | else { 252 | url = '#' 253 | } 254 | 255 | var qr 256 | if (url === '#') { 257 | qr = '' 258 | } 259 | else { 260 | qr = url 261 | } 262 | 263 | this.setState({ 264 | qrContent: qr 265 | }) 266 | 267 | // update wallet button 268 | $('#wallet-btn').attr("href", url) 269 | } 270 | 271 | sample() { 272 | this.setState({ 273 | address: helpers.constants.SAMPLE_PAYMENT_ADDRESS, 274 | validAddress: true, 275 | amount: '0.1', 276 | validAmount: true, 277 | message: 'Donate to KeyTools', 278 | recipient: 'KeyTools' 279 | }, 280 | function() { 281 | this.updateQR() 282 | this.setParams() 283 | }) 284 | } 285 | 286 | print() { 287 | window.print() 288 | } 289 | 290 | render() { 291 | return ( 292 |
293 |
294 |

Generate a Payment Card

295 |

Pay NANO to {this.state.address.slice(0,13) + '...' + this.state.address.slice(-8)} {this.state.recipient !== '' ? ('['+this.state.recipient+']'):''}

296 | 297 |
    298 |
  • Print QR or share the URL with anyone!
  • 299 |
300 | 301 |
    302 |
  • Scan QR with a wallet. The amount is included.
  • 303 |
  • Open in Wallet may work depending on wallet installed.
  • 304 |
305 |
306 |
307 | 308 | 309 | 310 | Address 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | Amount [NANO] 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | Optional Note 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | Optional Name 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | Open in Wallet 363 | 364 | 365 | 366 | 367 |
368 |
369 |
370 | 371 |
372 |
373 |
374 | ) 375 | } 376 | } 377 | export default PaymentTool 378 | -------------------------------------------------------------------------------- /src/tools/AddressExtractorTool.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import * as nano from 'nanocurrency' 3 | import * as nano_old from 'nanocurrency174' //must be used for high performance with derivePublicKey, including nano_old.init() 4 | import { InputGroup, FormControl, Button} from 'react-bootstrap' 5 | import * as helpers from '../helpers' 6 | import MainPage from '../mainPage' 7 | import QrImageStyle from './components/qrImageStyle' 8 | import {toast } from 'react-toastify' 9 | const toolParam = 'extract' 10 | 11 | class AddressExtractorTool extends Component { 12 | constructor(props) { 13 | super(props) 14 | 15 | this.inputToast = null 16 | 17 | this.state = { 18 | seed: '', 19 | startIndex: '0', 20 | endIndex: '10', 21 | qrContent: '', 22 | qrSize: 512, 23 | qrState: 0, //qr size 24 | activeQR: false, 25 | qrHidden: true, 26 | indexChecked: true, 27 | privKeyChecked: true, 28 | pubKeyChecked: true, 29 | addressChecked: true, 30 | validSeed: false, 31 | validStartIndex: true, 32 | validEndIndex: true, 33 | prepend: true, 34 | generating: false, 35 | output: '' 36 | } 37 | 38 | this.setMin = this.setMin.bind(this) 39 | this.setMax = this.setMax.bind(this) 40 | this.handleSeedChange = this.handleSeedChange.bind(this) 41 | this.handleStartIndexChange = this.handleStartIndexChange.bind(this) 42 | this.handleEndIndexChange = this.handleEndIndexChange.bind(this) 43 | this.handleIndexCheck = this.handleIndexCheck.bind(this) 44 | this.handlePrivKeyCheck = this.handlePrivKeyCheck.bind(this) 45 | this.handlePubKeyCheck = this.handlePubKeyCheck.bind(this) 46 | this.handleAddressCheck = this.handleAddressCheck.bind(this) 47 | this.sample = this.sample.bind(this) 48 | this.generate = this.generate.bind(this) 49 | this.updateQR = this.updateQR.bind(this) 50 | this.double = this.double.bind(this) 51 | this.clearText = this.clearText.bind(this) 52 | } 53 | 54 | componentDidMount() { 55 | // Read URL params from parent and construct new quick path 56 | this.setParams() 57 | } 58 | 59 | // Defines the url params 60 | setParams() { 61 | helpers.setURLParams('?tool='+toolParam) 62 | } 63 | 64 | // set min value for start index 65 | setMin() { 66 | this.setState({ 67 | startIndex: 0 68 | }) 69 | } 70 | 71 | // set max value for end index 72 | setMax() { 73 | this.setState({ 74 | endIndex: helpers.constants.INDEX_MAX 75 | }) 76 | } 77 | 78 | //Clear text from input field 79 | clearText(event) { 80 | switch(event.target.value) { 81 | case 'seed': 82 | this.setState({ 83 | seed: '', 84 | validSeed: false 85 | }, 86 | function() { 87 | this.updateQR() 88 | }) 89 | break 90 | default: 91 | break 92 | } 93 | } 94 | 95 | // loop qr state 1x, 2x, 4x 96 | double() { 97 | var state = this.state.qrState 98 | state = state + 1 99 | if (state >= helpers.qrClassesContainer.length) { 100 | state = 0 101 | } 102 | this.setState({ 103 | qrState: state 104 | }) 105 | } 106 | 107 | // Any QR button is pressed. Handle active button. 108 | // Any QR button is pressed 109 | handleQRChange = changeEvent => { 110 | let val = changeEvent.target.value 111 | // deselect button if clicking on the same button 112 | if (this.state.qrActive === val) { 113 | this.setState({ 114 | qrActive: '', 115 | qrHidden: true 116 | }) 117 | } 118 | else { 119 | this.setState({ 120 | qrActive: val, 121 | qrHidden: false, 122 | }, 123 | function() { 124 | this.updateQR() 125 | }) 126 | } 127 | } 128 | 129 | updateQR() { 130 | switch(this.state.qrActive) { 131 | case 'seed': 132 | this.setState({ 133 | qrContent: this.state.seed, 134 | }) 135 | break 136 | default: 137 | this.setState({ 138 | qrContent: '', 139 | qrHidden: true, 140 | }) 141 | break 142 | } 143 | } 144 | 145 | sample() { 146 | var seed = helpers.genSecureKey() 147 | seed = seed.toUpperCase() 148 | this.setState({ 149 | seed: seed, 150 | validSeed: true, 151 | validStartIndex: true, 152 | validEndIndex: true, 153 | }, 154 | function() { 155 | this.updateQR(this.state.activeQR) 156 | }) 157 | } 158 | 159 | handleSeedChange(event) { 160 | this.seedChange(event.target.value) 161 | } 162 | 163 | seedChange(seed) { 164 | if (!nano.checkSeed(seed)) { 165 | this.setState({ 166 | seed: seed, 167 | validSeed: false 168 | }, 169 | function() { 170 | this.updateQR() 171 | }) 172 | if (seed !== '') { 173 | new MainPage().notifyInvalidFormat() 174 | } 175 | return 176 | } 177 | this.setState({ 178 | seed: seed, 179 | validSeed: true, 180 | }, 181 | function() { 182 | this.updateQR() 183 | }) 184 | } 185 | 186 | handleStartIndexChange(event) { 187 | var index = event.target.value 188 | if (index > helpers.constants.INDEX_MAX) { 189 | index = helpers.constants.INDEX_MAX 190 | } 191 | this.setState({ 192 | startIndex: index 193 | }) 194 | 195 | var invalid = false 196 | if (helpers.isNumeric(index)) { 197 | index = parseInt(index) 198 | if (!nano.checkIndex(index)) { 199 | invalid = true 200 | } 201 | } 202 | else { 203 | invalid = true 204 | } 205 | if (invalid) { 206 | if (index !== '') { 207 | new MainPage().notifyInvalidFormat() 208 | } 209 | this.setState({ 210 | validStartIndex: false 211 | }) 212 | return 213 | } 214 | this.setState({ 215 | validStartIndex: true 216 | }) 217 | } 218 | 219 | handleEndIndexChange(event) { 220 | var index = event.target.value 221 | if (index > helpers.constants.INDEX_MAX) { 222 | index = helpers.constants.INDEX_MAX 223 | } 224 | this.setState({ 225 | endIndex: index 226 | }) 227 | 228 | var invalid = false 229 | if (helpers.isNumeric(index)) { 230 | index = parseInt(index) 231 | if (!nano.checkIndex(index)) { 232 | invalid = true 233 | } 234 | } 235 | else { 236 | invalid = true 237 | } 238 | if (invalid) { 239 | if (index !== '') { 240 | new MainPage().notifyInvalidFormat() 241 | } 242 | this.setState({ 243 | validEndIndex: false 244 | }) 245 | return 246 | } 247 | this.setState({ 248 | validEndIndex: true 249 | }) 250 | } 251 | 252 | handleIndexCheck(event) { 253 | this.setState({ 254 | indexChecked: event.target.checked 255 | }) 256 | } 257 | 258 | handlePrivKeyCheck(event) { 259 | this.setState({ 260 | privKeyChecked: event.target.checked 261 | }) 262 | } 263 | 264 | handlePubKeyCheck(event) { 265 | this.setState({ 266 | pubKeyChecked: event.target.checked 267 | }) 268 | } 269 | 270 | handleAddressCheck(event) { 271 | this.setState({ 272 | addressChecked: event.target.checked 273 | }) 274 | } 275 | 276 | /* Start generation of addresses */ 277 | async generate() { 278 | // check that max number is not exceeded 279 | if (parseInt(this.state.endIndex) - parseInt(this.state.startIndex) > helpers.constants.KEYS_MAX) { 280 | if (! toast.isActive(this.inputToast)) { 281 | this.inputToast = toast("The total range can't exceed " + helpers.addCommas(String(helpers.constants.KEYS_MAX)), helpers.getToast(helpers.toastType.ERROR_AUTO_LONG)) 282 | } 283 | return 284 | } 285 | if (parseInt(this.state.endIndex) < parseInt(this.state.startIndex)) { 286 | if (! toast.isActive(this.inputToast)) { 287 | this.inputToast = toast("End index can't be smaller than start index", helpers.getToast(helpers.toastType.ERROR_AUTO_LONG)) 288 | } 289 | return 290 | } 291 | 292 | this.setState({ 293 | generating: true 294 | }) 295 | 296 | var i 297 | var output = [] 298 | if (this.state.validSeed && this.state.validEndIndex && this.state.validStartIndex) { 299 | await nano_old.init() 300 | for (i=parseInt(this.state.startIndex); i <= parseInt(this.state.endIndex); i++) { 301 | let privKey = nano_old.deriveSecretKey(this.state.seed, i) 302 | let pubKey = nano_old.derivePublicKey(privKey) 303 | let address = nano.deriveAddress(pubKey, {useNanoPrefix: true}) 304 | 305 | // save result in array 306 | var obj = {} 307 | if (this.state.indexChecked) { 308 | obj.index = i 309 | } 310 | if (this.state.privKeyChecked) { 311 | obj.privKey = privKey 312 | } 313 | if (this.state.pubKeyChecked) { 314 | obj.pubKey = pubKey 315 | } 316 | if (this.state.addressChecked) { 317 | obj.address = address 318 | } 319 | 320 | output.push(obj) 321 | } 322 | 323 | this.setState({ 324 | output: JSON.stringify(output, null, 2) 325 | }) 326 | } 327 | else { 328 | new MainPage().notifyInvalidFormat() 329 | } 330 | 331 | this.setState({ 332 | generating: false 333 | }) 334 | } 335 | 336 | render() { 337 | return ( 338 |
339 |

Mass extract keypairs in a range of indexes using a fixed seed

340 |
    341 |
  • A large index range may take a very long time
  • 342 |
343 | 344 | 345 | 346 | 347 | Seed 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | Start Index 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | End Index 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 |
384 | 385 | 386 |
387 |
388 | 389 | 390 |
391 |
392 | 393 | 394 |
395 |
396 | 397 | 398 |
399 |
400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | JSON 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 |
419 |
420 | 421 |
422 |
423 |
424 | ) 425 | } 426 | } 427 | export default AddressExtractorTool 428 | -------------------------------------------------------------------------------- /src/tools/QRTool.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { InputGroup, FormControl, Button} from 'react-bootstrap' 3 | import * as helpers from '../helpers' 4 | import jsQR from 'jsqr' 5 | import {toast } from 'react-toastify' 6 | import QrImageStyle from './components/qrImageStyle' 7 | import ImageFilters from 'canvas-filters' 8 | const toolParam = 'qr' 9 | 10 | class QRTool extends Component { 11 | constructor(props) { 12 | super(props) 13 | 14 | // READER 15 | this.video = null 16 | this.videoCanvas = null 17 | this.videoCtx = null 18 | this.loadingMessage = null 19 | this.outputFound = false 20 | 21 | this.state = { 22 | input: '', 23 | qrContent: '', 24 | qrState: 0, //qr size 25 | qrSize: 1024, 26 | selectedOption: '0', 27 | output: "", 28 | selectedFile: null, 29 | saturation: '0%', 30 | } 31 | 32 | this.handleInputChange = this.handleInputChange.bind(this) 33 | this.clearText = this.clearText.bind(this) 34 | this.updateQR = this.updateQR.bind(this) 35 | this.double = this.double.bind(this) 36 | this.startReader = this.startReader.bind(this) 37 | this.drawRect = this.drawRect.bind(this) 38 | this.tick = this.tick.bind(this) 39 | this.reset = this.reset.bind(this) 40 | } 41 | 42 | componentDidMount = () => { 43 | // Read URL params from parent and construct new quick path 44 | var type = this.props.state.type 45 | 46 | if (typeof type !== 'undefined') { 47 | this.optionChange(type) 48 | } 49 | if(!type) { 50 | this.setParams() 51 | } 52 | } 53 | 54 | // Defines the url params 55 | setParams() { 56 | helpers.setURLParams('?tool='+toolParam + '&type='+this.state.selectedOption) 57 | } 58 | 59 | //Clear text from input field 60 | clearText(event) { 61 | switch(event.target.value) { 62 | case 'input': 63 | this.setState({ 64 | input: '', 65 | }, 66 | function() { 67 | this.updateQR() 68 | this.setParams() 69 | }) 70 | break 71 | default: 72 | break 73 | } 74 | } 75 | 76 | handleInputChange(event) { 77 | this.inputChange(event.target.value) 78 | } 79 | 80 | inputChange(input) { 81 | this.setState({ 82 | input: input, 83 | }, 84 | function() { 85 | this.updateQR() 86 | }) 87 | } 88 | 89 | updateQR() { 90 | this.setState({ 91 | qrContent: this.state.input 92 | }) 93 | } 94 | 95 | print() { 96 | window.print() 97 | } 98 | 99 | reset() { 100 | switch (this.state.selectedOption) { 101 | case '1': 102 | this.startReader() 103 | break 104 | 105 | case '2': 106 | this.setState({ 107 | output: '', 108 | selectedFile: null, 109 | }) 110 | this.outputFound = false 111 | const canvas = this.refs.fileCanvas 112 | const ctx = canvas.getContext('2d') 113 | ctx.clearRect(0, 0, canvas.width, canvas.height); 114 | canvas.hidden = true 115 | break 116 | 117 | default: 118 | break 119 | } 120 | 121 | } 122 | 123 | // main checkboxes 124 | handleOptionChange = changeEvent => { 125 | this.optionChange(changeEvent.target.value) 126 | } 127 | 128 | optionChange(val) { 129 | this.setState({ 130 | selectedOption: val, 131 | },function() { 132 | this.setParams() 133 | //Init webcam 134 | if (val === "1") { 135 | this.startReader() 136 | } 137 | if (val === "2") { 138 | this.setState({ 139 | output: '', 140 | selectedFile: null, 141 | }) 142 | this.outputFound = false 143 | } 144 | }) 145 | } 146 | 147 | // loop qr state 1x, 2x, 4x 148 | double() { 149 | var state = this.state.qrState 150 | state = state + 1 151 | if (state >= helpers.qrClassesContainer.length) { 152 | state = 0 153 | } 154 | this.setState({ 155 | qrState: state 156 | }) 157 | } 158 | 159 | //READER CODE 160 | // Draws border around video canvas 161 | drawLine(canvas, begin, end, color, width) { 162 | canvas.beginPath() 163 | canvas.moveTo(begin.x, begin.y) 164 | canvas.lineTo(end.x, end.y) 165 | canvas.lineWidth = width 166 | canvas.strokeStyle = color 167 | canvas.stroke() 168 | } 169 | 170 | drawRect(canvas, height, code, color) { 171 | const lineWidth = Math.ceil(Math.max(height / 100, 3)) 172 | this.drawLine(canvas, code.location.topLeftCorner, code.location.topRightCorner, color, lineWidth) 173 | this.drawLine(canvas, code.location.topRightCorner, code.location.bottomRightCorner, color, lineWidth) 174 | this.drawLine(canvas, code.location.bottomRightCorner, code.location.bottomLeftCorner, color, lineWidth) 175 | this.drawLine(canvas, code.location.bottomLeftCorner, code.location.topLeftCorner, color, lineWidth) 176 | } 177 | 178 | // Upload a file 179 | onFileHandler = event => { 180 | let reader = new FileReader() 181 | let file = event.target.files[0] 182 | let fileName = event.target.value 183 | 184 | reader.onloadend = () => { 185 | this.outputFound = false 186 | this.setState({ 187 | selectedFile: fileName.replace(/.*[\\]/, ''), 188 | },function() { 189 | const fileCanvas = this.refs.fileCanvas 190 | const fileCtx = fileCanvas.getContext('2d') 191 | fileCanvas.hidden = false 192 | 193 | var qrImage = new Image() 194 | qrImage.src = reader.result 195 | qrImage.onload = function() { 196 | var newWidth = qrImage.width 197 | var newHeight = qrImage.height 198 | // Scale down if image too large 199 | fileCanvas.width = newWidth 200 | fileCanvas.height = newHeight 201 | if (newWidth > 1500 && qrImage.height > 0) { 202 | let ratio = qrImage.height / qrImage.width 203 | newWidth = 1500 204 | newHeight = Math.ceil(newWidth * ratio) 205 | } 206 | fileCanvas.width = newWidth 207 | fileCanvas.height = newHeight 208 | fileCtx.drawImage(qrImage,0,0, newWidth, newHeight) 209 | var imageData = fileCtx.getImageData(0, 0, newWidth, newHeight) 210 | 211 | // increase the chances to succeed on poor images 212 | var filtered = ImageFilters.GrayScale(imageData) 213 | filtered = ImageFilters.BrightnessContrastPhotoshop(filtered, 0, 40) 214 | fileCtx.putImageData(filtered, 0, 0) // put back the filtered image 215 | 216 | var code = jsQR(imageData.data, newWidth, newHeight) 217 | 218 | if (code) { 219 | // draw line around the QR 220 | this.drawRect(fileCtx, imageData.height, code, "#ff0000") 221 | 222 | if (!this.outputFound) { 223 | this.outputFound = true 224 | toast("Found QR data", helpers.getToast(helpers.toastType.SUCCESS_AUTO)) 225 | this.setState({ 226 | output: code.data, 227 | }) 228 | this.outputFound = true 229 | } 230 | } 231 | else { 232 | toast("Did not find a QR in the uploaded image", helpers.getToast(helpers.toastType.ERROR_AUTO_LONG)) 233 | this.setState({ 234 | output: '', 235 | }) 236 | this.outputFound = false 237 | } 238 | }.bind(this) 239 | }.bind(this)) 240 | } 241 | 242 | if (file instanceof Blob) { 243 | reader.readAsDataURL(file) 244 | } 245 | } 246 | 247 | // Webcam reader and qr detector 248 | tick() { 249 | if (this.video.readyState === this.video.HAVE_ENOUGH_DATA) { 250 | this.videoCanvas.hidden = false 251 | this.videoCanvas.height = this.video.videoHeight 252 | this.videoCanvas.width = this.video.videoWidth 253 | this.videoCtx.drawImage(this.video, 0, 0, this.videoCanvas.width, this.videoCanvas.height) 254 | var imageData = this.videoCtx.getImageData(0, 0, this.videoCanvas.width, this.videoCanvas.height) 255 | 256 | // increase the chances to succeed on poor images 257 | var filtered = ImageFilters.GrayScale(imageData) 258 | filtered = ImageFilters.BrightnessContrastPhotoshop(filtered, 0, 40) 259 | this.videoCtx.putImageData(filtered, 0, 0) // put back the filtered image 260 | 261 | var code = jsQR(imageData.data, imageData.width, imageData.height, { 262 | inversionAttempts: "dontInvert", 263 | }) 264 | if (code) { 265 | // draw line around the QR 266 | this.drawRect(this.videoCtx, imageData.height, code, "#ff0000") 267 | 268 | if (!this.outputFound) { 269 | this.outputFound = true 270 | toast("Found QR data", helpers.getToast(helpers.toastType.SUCCESS_AUTO)) 271 | this.setState({ 272 | output: code.data, 273 | }) 274 | } 275 | } 276 | } 277 | 278 | // only run video stream as long as reader is active 279 | if (this.state.selectedOption === "1" && !this.outputFound) { 280 | requestAnimationFrame(this.tick) 281 | } 282 | else { 283 | // stop video stream when QR found 284 | this.video.srcObject.getTracks()[0].stop() 285 | } 286 | } 287 | 288 | // Init video component 289 | startReader() { 290 | this.setState({ 291 | output: '', 292 | }) 293 | this.outputFound = false 294 | this.video = document.createElement("video") 295 | this.videoCanvas = this.refs.videoCanvas 296 | this.videoCtx = this.videoCanvas.getContext('2d') 297 | 298 | // Use facingMode: environment to attemt to get the front camera on phones 299 | try { 300 | let device = navigator.mediaDevices 301 | if (device) { 302 | device.getUserMedia({ video: { facingMode: "environment", audio: false, video: true } }).then(async function(stream) { 303 | this.video.srcObject = stream 304 | this.video.setAttribute("playsinline", true) // required to tell iOS safari we don't want fullscreen 305 | this.video.setAttribute('autoplay', ''); 306 | this.video.setAttribute('muted', ''); 307 | await this.video.play() 308 | requestAnimationFrame(this.tick) 309 | }.bind(this)).catch(function(err) { 310 | console.log(err) 311 | toast("Failed to load video device. Make sure it's connected.", helpers.getToast(helpers.toastType.ERROR_AUTO_LONG)) 312 | }) 313 | } 314 | } 315 | catch(error) { 316 | console.log(error) 317 | } 318 | } 319 | 320 | render() { 321 | return ( 322 |
323 |
324 | 325 |
326 | 327 | 328 |
329 |
330 | 331 | 332 |
333 |
334 | 335 | 336 |
337 |
338 | 339 |
    340 |
  • Click on QR to toggle size, if using large data
  • 341 |
342 | 343 |
344 | 345 | 346 | 347 | QR Text 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 |
359 |
360 | 361 |
362 |
363 | 364 |
365 |
366 | 367 |
368 |
    369 |
  • If the cam stream is not visible or QR data not fetched, try use another device/browser
  • 370 |
371 | 372 | 373 | 374 | 375 | QR Data 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 |
385 | 386 |
387 |
    388 |
  • No image data is stored other than in device RAM until page is reloaded or reset
  • 389 |
390 | 391 | 392 | 393 | Upload 394 | 395 | 396 |
397 | 405 | 408 |
409 |
410 | 411 | 412 | 413 | 414 | 415 | QR Data 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 |
425 |
426 | ) 427 | } 428 | } 429 | export default QRTool 430 | -------------------------------------------------------------------------------- /src/tools/WorkGeneratorTool.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import * as nano from 'nanocurrency' 3 | import { InputGroup, FormControl, Button} from 'react-bootstrap' 4 | import * as helpers from '../helpers' 5 | import * as webglpow from '../modules/nano-webgl-pow' 6 | import MainPage from '../mainPage' 7 | import {toast } from 'react-toastify' 8 | import QrImageStyle from './components/qrImageStyle' 9 | const toolParam = 'pow' 10 | 11 | class WorkGeneratorTool extends Component { 12 | constructor(props) { 13 | super(props) 14 | 15 | this.webGLPower = [256,512,1024,2048,4096,8192,16384,32768,65536,131072] 16 | this.difficulties = [ 17 | helpers.constants.WORK_THRESHOLD_LOW, 18 | helpers.constants.WORK_THRESHOLD_ORIGINAL, 19 | helpers.constants.WORK_THRESHOLD_2X, 20 | helpers.constants.WORK_THRESHOLD_4X, 21 | helpers.constants.WORK_THRESHOLD_8X 22 | ] 23 | this.webglWidth = this.webGLPower[3] 24 | this.webglHeight = 1024 25 | 26 | this.state = { 27 | workHash: '', 28 | work: '', 29 | qrContent: '', 30 | qrSize: 512, 31 | activeQR: false, 32 | qrHidden: true, 33 | qrState: 0, //qr size 34 | validWorkHash: false, 35 | validWork: false, 36 | generating: false, 37 | output: '', 38 | savedOutput: [], 39 | selectedLoadOption: '4', 40 | selectedMultiplierOption: '1', 41 | } 42 | 43 | this.handleWorkHashChange = this.handleWorkHashChange.bind(this) 44 | this.sample = this.sample.bind(this) 45 | this.generateWork = this.generateWork.bind(this) 46 | this.updateQR = this.updateQR.bind(this) 47 | this.clearText = this.clearText.bind(this) 48 | this.handleLoadOptionChange = this.handleLoadOptionChange.bind(this) 49 | this.handleMultiplierOptionChange = this.handleMultiplierOptionChange.bind(this) 50 | this.double = this.double.bind(this) 51 | this.clearOutput = this.clearOutput.bind(this) 52 | } 53 | 54 | // Init component 55 | componentDidMount() { 56 | // Read URL params from parent and construct new quick path 57 | var hash = this.props.state.hash 58 | var load = this.props.state.load 59 | var multiplier = this.props.state.multiplier 60 | if (hash) { 61 | this.workHashChange(hash) 62 | } 63 | if (parseInt(load) < this.webGLPower.length) { 64 | this.loadChange(load) 65 | } 66 | if (parseInt(multiplier) < this.difficulties.length) { 67 | this.multiplierChange(multiplier) 68 | } 69 | if (!hash && !load && !multiplier) { 70 | this.setParams() 71 | } 72 | } 73 | 74 | // Defines the url params 75 | setParams() { 76 | helpers.setURLParams('?tool='+toolParam + '&hash='+this.state.workHash + '&load='+this.state.selectedLoadOption + '&multiplier='+this.state.selectedMultiplierOption) 77 | } 78 | 79 | //Clear text from input field 80 | clearText(event) { 81 | switch(event.target.value) { 82 | case 'workHash': 83 | this.setState({ 84 | workHash: '', 85 | validWorkHash: false 86 | }, 87 | function() { 88 | this.updateQR() 89 | this.setParams() 90 | }) 91 | break 92 | 93 | case 'work': 94 | this.setState({ 95 | work: '', 96 | validWork: false 97 | }, 98 | function() { 99 | this.updateQR() 100 | this.setParams() 101 | }) 102 | break 103 | default: 104 | break 105 | } 106 | } 107 | 108 | clearOutput() { 109 | this.setState({ 110 | output: '', 111 | savedOutput: [] 112 | }) 113 | } 114 | 115 | // loop qr state 1x, 2x, 4x 116 | double() { 117 | var state = this.state.qrState 118 | state = state + 1 119 | if (state >= helpers.qrClassesContainer.length) { 120 | state = 0 121 | } 122 | this.setState({ 123 | qrState: state 124 | }) 125 | } 126 | 127 | // Select GPU load 128 | handleLoadOptionChange = changeEvent => { 129 | this.loadChange(changeEvent.target.value) 130 | } 131 | 132 | loadChange(val) { 133 | this.setState({ 134 | selectedLoadOption: val 135 | },function() { 136 | this.setParams() 137 | }) 138 | this.webglWidth = this.webGLPower[parseInt(val)] 139 | this.webglHeight = 256 140 | 141 | } 142 | 143 | // Select difficulty multiplier 144 | handleMultiplierOptionChange = changeEvent => { 145 | this.multiplierChange(changeEvent.target.value) 146 | } 147 | 148 | multiplierChange(val) { 149 | this.setState({ 150 | selectedMultiplierOption: val 151 | },function() { 152 | this.setParams() 153 | }) 154 | } 155 | 156 | // Any QR button is pressed 157 | handleQRChange = changeEvent => { 158 | let val = changeEvent.target.value 159 | // deselect button if clicking on the same button 160 | if (this.state.qrActive === val) { 161 | this.setState({ 162 | qrActive: '', 163 | qrHidden: true 164 | }) 165 | } 166 | else { 167 | this.setState({ 168 | qrActive: val, 169 | qrHidden: false, 170 | }, 171 | function() { 172 | this.updateQR() 173 | }) 174 | } 175 | } 176 | 177 | updateQR() { 178 | switch(this.state.qrActive) { 179 | case 'workHash': 180 | this.setState({ 181 | qrContent: this.state.workHash, 182 | }) 183 | break 184 | case 'work': 185 | this.setState({ 186 | qrContent: this.state.work, 187 | }) 188 | break 189 | default: 190 | this.setState({ 191 | qrContent: '', 192 | qrHidden: true, 193 | }) 194 | break 195 | } 196 | } 197 | 198 | sample() { 199 | this.setState({ 200 | workHash: '4E5004CA14899B8F9AABA7A76D010F73E6BAE54948912588B8C4FE0A3B558CA5', 201 | validWorkHash: true, 202 | }, 203 | function() { 204 | this.updateQR() 205 | this.setParams() 206 | }) 207 | } 208 | 209 | handleWorkHashChange(event) { 210 | this.workHashChange(event.target.value) 211 | } 212 | 213 | workHashChange(hash) { 214 | if (!nano.checkHash(hash)) { 215 | this.setState({ 216 | workHash: hash, 217 | validWorkHash: false, 218 | }, 219 | function() { 220 | this.updateQR() 221 | }) 222 | if (hash !== '') { 223 | new MainPage().notifyInvalidFormat() 224 | } 225 | return 226 | } 227 | this.setState({ 228 | workHash: hash, 229 | validWorkHash: true, 230 | }, 231 | function() { 232 | this.updateQR() 233 | this.setParams() 234 | }) 235 | } 236 | 237 | generateWork() { 238 | this.setState({ 239 | generating: true 240 | }) 241 | 242 | if (this.state.validWorkHash) { 243 | try { 244 | toast("Started generating PoW...", helpers.getToast(helpers.toastType.SUCCESS_AUTO)) 245 | const start = Date.now() 246 | webglpow.calculate(this.state.workHash, this.difficulties[parseInt(this.state.selectedMultiplierOption)], this.webglWidth, this.webglHeight, 247 | (work, n) => { 248 | const hashes = this.webglWidth * this.webglHeight * n 249 | const calcTime = (Date.now() - start) / 1000 250 | toast("Successfully generated PoW at " + helpers.addCommas(String(Math.round(hashes / calcTime / 1000))) +" khash/s", helpers.getToast(helpers.toastType.SUCCESS_AUTO)) 251 | this.setState({ 252 | generating: false 253 | }) 254 | this.workChange(work) 255 | }, 256 | n => { 257 | toast("Calculated " + helpers.addCommas(n*this.webglWidth * this.webglHeight) + " hashes...", helpers.getToast(helpers.toastType.SUCCESS_AUTO)) 258 | } 259 | ) 260 | } 261 | catch(error) { 262 | if(error.message === 'webgl2_required') { 263 | toast("WebGL 2 is required to generate work.", helpers.getToast(helpers.toastType.ERROR)) 264 | } 265 | else if(error.message === 'invalid_hash') { 266 | toast("Block hash must be 64 character hex string", helpers.getToast(helpers.toastType.ERROR)) 267 | } 268 | else { 269 | toast("An unknown error occurred while generating PoW", helpers.getToast(helpers.toastType.ERROR)) 270 | console.log("An unknown error occurred while generating PoW" + error) 271 | } 272 | this.setState({ 273 | generating: false 274 | }) 275 | return 276 | } 277 | } 278 | else { 279 | toast("Need a valid input hash to generate work", helpers.getToast(helpers.toastType.ERROR_AUTO_LONG)) 280 | } 281 | } 282 | 283 | workChange(hash) { 284 | /* 285 | if (!nano.validateWork({blockHash:this.state.workHash, work:hash})) { 286 | toast("The output work did not validate against input hash.", helpers.getToast(helpers.toastType.ERROR_AUTO_LONG)) 287 | this.setState({ 288 | work: '', 289 | validWork: false 290 | }) 291 | return 292 | } 293 | */ 294 | this.setState({ 295 | work: hash, 296 | validWork: true 297 | }, 298 | function() { 299 | this.updateQR() 300 | this.appendOutput() 301 | }) 302 | } 303 | 304 | // add a new line to output text field 305 | appendOutput() { 306 | let output = {hash: this.state.workHash, work: this.state.work} 307 | var saved = this.state.savedOutput 308 | saved.push(output) 309 | this.setState({ 310 | output: JSON.stringify(saved, null, 2), 311 | savedOutput: saved 312 | }) 313 | } 314 | 315 | render() { 316 | return ( 317 |
318 |

Generate Proof of Work (PoW) from Input Hash

319 |
    320 |
  • The generator is using the GPU via webGL which is not supported in all browsers
  • 321 |
  • 1x equals node v21 default SEND threshold of fffffff800000000
  • 322 |
  • 1/64x equals node v21 default RECEIVE threshold of fffffe0000000000
  • 323 |
  • Higher GPU Load is for high end GPUs
  • 324 |
325 | 326 | 327 | 328 | 329 | Work Hash 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | PoW 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 |
Multiplier:
355 |
356 | 357 | 358 |
359 |
360 | 361 | 362 |
363 |
364 | 365 | 366 |
367 |
368 | 369 | 370 |
371 |
372 | 373 | 374 |
375 |
376 | 377 | 378 |
GPU Load:
379 |
380 | 381 | 382 |
383 |
384 | 385 | 386 |
387 |
388 | 389 | 390 |
391 |
392 | 393 | 394 |
395 |
396 | 397 | 398 |
399 |
400 | 401 | 402 |
403 |
404 | 405 | 406 |
407 |
408 | 409 | 410 |
411 |
412 | 413 | 414 |
415 |
416 | 417 | 418 |
419 |
420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | JSON 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 |
440 |
441 | 442 |
443 |
444 |
445 | ) 446 | } 447 | } 448 | export default WorkGeneratorTool -------------------------------------------------------------------------------- /src/helpers.js: -------------------------------------------------------------------------------- 1 | import MainPage from './mainPage' 2 | import { css } from 'glamor'; 3 | import $ from 'jquery' 4 | import * as nano from 'nanocurrency' 5 | import bigInt from 'big-integer' 6 | import bigDec from 'bigdecimal' //https://github.com/iriscouch/bigdecimal.js 7 | import * as convert from './modules/conversion' 8 | import namedNumber from 'hsimp-named-number' 9 | import namedNumberDictionary from 'hsimp-named-number/named-number-dictionary.json' 10 | import nacl from 'tweetnacl/nacl'; 11 | import { Base64 } from 'js-base64'; 12 | import * as rpc from './rpc' //rpc creds not shared on github 13 | 14 | namedNumber.setDictionary(namedNumberDictionary) 15 | 16 | const RPC_TIMEOUT = 10000 17 | 18 | //CONSTANTS 19 | export const constants = { 20 | INDEX_MAX: 4294967295, //seed index 21 | KEYS_MAX: 10000, //max keys to export 22 | SWEEP_MAX_INDEX: 100, //max keys to sweep using blake2b derivation 23 | SWEEP_MAX_PENDING: 1000, // max pending blocks to process per run 24 | RPC_MAX: 500, //max rpc requests of same type, for example pending blocks 25 | SAMPLE_PAYMENT_ADDRESS: 'nano_1gur37mt5cawjg5844bmpg8upo4hbgnbbuwcerdobqoeny4ewoqshowfakfo', 26 | RPC_SERVER: rpc.RPC_SERVER, 27 | RPC_SWEEP_SERVER: rpc.RPC_SWEEP_SERVER, 28 | RPC_LIMIT: rpc.RPC_LIMIT, 29 | RPC_CREDS: rpc.RPC_CREDS, 30 | WORK_THRESHOLD_LOW: '0xFFFFFE00', 31 | WORK_THRESHOLD_ORIGINAL: '0xFFFFFFF8', 32 | WORK_THRESHOLD_2X: '0xFFFFFFFC', 33 | WORK_THRESHOLD_4X: '0xFFFFFFFE', 34 | WORK_THRESHOLD_8X: '0xFFFFFFFF', 35 | } 36 | 37 | class RPCError extends Error { 38 | constructor(code, ...params) { 39 | super(...params) 40 | 41 | // Maintains proper stack trace for where our error was thrown (only available on V8) 42 | if (Error.captureStackTrace) { 43 | Error.captureStackTrace(this, RPCError) 44 | } 45 | 46 | this.name = 'RPCError' 47 | // Custom debugging information 48 | this.code = code 49 | } 50 | } 51 | 52 | // QR css 53 | export const qrClassesContainer = ["QR-container", "QR-container-2x", "QR-container-4x"] 54 | export const qrClassesImg = ["QR-img", "QR-img-2x", "QR-img-4x"] 55 | 56 | // Subtract two big integers 57 | export function bigSubtract(input,value) { 58 | let insert = bigInt(input) 59 | let val = bigInt(value) 60 | return insert.subtract(val).toString() 61 | } 62 | 63 | // Generate secure random 64 char hex 64 | export function genSecureKey() { 65 | const rand = nacl.randomBytes(32) 66 | return rand.reduce((hex, idx) => hex + (`0${idx.toString(16)}`).slice(-2), '') 67 | } 68 | 69 | // uint8array to hex string 70 | export function uint8ToHex(uintValue) { 71 | let hex = ''; 72 | let aux; 73 | for (let i = 0; i < uintValue.length; i++) { 74 | aux = uintValue[i].toString(16).toUpperCase(); 75 | if (aux.length === 1) { 76 | aux = '0' + aux; 77 | } 78 | hex += aux; 79 | aux = ''; 80 | } 81 | 82 | return(hex); 83 | } 84 | 85 | // Add two big integers 86 | export function bigAdd(input,value) { 87 | let insert = bigInt(input) 88 | let val = bigInt(value) 89 | return insert.add(val).toString() 90 | } 91 | 92 | // Checks if a big integer is negative 93 | export function bigIsNegative(input) { 94 | return bigInt(input).isNegative() 95 | } 96 | 97 | export function rawTonano(input) { 98 | //return this.isNumeric(input) ? nano.convert(input, {from: nano.Unit.raw, to: nano.Unit.nano}) : 'N/A' 99 | return isNumeric(input) ? convert.convert(input, {from: nano.Unit.raw, to: nano.Unit.nano}) : 'N/A' 100 | } 101 | 102 | export function rawToMnano(input) { 103 | //return this.isNumeric(input) ? nano.convert(input, {from: nano.Unit.raw, to: nano.Unit.NANO}) : 'N/A' 104 | return isNumeric(input) ? convert.convert(input, {from: nano.Unit.raw, to: nano.Unit.NANO}) : 'N/A' 105 | } 106 | 107 | export function nanoToRaw(input) { 108 | //return this.isNumeric(input) ? nano.convert(input, {from: nano.Unit.nano, to: nano.Unit.raw}) : 'N/A' 109 | return isNumeric(input) ? convert.convert(input, {from: nano.Unit.nano, to: nano.Unit.raw}) : 'N/A' 110 | } 111 | 112 | export function nanoToMnano(input) { 113 | //return this.isNumeric(input) ? nano.convert(input, {from: nano.Unit.nano, to: nano.Unit.NANO}) : 'N/A' 114 | return isNumeric(input) ? convert.convert(input, {from: nano.Unit.nano, to: nano.Unit.NANO}) : 'N/A' 115 | } 116 | 117 | export function MnanoToRaw(input) { 118 | //return this.isNumeric(input) ? nano.convert(input, {from: nano.Unit.NANO, to: nano.Unit.raw}) : 'N/A' 119 | return isNumeric(input) ? convert.convert(input, {from: nano.Unit.NANO, to: nano.Unit.raw}) : 'N/A' 120 | } 121 | 122 | export function MnanoTonano(input) { 123 | //return this.isNumeric(input) ? nano.convert(input, {from: nano.Unit.NANO, to: nano.Unit.nano}) : 'N/A' 124 | return isNumeric(input) ? convert.convert(input, {from: nano.Unit.NANO, to: nano.Unit.nano}) : 'N/A' 125 | } 126 | 127 | // Check if numeric string 128 | export function isNumeric(val) { 129 | //numerics and last character is not a dot and number of dots is 0 or 1 130 | let isnum = /^-?\d*\.?\d*$/.test(val) 131 | if (isnum && String(val).slice(-1) !== '.') { 132 | return true 133 | } 134 | else { 135 | return false 136 | } 137 | } 138 | 139 | // Check if numeric and larger than 0 string 140 | export function isValidDiffMultiplier(val) { 141 | //numerics and last character is not a dot and number of dots is 0 or 1 142 | let isnum = /^-?\d*\.?\d*$/.test(val) 143 | if (isnum && String(val).slice(-1) !== '.') { 144 | if (parseFloat(val) > 0) { 145 | return true 146 | } 147 | else { 148 | return false 149 | } 150 | } 151 | else { 152 | return false 153 | } 154 | } 155 | 156 | // Return number of logical processors (not suppoerted on most mobile browsers) 157 | export function getHardwareConcurrency() { 158 | var threads = window.navigator.hardwareConcurrency || 1; 159 | if (threads >= 12) { 160 | threads-=2 //save two thread for handling the site 161 | } 162 | else if (threads >= 6) { 163 | threads-=1 //save one threads for handling the site 164 | } 165 | return threads 166 | } 167 | 168 | //Copy any text to clipboard 169 | export function copyText(event) { 170 | var dummy = document.createElement("input") 171 | document.body.appendChild(dummy) 172 | dummy.setAttribute('value', event.target.value) 173 | dummy.select() 174 | let success = document.execCommand("copy") 175 | document.body.removeChild(dummy) 176 | 177 | if (success) { 178 | new MainPage().notifyCopy() 179 | } 180 | else { 181 | new MainPage().notifyCopyFail() 182 | } 183 | } 184 | 185 | export function copyOutput() { 186 | copyOutputText() 187 | } 188 | 189 | export function copyInput1() { 190 | copyOutputText('#input-area') 191 | } 192 | 193 | export function copyInput2() { 194 | copyOutputText('#input2-area') 195 | } 196 | 197 | // Copy output of textarea to clipboard, and preserve line breaks 198 | export function copyOutputText(id='#output-area') { 199 | const disabledProp = $(id).prop("disabled") 200 | if (disabledProp) { 201 | $(id).prop("disabled", false); 202 | } 203 | $(id).trigger('select') 204 | document.execCommand('copy') 205 | if (disabledProp) { 206 | $(id).prop("disabled", true) 207 | } 208 | 209 | // clear selection 210 | if (window.getSelection) {window.getSelection().removeAllRanges();} 211 | else if (document.selection) {document.selection.empty();} 212 | 213 | let success = document.execCommand("copy") 214 | 215 | if (success) { 216 | new MainPage().notifyCopy() 217 | } 218 | else { 219 | new MainPage().notifyCopyFail() 220 | } 221 | } 222 | 223 | //Toast styling 224 | export const toastType = { 225 | SUCCESS: 'success', 226 | SUCCESS_AUTO: 'success-auto', 227 | ERROR: 'error', 228 | ERROR_AUTO: 'error-auto', 229 | ERROR_AUTO_LONG: 'error-auto-long' 230 | } 231 | 232 | export function getToast(id) { 233 | const colorRed = 'rgba(214,95,100,1.0)' 234 | const colorBlue = 'rgba(74,144,226,1.0)' 235 | 236 | var color = '' 237 | switch (id) { 238 | case toastType.SUCCESS: 239 | color = colorBlue 240 | break 241 | 242 | case toastType.SUCCESS_AUTO: 243 | color = colorBlue 244 | break 245 | 246 | case toastType.ERROR: 247 | color = colorRed 248 | break 249 | 250 | case toastType.ERROR_AUTO: 251 | color = colorRed 252 | break 253 | 254 | case toastType.ERROR_AUTO_LONG: 255 | color = colorRed 256 | break 257 | 258 | default: 259 | color = colorBlue 260 | break 261 | } 262 | return { 263 | containerId: id, 264 | className: css({ 265 | background: color, 266 | minHeight: '54px' 267 | }), 268 | bodyClassName: css({ 269 | fontSize: 'calc(10px + 0.8vmin)', 270 | color: '#EEE', 271 | }), 272 | progressClassName: css({ 273 | background: '#EEE' 274 | }) 275 | } 276 | } 277 | 278 | //Thousand separator 279 | export function addCommas(nStr) { 280 | nStr += ''; 281 | var x = nStr.split('.'); 282 | var x1 = x[0]; 283 | var x2 = x.length > 1 ? '.' + x[1] : ''; 284 | var rgx = /(\d+)(\d{3})/; 285 | while (rgx.test(x1)) { 286 | x1 = x1.replace(rgx, '$1,$2'); 287 | } 288 | return x1 + x2; 289 | } 290 | 291 | export const MS_S = 1000; 292 | export const MS_M = MS_S * 60; 293 | export const MS_H = MS_M * 60; 294 | export const MS_D = MS_H * 24; 295 | export const MS_W = MS_D * 7; 296 | export const MS_Y = MS_D * 365.25; 297 | 298 | export function plural(n, text1, textn) { 299 | return n > 1 ? textn : text1; 300 | } 301 | 302 | function formatDurationWord(ms, ref, word1, wordn) { 303 | const value = Math.round(ms / ref); 304 | return `${value} ${plural(value, word1, wordn)}`; 305 | } 306 | 307 | export function formatDurationEstimation(ms) { 308 | if (ms >= MS_Y * 1000) { 309 | const name = namedNumber(Math.round(ms / MS_Y)); 310 | return `${name.getName()} years`; 311 | } 312 | if (ms >= MS_Y) { 313 | return formatDurationWord(ms, MS_Y, 'year', 'years'); 314 | } 315 | if (ms >= MS_W) { 316 | return formatDurationWord(ms, MS_W, 'week', 'weeks'); 317 | } 318 | if (ms >= MS_D) { 319 | return formatDurationWord(ms, MS_D, 'day', 'days'); 320 | } 321 | if (ms >= MS_H) { 322 | return formatDurationWord(ms, MS_H, 'hour', 'hours'); 323 | } 324 | if (ms >= MS_M) { 325 | return formatDurationWord(ms, MS_M, 'minute', 'minutes'); 326 | } 327 | return formatDurationWord(ms, MS_S, 'second', 'seconds'); 328 | } 329 | 330 | // From a range of numbers, return start and end values of the numerous chunks specified by the count 331 | export function getIndexChunks(start, end, count) { 332 | var rangeChunks = [] 333 | var i = start 334 | var len = Math.ceil((end-start)/count) 335 | var chunkEnd = 0 336 | while (i <= end-start) { 337 | chunkEnd = i + len 338 | if (chunkEnd > end) { 339 | chunkEnd = end 340 | } 341 | rangeChunks.push({indexStart: i, indexEnd: chunkEnd}) 342 | i += len + 1 343 | } 344 | // special case if zero 345 | if (len === 0) { 346 | rangeChunks = [{indexStart: 0, indexEnd: 0}] 347 | } 348 | return rangeChunks 349 | } 350 | 351 | // Convert URL params to accessible object 352 | export function getUrlParams(url) { 353 | 354 | // get query string from url (optional) or window 355 | var queryString = url ? url.split('?')[1] : window.location.search.slice(1); 356 | 357 | // we'll store the parameters here 358 | var obj = {}; 359 | 360 | // if query string exists 361 | if (queryString) { 362 | // stuff after # is not part of query string, so get rid of it 363 | queryString = queryString.split('#')[0]; 364 | // split our query string into its component parts 365 | var arr = queryString.split('&'); 366 | for (var i = 0; i < arr.length; i++) { 367 | // separate the keys and the values 368 | var a = arr[i].split('='); 369 | // set parameter name and value (use 'true' if empty) 370 | var paramName = a[0]; 371 | var paramValue = typeof (a[1]) === 'undefined' ? true : a[1]; 372 | paramValue = decodeURI(paramValue); 373 | //paramValue = this.sanitize(paramValue); //sanitizer 374 | // if the paramName ends with square brackets, e.g. colors[] or colors[2] 375 | if (paramName.match(/\[(\d+)?\]$/)) { 376 | // create key if it doesn't exist 377 | var key = paramName.replace(/\[(\d+)?\]/, ''); 378 | if (!obj[key]) obj[key] = []; 379 | // if it's an indexed array e.g. colors[2] 380 | if (paramName.match(/\[\d+\]$/)) { 381 | // get the index value and add the entry at the appropriate position 382 | var index = /\[(\d+)\]/.exec(paramName)[1]; 383 | obj[key][index] = paramValue; 384 | } else { 385 | // otherwise add the value to the end of the array 386 | obj[key].push(paramValue); 387 | } 388 | } else { 389 | // we're dealing with a string 390 | if (!obj[paramName]) { 391 | // if it doesn't exist, create property 392 | obj[paramName] = paramValue; 393 | } else if (obj[paramName] && typeof obj[paramName] === 'string'){ 394 | // if property does exist and it's a string, convert it to an array 395 | obj[paramName] = [obj[paramName]]; 396 | obj[paramName].push(paramValue); 397 | } else { 398 | // otherwise add the property 399 | obj[paramName].push(paramValue); 400 | } 401 | } 402 | } 403 | } 404 | return obj; 405 | } 406 | 407 | // Replace the address bar content 408 | export function setURLParams(params) { 409 | if (window.history.pushState) { 410 | try { 411 | window.history.replaceState(null, null, "/"+params) 412 | } 413 | catch(error) { 414 | //console.log(error) 415 | } 416 | } 417 | } 418 | 419 | // Check if string is hexdecimal 420 | export function isHex(h) { 421 | let re = /^[0-9a-fA-F]+$/ 422 | if (re.test(h)) { 423 | return true 424 | } 425 | return false 426 | } 427 | 428 | // Determine new difficulty from base difficulty (hexadecimal string) and a multiplier (float). Returns hex string 429 | export function difficulty_from_multiplier(multiplier, base_difficulty) { 430 | let big64 = bigDec.BigDecimal(2).pow(64) 431 | let big_multiplier = bigDec.BigDecimal(multiplier) 432 | let big_base = bigDec.BigDecimal(bigDec.BigInteger(base_difficulty,16)) 433 | let mode = bigDec.RoundingMode.HALF_DOWN() 434 | return big64.subtract((big64.subtract(big_base).divide(big_multiplier,0,mode))).toBigInteger().toString(16) 435 | } 436 | 437 | // Determine new multiplier from base difficulty (hexadecimal string) and target difficulty (hexadecimal string). Returns float 438 | export function multiplier_from_difficulty(difficulty, base_difficulty) { 439 | let big64 = bigDec.BigDecimal(2).pow(64) 440 | let big_diff = bigDec.BigDecimal(bigDec.BigInteger(difficulty,16)) 441 | let big_base = bigDec.BigDecimal(bigDec.BigInteger(base_difficulty,16)) 442 | let mode = bigDec.RoundingMode.HALF_DOWN() 443 | return big64.subtract(big_base).divide(big64.subtract(big_diff),32,mode).toPlainString() 444 | } 445 | 446 | // Post data with no error handling 447 | /* 448 | export async function postData(data = {}) { 449 | // Default options are marked with * 450 | const response = await fetch(constants.RPC_SERVER, { 451 | method: 'POST', // *GET, POST, PUT, DELETE, etc. 452 | mode: 'cors', // no-cors, *cors, same-origin 453 | cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached 454 | credentials: 'same-origin', // include, *same-origin, omit 455 | headers: { 456 | 'Content-Type': 'application/json' 457 | // 'Content-Type': 'application/x-www-form-urlencoded', 458 | }, 459 | redirect: 'follow', // manual, *follow, error 460 | referrerPolicy: 'no-referrer', // no-referrer, *client 461 | body: JSON.stringify(data) // body data type must match "Content-Type" header 462 | }) 463 | return await response.json(); // parses JSON response into native JavaScript objects 464 | } 465 | */ 466 | 467 | // Post data with timeout and catch errors 468 | export async function postDataTimeout(data = {}, server=constants.RPC_SERVER) { 469 | let didTimeOut = false; 470 | 471 | return new Promise(function(resolve, reject) { 472 | const timeout = setTimeout(function() { 473 | didTimeOut = true; 474 | reject(new Error('Request timed out')); 475 | }, RPC_TIMEOUT); 476 | 477 | fetch(server, { 478 | method: 'POST', // *GET, POST, PUT, DELETE, etc. 479 | mode: 'cors', // no-cors, *cors, same-origin 480 | cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached 481 | credentials: 'same-origin', // include, *same-origin, omit 482 | headers: { 483 | 'Content-Type': 'application/json', 484 | // 'Content-Type': 'application/x-www-form-urlencoded', 485 | 'Authorization': 'Basic ' + Base64.encode(constants.RPC_CREDS) 486 | }, 487 | redirect: 'follow', // manual, *follow, error 488 | referrerPolicy: 'no-referrer', // no-referrer, *client 489 | body: JSON.stringify(data) // body data type must match "Content-Type" header 490 | }) 491 | .then(function(response) { 492 | // Clear the timeout as cleanup 493 | clearTimeout(timeout); 494 | if(!didTimeOut) { 495 | if(response.status === 200) { 496 | resolve(response); 497 | } 498 | else { 499 | //console.log('Bad RPC http status!'); 500 | throw new RPCError(response.status, "HTTP status "+response.status) 501 | } 502 | } 503 | }) 504 | .catch(function(err) { 505 | //console.log('RPC fetch failed! ', err); 506 | 507 | // Rejection already happened with setTimeout 508 | if(didTimeOut) return; 509 | // Reject with error 510 | reject(err); 511 | }); 512 | }) 513 | .then(async function(result) { 514 | // Request success and no timeout 515 | return await result.json() 516 | }) 517 | /*Catch error upstream instead 518 | .catch(function(err) { 519 | // Error: response error, request timeout or runtime error 520 | console.log('RPC error! ', err); 521 | });*/ 522 | } 523 | 524 | /* Helper functions to detect CPU concurrency */ 525 | var blobUrl = URL.createObjectURL(new Blob(['(', 526 | function() { 527 | // eslint-disable-next-line no-restricted-globals 528 | self.addEventListener('message', function(e) { 529 | // run worker for 4 ms 530 | var st = Date.now(); 531 | var et = st + 4; 532 | while(Date.now() < et); 533 | // eslint-disable-next-line no-restricted-globals 534 | self.postMessage({st: st, et: et}); 535 | }); 536 | }.toString(), 537 | ')()'], {type: 'application/javascript'})); 538 | 539 | export function sample(max, samples, numWorkers, callback) { 540 | if(samples === 0) { 541 | // get overlap average 542 | var avg = Math.floor(max.reduce(function(avg, x) { 543 | return avg + x; 544 | }, 0) / max.length); 545 | avg = Math.max(1, avg); 546 | if (avg >= 12) { 547 | avg-=2 //save two thread for handling the site 548 | } 549 | else if (avg >= 6) { 550 | avg-=1 //save one threads for handling the site 551 | } 552 | return callback(null, avg); 553 | } 554 | map(numWorkers, function(err, results) { 555 | max.push(reduce(numWorkers, results)); 556 | sample(max, samples - 1, numWorkers, callback); 557 | }); 558 | } 559 | 560 | function map(numWorkers, callback) { 561 | var workers = []; 562 | var results = []; 563 | for(var i = 0; i < numWorkers; ++i) { 564 | var worker = new Worker(blobUrl); 565 | worker.addEventListener('message', function(e) { 566 | results.push(e.data); 567 | if(results.length === numWorkers) { 568 | for(var i = 0; i < numWorkers; ++i) { 569 | workers[i].terminate(); 570 | } 571 | callback(null, results); 572 | } 573 | }); 574 | workers.push(worker); 575 | } 576 | for(i = 0; i < numWorkers; ++i) { 577 | workers[i].postMessage(i); 578 | } 579 | } 580 | 581 | function reduce(numWorkers, results) { 582 | // find overlapping time windows 583 | var overlaps = []; 584 | for(var n = 0; n < numWorkers; ++n) { 585 | var r1 = results[n]; 586 | var overlap = overlaps[n] = []; 587 | for(var i = 0; i < numWorkers; ++i) { 588 | if(n === i) { 589 | continue; 590 | } 591 | var r2 = results[i]; 592 | if((r1.st > r2.st && r1.st < r2.et) || 593 | (r2.st > r1.st && r2.st < r1.et)) { 594 | overlap.push(i); 595 | } 596 | } 597 | } 598 | // get maximum overlaps ... don't include overlapping worker itself 599 | // as the main JS process was also being scheduled during the work and 600 | // would have to be subtracted from the estimate anyway 601 | return overlaps.reduce(function(max, overlap) { 602 | return Math.max(max, overlap.length); 603 | }, 0); 604 | } --------------------------------------------------------------------------------