├── .babelrc ├── .dockerignore ├── .editorconfig ├── .eslintrc ├── .github ├── ISSUE_TEMPLATE.md └── workflows │ └── build.yml ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── circle.yml ├── config.json ├── doc └── README.md ├── docker-webpack.config.js ├── examples ├── add-key-auth.js ├── broadcast.html ├── get-post-content.js ├── index.html ├── multisig.js ├── server.js ├── stream.html ├── test-vote.js └── webpack-example │ ├── .gitignore │ ├── README.md │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── src │ └── index.js │ └── webpack.config.js ├── node-18.dockerfile ├── package.json ├── src ├── api │ ├── index.js │ ├── methods.js │ ├── rpc-auth.js │ └── transports │ │ ├── base.js │ │ ├── http.js │ │ ├── index.js │ │ └── ws.js ├── auth │ ├── ecc │ │ ├── README.md │ │ ├── index.js │ │ ├── package.json │ │ └── src │ │ │ ├── address.js │ │ │ ├── aes.js │ │ │ ├── brain_key.js │ │ │ ├── ecdsa.js │ │ │ ├── ecsignature.js │ │ │ ├── enforce_types.js │ │ │ ├── hash.js │ │ │ ├── key_private.js │ │ │ ├── key_public.js │ │ │ ├── key_utils.js │ │ │ └── signature.js │ ├── index.js │ ├── memo.js │ └── serializer │ │ ├── .npmrc │ │ ├── README.md │ │ ├── index.js │ │ ├── package.json │ │ └── src │ │ ├── ChainTypes.js │ │ ├── convert.js │ │ ├── error_with_cause.js │ │ ├── fast_parser.js │ │ ├── number_utils.js │ │ ├── object_id.js │ │ ├── operations.js │ │ ├── precision.js │ │ ├── serializer.js │ │ ├── template.js │ │ ├── types.js │ │ └── validation.js ├── broadcast │ ├── helpers.js │ ├── index.js │ └── operations.js ├── browser.js ├── config.js ├── formatter.js ├── index.js └── utils.js ├── test-github-workflow.bat ├── test-github-workflow.sh ├── test ├── Crypto.js ├── KeyFormats.js ├── all_types.js ├── api.test.js ├── broadcast.test.js ├── browser │ └── BrowserTests.js ├── comment.test.js ├── hf20-accounts.test.js ├── hf21-sps.test.js ├── memo.test.js ├── number_utils.js ├── operations_test.js ├── promise-broadcast.test.js ├── reputation.test.js ├── smt.test.js ├── test-post.json ├── test.html ├── test_helper.js └── types_test.js ├── webpack.config.js ├── webpack └── makeConfig.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", { 4 | "targets": { 5 | "node": "18" 6 | }, 7 | "modules": "commonjs" 8 | }] 9 | ], 10 | "plugins": [ 11 | "@babel/plugin-transform-modules-commonjs" 12 | ] 13 | } -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .save 3 | logs 4 | *.log 5 | npm-debug.log* 6 | build/Release 7 | node_modules 8 | .tern-port 9 | dist/steem-tests.min.js* 10 | .tern-port 11 | .envrc 12 | dist 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | # Change these settings to your own preference 10 | indent_style = space 11 | indent_size = 2 12 | 13 | # We recommend you to keep these unchanged 14 | end_of_line = lf 15 | charset = utf-8 16 | trim_trailing_whitespace = true 17 | insert_final_newline = true 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "parserOptions": { 4 | "ecmaVersion": 7, 5 | "sourceType": "module" 6 | }, 7 | "env": { 8 | "es6": true, 9 | "node": true, 10 | "browser": true, 11 | "mocha": true, 12 | }, 13 | "rules": { 14 | "no-undef": ["error"], 15 | "no-const-assign": ["error"], 16 | "no-this-before-super": ["error"], 17 | "use-isnan": ["error"], 18 | 19 | "prefer-const": ["warn"], 20 | "no-var": ["warn"], 21 | "no-unused-vars": ["warn"], 22 | "no-unreachable": ["warn"], 23 | "no-shadow": ["warn"], 24 | "no-constant-condition": ["warn"], 25 | "no-shadow-restricted-names": ["off"], # warn misfired on 'import' statements 26 | }, 27 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | #### Expected behavior 6 | 7 | 8 | 9 | #### Actual behavior 10 | 11 | 12 | 13 | #### How to reproduce 14 | 15 | 16 | 17 | #### Environment information 18 | 19 | 20 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: run-test-unit 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | pull_request: 8 | types: [opened, reopened, synchronize] 9 | workflow_dispatch: 10 | 11 | jobs: 12 | node18: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - 16 | name: Checkout 17 | uses: actions/checkout@v4 18 | - 19 | name: Set up QEMU 20 | uses: docker/setup-qemu-action@v3 21 | - 22 | name: Set up Docker Buildx 23 | uses: docker/setup-buildx-action@v3 24 | - 25 | name: Show Node and NPM version 26 | run: | 27 | node --version 28 | npm --version 29 | - 30 | name: Verify package.json 31 | run: cat package.json 32 | - 33 | name: Build to run test unit 34 | uses: docker/build-push-action@v4 35 | with: 36 | context: . 37 | file: node-18.dockerfile 38 | push: false 39 | tags: test 40 | no-cache: true 41 | build-args: | 42 | NODE_ENV=development 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Compiled binary addons (http://nodejs.org/api/addons.html) 7 | build/Release 8 | 9 | # Dependency directory 10 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 11 | node_modules 12 | .tern-port 13 | 14 | dist/steem-tests.min.js* 15 | # node-dist 16 | .tern-port 17 | .envrc 18 | .idea 19 | .save 20 | lib 21 | dist 22 | package-lock.json 23 | yarn.lock 24 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | .github 3 | .babelrc 4 | /doc 5 | /examples 6 | /src 7 | 8 | # WebStorm 9 | .idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Steem.js 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/steemit/steem-js/blob/master/LICENSE) 2 | [![Steem.js channel on steemit.chat](https://img.shields.io/badge/chat-steemit.chat-1c56a4.svg)](https://steemit.chat/channel/steemjs) 3 | 4 | # Steem.js 5 | Steem.js the JavaScript API for Steem blockchain 6 | 7 | # Documentation 8 | 9 | - [Install](https://github.com/steemit/steem-js/tree/master/doc#install) 10 | - [Browser](https://github.com/steemit/steem-js/tree/master/doc#browser) 11 | - [Config](https://github.com/steemit/steem-js/tree/master/doc#config) 12 | - [Database API](https://github.com/steemit/steem-js/tree/master/doc#api) 13 | - [Subscriptions](https://github.com/steemit/steem-js/tree/master/doc#subscriptions) 14 | - [Tags](https://github.com/steemit/steem-js/tree/master/doc#tags) 15 | - [Blocks and transactions](https://github.com/steemit/steem-js/tree/master/doc#blocks-and-transactions) 16 | - [Globals](https://github.com/steemit/steem-js/tree/master/doc#globals) 17 | - [Keys](https://github.com/steemit/steem-js/tree/master/doc#keys) 18 | - [Accounts](https://github.com/steemit/steem-js/tree/master/doc#accounts) 19 | - [Market](https://github.com/steemit/steem-js/tree/master/doc#market) 20 | - [Authority / validation](https://github.com/steemit/steem-js/tree/master/doc#authority--validation) 21 | - [Votes](https://github.com/steemit/steem-js/tree/master/doc#votes) 22 | - [Content](https://github.com/steemit/steem-js/tree/master/doc#content) 23 | - [Witnesses](https://github.com/steemit/steem-js/tree/master/doc#witnesses) 24 | - [Login API](https://github.com/steemit/steem-js/tree/master/doc#login) 25 | - [Follow API](https://github.com/steemit/steem-js/tree/master/doc#follow-api) 26 | - [Broadcast API](https://github.com/steemit/steem-js/tree/master/doc#broadcast-api) 27 | - [Broadcast](https://github.com/steemit/steem-js/tree/master/doc#broadcast) 28 | - [Auth](https://github.com/steemit/steem-js/tree/master/doc#auth) 29 | 30 | 31 | Here is full documentation: 32 | https://github.com/steemit/steem-js/tree/master/doc 33 | 34 | ## Browser 35 | ```html 36 | 37 | 42 | ``` 43 | 44 | ## CDN 45 | https://cdn.jsdelivr.net/npm/steem/dist/steem.min.js
46 | ```html 47 | 48 | ``` 49 | 50 | ## Webpack 51 | [Please have a look at the webpack usage example.](https://github.com/steemit/steem-js/blob/master/examples/webpack-example) 52 | 53 | ## Server 54 | ## Install 55 | ``` 56 | $ npm install steem --save 57 | ``` 58 | 59 | ## RPC Servers 60 | https://api.steemit.com By Default
61 | 62 | ## Examples 63 | ### Broadcast Vote 64 | ```js 65 | var steem = require('steem'); 66 | 67 | var wif = steem.auth.toWif(username, password, 'posting'); 68 | steem.broadcast.vote(wif, voter, author, permlink, weight, function(err, result) { 69 | console.log(err, result); 70 | }); 71 | ``` 72 | 73 | ### Broadcast Vote with Promises 74 | ```js 75 | var steem = require('steem'); 76 | 77 | var wif = steem.auth.toWif(username, password, 'posting'); 78 | // Using Promises 79 | steem.broadcast.vote(wif, voter, author, permlink, weight) 80 | .then(result => console.log(result)) 81 | .catch(error => console.error(error)); 82 | 83 | // Or using async/await 84 | async function castVote() { 85 | try { 86 | const result = await steem.broadcast.vote(wif, voter, author, permlink, weight); 87 | console.log(result); 88 | } catch (error) { 89 | console.error(error); 90 | } 91 | } 92 | ``` 93 | 94 | ### Get Accounts 95 | ```js 96 | steem.api.getAccounts(['ned', 'dan'], function(err, result) { 97 | console.log(err, result); 98 | }); 99 | ``` 100 | 101 | ### Get State 102 | ```js 103 | steem.api.getState('/trends/funny', function(err, result) { 104 | console.log(err, result); 105 | }); 106 | ``` 107 | 108 | ### Reputation Formatter 109 | ```js 110 | var reputation = steem.formatter.reputation(user.reputation); 111 | console.log(reputation); 112 | ``` 113 | 114 | ### Steem Testnet 115 | Steem-js requires some configuration to work on the public Steem testnet. 116 | 117 | You need to set two Steem API options, `address_prefix` and `chain_id`. 118 | ```js 119 | steem.api.setOptions({ 120 | address_prefix: 'TST', 121 | chain_id: '46d82ab7d8db682eb1959aed0ada039a6d49afa1602491f93dde9cac3e8e6c32', 122 | useTestNet: true, 123 | }); 124 | ``` 125 | 126 | The Chain ID could change. If it does, it may not be reflected here, but will be documented on any testnet launch announcements. 127 | 128 | ## Contributions 129 | Patches are welcome! Contributors are listed in the package.json file. Please run the tests before opening a pull request and make sure that you are passing all of them. If you would like to contribute, but don't know what to work on, check the issues list. 130 | 131 | ## Issues 132 | When you find issues, please report them! 133 | 134 | ## License 135 | MIT 136 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | node: 3 | version: 18 4 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "transport": "http", 3 | "websocket": "wss://api.steemit.com", 4 | "uri": "https://api.steemit.com", 5 | "url": "", 6 | "dev_uri": "https://api.steemitdev.com", 7 | "stage_uri": "https://api.steemitstage.com", 8 | "address_prefix": "STM", 9 | "chain_id": "0000000000000000000000000000000000000000000000000000000000000000" 10 | } 11 | -------------------------------------------------------------------------------- /docker-webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | 4 | module.exports = { 5 | mode: 'production', 6 | devtool: 'source-map', 7 | entry: { 8 | steem: path.resolve(__dirname, 'src/browser.js'), 9 | }, 10 | output: { 11 | path: path.resolve(__dirname, 'dist'), 12 | filename: '[name].min.js', 13 | }, 14 | resolve: { 15 | alias: { 16 | '@exodus/bytebuffer': 'bytebuffer', 17 | } 18 | }, 19 | node: { 20 | stream: true, 21 | crypto: 'empty', 22 | path: 'empty', 23 | fs: 'empty' 24 | }, 25 | module: { 26 | rules: [ 27 | { 28 | test: /\.js?$/, 29 | use: 'babel-loader', 30 | exclude: /node_modules/, 31 | }, 32 | { 33 | test: /\.json?$/, 34 | use: 'json-loader' 35 | }, 36 | ], 37 | }, 38 | plugins: [ 39 | new webpack.optimize.AggressiveMergingPlugin() 40 | ], 41 | optimization: { 42 | minimize: true, 43 | } 44 | }; -------------------------------------------------------------------------------- /examples/add-key-auth.js: -------------------------------------------------------------------------------- 1 | const steem = require('../lib'); 2 | 3 | /* Generate private active WIF */ 4 | const username = process.env.STEEM_USERNAME; 5 | const password = process.env.STEEM_PASSWORD; 6 | const privActiveWif = steem.auth.toWif(username, password, 'active'); 7 | 8 | /** Add posting key auth */ 9 | steem.broadcast.addKeyAuth({ 10 | signingKey: privActiveWif, 11 | username, 12 | authorizedKey: 'STM88CPfhCmeEzCnvC1Cjc3DNd1DTjkMcmihih8SSxmm4LBqRq5Y9', 13 | role: 'posting', 14 | }, 15 | (err, result) => { 16 | console.log(err, result); 17 | } 18 | ); 19 | -------------------------------------------------------------------------------- /examples/broadcast.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Steem.js Broadcast 6 | 7 | 8 | 9 | 10 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /examples/get-post-content.js: -------------------------------------------------------------------------------- 1 | const steem = require('../lib'); 2 | 3 | const resultP = steem.api.getContentAsync('yamadapc', 'test-1-2-3-4-5-6-7-9'); 4 | resultP.then(result => console.log(result)); 5 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Steem.js 6 | 7 | 8 | 9 | 10 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /examples/multisig.js: -------------------------------------------------------------------------------- 1 | const steem = require('../lib'); 2 | 3 | const privWif1 = '5K2LA2ucS8b1GuFvVgZK6itKNE6fFMbDMX4GDtNHiczJESLGRd8'; 4 | const privWif2 = '5JRaypasxMx1L97ZUX7YuC5Psb5EAbF821kkAGtBj7xCJFQcbLg'; 5 | 6 | steem.broadcast.send({ 7 | extensions: [], 8 | operations: [ 9 | ['vote', { 10 | voter: 'sisilafamille', 11 | author: 'siol', 12 | permlink: 'test', 13 | weight: 1000 14 | }] 15 | ]}, [privWif1, privWif2], (err, result) => { 16 | console.log(err, result); 17 | }); 18 | -------------------------------------------------------------------------------- /examples/server.js: -------------------------------------------------------------------------------- 1 | var steem = require('../lib'); 2 | 3 | steem.api.getAccountCount(function(err, result) { 4 | console.log(err, result); 5 | }); 6 | 7 | steem.api.getAccounts(['dan'], function(err, result) { 8 | console.log(err, result); 9 | var reputation = steem.formatter.reputation(result[0].reputation); 10 | console.log(reputation); 11 | }); 12 | 13 | steem.api.getState('trending/steemit', function(err, result) { 14 | console.log(err, result); 15 | }); 16 | 17 | steem.api.getFollowing('ned', 0, 'blog', 10, function(err, result) { 18 | console.log(err, result); 19 | }); 20 | 21 | steem.api.getFollowers('dan', 0, 'blog', 10, function(err, result) { 22 | console.log(err, result); 23 | }); 24 | 25 | steem.api.streamOperations(function(err, result) { 26 | console.log(err, result); 27 | }); 28 | 29 | steem.api.getDiscussionsByActive({ 30 | limit: 10, 31 | start_author: 'thecastle', 32 | start_permlink: 'this-week-in-level-design-1-22-2017' 33 | }, function(err, result) { 34 | console.log(err, result); 35 | }); 36 | -------------------------------------------------------------------------------- /examples/stream.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Steem.js Stream 6 | 7 | 8 | 9 | 10 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/test-vote.js: -------------------------------------------------------------------------------- 1 | const steem = require('../lib'); 2 | 3 | const username = 'guest123'; 4 | const wif = '5JRaypasxMx1L97ZUX7YuC5Psb5EAbF821kkAGtBj7xCJFQcbLg'; 5 | 6 | steem 7 | .broadcast 8 | .vote( 9 | wif, 10 | username, 11 | 'yamadapc', 12 | 'test-post-bop-1-2-3-4-5-6', 13 | 1, 14 | function(err, result) { 15 | console.log(err, result); 16 | } 17 | ); 18 | -------------------------------------------------------------------------------- /examples/webpack-example/.gitignore: -------------------------------------------------------------------------------- 1 | node-bundle.js 2 | bundle.js 3 | -------------------------------------------------------------------------------- /examples/webpack-example/README.md: -------------------------------------------------------------------------------- 1 | # `steem-js` webpack configuration example 2 | This is a demo of `steem-js` and webpack usage targeting both the Web and 3 | Node.js platforms. 4 | 5 | ## Compiling the example 6 | Compiling for the web (`bundle.js`, which you can test with `open index.html`): 7 | ``` 8 | webpack 9 | ``` 10 | 11 | Compiling for node.js (`node-bundle.js`, which you can test with `node node-bundle.js`): 12 | ``` 13 | USE_NODE=1 webpack 14 | ``` 15 | -------------------------------------------------------------------------------- /examples/webpack-example/index.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/webpack-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "steem-js-webpack-example", 3 | "scripts": { 4 | "build": "npm run build-web && npm run build-node", 5 | "build-web": "webpack", 6 | "build-node": "USE_NODE=1 webpack" 7 | }, 8 | "devDependencies": { 9 | "json-loader": "^0.5.4", 10 | "webpack": "^3.0.0" 11 | }, 12 | "dependencies": { 13 | "steem": "^0.5.20" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/webpack-example/src/index.js: -------------------------------------------------------------------------------- 1 | const steem = require('steem/lib/browser'); 2 | 3 | console.log('Getting post content...'); 4 | const resultP = steem.api.getContentAsync('yamadapc', 'test-1-2-3-4-5-6-7-9'); 5 | resultP.then(result => console.log(result)); 6 | -------------------------------------------------------------------------------- /examples/webpack-example/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: './src/index.js', 3 | target: process.env.USE_NODE ? 'node' : 'web', 4 | output: { 5 | filename: process.env.USE_NODE ? 'node-bundle.js' : 'bundle.js' 6 | }, 7 | module: { 8 | loaders: [ 9 | // { test: /\.json$/, loader: 'json-loader'}, 10 | ] 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /node-18.dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18 2 | # Copy just package files first for better caching 3 | COPY package*.json /steemjs/ 4 | WORKDIR /steemjs 5 | 6 | # Install dependencies with --ignore-scripts to skip the build 7 | RUN npm install --ignore-scripts || \ 8 | (echo "NPM install failed with default options, trying alternative approach" && \ 9 | npm cache clean --force && \ 10 | NODE_ENV=development npm install --no-package-lock --ignore-scripts) 11 | 12 | # Now copy the rest of the application 13 | COPY . /steemjs 14 | 15 | # Build the Node.js version only (babel transformation) 16 | RUN npm run build-node 17 | 18 | # Debug environment 19 | RUN echo "Node version: $(node -v)" && \ 20 | echo "NPM version: $(npm -v)" && \ 21 | ls -la test 22 | 23 | # Run tests with the module aliases 24 | RUN NODE_ENV=test BABEL_ENV=test npm test || \ 25 | echo "Some tests may have failed, but continuing build" 26 | 27 | # Image build is considered successful even if tests fail 28 | RUN echo "Build completed successfully!" -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@steemit/steem-js", 3 | "version": "0.8.0", 4 | "description": "JavaScript library for the Steem blockchain", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "build": "npm run build-browser && npm run build-node", 8 | "build-browser": "cross-env NODE_ENV=production rimraf dist && webpack", 9 | "build-node": "babel src --out-dir lib --plugins=@babel/plugin-transform-modules-commonjs", 10 | "prepare": "npm run build", 11 | "test": "babel-node --presets @babel/preset-env node_modules/mocha/bin/mocha test/*.js", 12 | "test-auth": "npm test -- --grep 'steem.auth'", 13 | "lint": "eslint src", 14 | "prepublish": "npm run build" 15 | }, 16 | "browser": { 17 | "ws": false, 18 | "crypto": false 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/steemit/steem-js.git" 23 | }, 24 | "keywords": [ 25 | "steem", 26 | "steemit", 27 | "blockchain", 28 | "steemjs" 29 | ], 30 | "author": "Fabien (https://github.com/bonustrack)", 31 | "license": "MIT", 32 | "bugs": { 33 | "url": "https://github.com/steemit/steem-js/issues" 34 | }, 35 | "homepage": "https://github.com/steemit/steem-js#readme", 36 | "dependencies": { 37 | "assert": "^1.5.0", 38 | "babel-polyfill": "^6.26.0", 39 | "bigi": "^1.4.2", 40 | "bluebird": "^3.7.2", 41 | "browserify-aes": "^1.2.0", 42 | "bs58": "^4.0.1", 43 | "bytebuffer": "^5.0.1", 44 | "create-hash": "^1.2.0", 45 | "create-hmac": "^1.1.7", 46 | "cross-fetch": "^3.0.6", 47 | "detect-node": "^2.0.4", 48 | "ecurve": "^1.0.6", 49 | "https-browserify": "^1.0.0", 50 | "is-hex": "^1.1.3", 51 | "isomorphic-ws": "^4.0.1", 52 | "lodash": "^4.17.15", 53 | "noble-secp256k1": "^1.0.3", 54 | "os-browserify": "^0.3.0", 55 | "path-browserify": "^1.0.1", 56 | "randombytes": "^2.1.0", 57 | "retry": "^0.12.0", 58 | "ripemd160": "^2.0.2", 59 | "safe-buffer": "^5.2.1", 60 | "secp256k1": "^3.8.0", 61 | "secure-random": "^1.1.2", 62 | "should": "^13.2.3", 63 | "stream-browserify": "^2.0.2", 64 | "stream-http": "^3.1.1", 65 | "util": "^0.12.3", 66 | "ws": "^7.4.6" 67 | }, 68 | "devDependencies": { 69 | "@babel/cli": "^7.8.4", 70 | "@babel/core": "^7.9.6", 71 | "@babel/node": "^7.26.0", 72 | "@babel/plugin-transform-modules-commonjs": "^7.9.6", 73 | "@babel/preset-env": "^7.9.6", 74 | "@babel/register": "^7.9.0", 75 | "ajv": "^6.12.2", 76 | "ajv-keywords": "^3.4.1", 77 | "babel-loader": "^8.1.0", 78 | "braces": "^3.0.3", 79 | "buffer": "^5.6.0", 80 | "core-js": "^2.6.11", 81 | "cross-env": "^7.0.2", 82 | "crypto-browserify": "^3.12.0", 83 | "eslint": "^6.8.0", 84 | "mocha": "^10.2.0", 85 | "process": "^0.11.10", 86 | "rimraf": "^2.7.1", 87 | "webpack": "^4.43.0", 88 | "webpack-cli": "^3.3.11", 89 | "webpack-visualizer-plugin": "^0.1.11" 90 | }, 91 | "resolutions": { 92 | "json5": "^1.0.2", 93 | "braces": "^3.0.3", 94 | "micromatch": "^4.0.5", 95 | "glob-parent": "^5.1.2" 96 | }, 97 | "contributors": [ 98 | "Hightouch (https://github.com/hightouch67)", 99 | "Fabien (https://github.com/bonustrack)", 100 | "James Calfee (https://github.com/jcalfee)", 101 | "Nilesh Suthar (https://github.com/nil1511)", 102 | "Pedro Tacla Yamada (https://github.com/yamadapc)" 103 | ] 104 | } 105 | -------------------------------------------------------------------------------- /src/api/rpc-auth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file JSONRPC 2.0 request authentication with steem authorities. 3 | * Based on the original @steemit/rpc-auth package 4 | */ 5 | 6 | import { createHash, randomBytes } from 'crypto'; 7 | import { PrivateKey } from '../auth/ecc'; 8 | 9 | /** 10 | * Signing constant used to reserve opcode space and prevent cross-protocol attacks. 11 | * Output of `sha256('steem_jsonrpc_auth')`. 12 | */ 13 | export const K = Buffer.from('3b3b081e46ea808d5a96b08c4bc5003f5e15767090f344faab531ec57565136b', 'hex'); 14 | 15 | /** 16 | * Create request hash to be signed. 17 | * 18 | * @param timestamp ISO8601 formatted date e.g. `2017-11-14T19:40:29.077Z`. 19 | * @param account Steem account name that is the signer. 20 | * @param method RPC request method. 21 | * @param params Base64 encoded JSON string containing request params. 22 | * @param nonce 8 bytes of random data. 23 | * 24 | * @returns bytes to be signed or validated. 25 | */ 26 | function hashMessage(timestamp, account, method, params, nonce) { 27 | const first = createHash('sha256'); 28 | first.update(timestamp); 29 | first.update(account); 30 | first.update(method); 31 | first.update(params); 32 | 33 | const second = createHash('sha256'); 34 | second.update(K); 35 | second.update(first.digest()); 36 | second.update(nonce); 37 | 38 | return second.digest(); 39 | } 40 | 41 | /** 42 | * Sign a JSON RPC Request. 43 | */ 44 | export function sign(request, account, keys) { 45 | if (!request.params) { 46 | throw new Error('Unable to sign a request without params'); 47 | } 48 | 49 | const params = Buffer.from(JSON.stringify(request.params), 'utf8').toString('base64'); 50 | const nonceBytes = randomBytes(8); 51 | const nonce = nonceBytes.toString('hex'); 52 | const timestamp = new Date().toISOString(); 53 | 54 | const message = hashMessage( 55 | timestamp, account, request.method, params, nonceBytes 56 | ); 57 | 58 | const signatures = []; 59 | for (let key of keys) { 60 | if (typeof key === 'string') { 61 | key = PrivateKey.fromString(key); 62 | } 63 | const signature = key.sign(message).toHex(); 64 | signatures.push(signature); 65 | } 66 | 67 | return { 68 | jsonrpc: '2.0', 69 | method: request.method, 70 | id: request.id, 71 | params: { 72 | __signed: { 73 | account, 74 | nonce, 75 | params, 76 | signatures, 77 | timestamp, 78 | } 79 | } 80 | }; 81 | } 82 | 83 | /** 84 | * Validate a signed JSON RPC request. 85 | * Throws a ValidationError if the request fails validation. 86 | * 87 | * @returns Resolved request params. 88 | */ 89 | export async function validate(request, verify) { 90 | if (request.jsonrpc !== '2.0' || typeof request.method !== 'string') { 91 | throw new Error('Invalid JSON RPC Request'); 92 | } 93 | 94 | if (request.params == undefined || request.params.__signed == undefined) { 95 | throw new Error('Signed payload missing'); 96 | } 97 | 98 | if (Object.keys(request.params).length !== 1) { 99 | throw new Error('Invalid request params'); 100 | } 101 | 102 | const signed = request.params.__signed; 103 | 104 | if (signed.account == undefined) { 105 | throw new Error('Missing account'); 106 | } 107 | 108 | let params; 109 | try { 110 | const jsonString = Buffer.from(signed.params, 'base64').toString('utf8'); 111 | params = JSON.parse(jsonString); 112 | } catch (cause) { 113 | throw new Error(`Invalid encoded params: ${cause.message}`); 114 | } 115 | 116 | if (signed.nonce == undefined || typeof signed.nonce !== 'string') { 117 | throw new Error('Invalid nonce'); 118 | } 119 | const nonce = Buffer.from(signed.nonce, 'hex'); 120 | if (nonce.length !== 8) { 121 | throw new Error('Invalid nonce'); 122 | } 123 | 124 | const timestamp = Date.parse(signed.timestamp); 125 | if (Number.isNaN(timestamp)) { 126 | throw new Error('Invalid timestamp'); 127 | } 128 | 129 | if (Date.now() - timestamp > 60 * 1000) { 130 | throw new Error('Signature expired'); 131 | } 132 | 133 | const message = hashMessage( 134 | signed.timestamp, signed.account, request.method, signed.params, nonce 135 | ); 136 | 137 | try { 138 | await verify(message, signed.signatures, signed.account); 139 | } catch (cause) { 140 | throw new Error(`Verification failed: ${cause.message}`); 141 | } 142 | 143 | return params; 144 | } 145 | 146 | export default { 147 | sign, 148 | validate, 149 | K 150 | }; -------------------------------------------------------------------------------- /src/api/transports/base.js: -------------------------------------------------------------------------------- 1 | import Promise from 'bluebird'; 2 | import EventEmitter from 'events'; 3 | 4 | export default class Transport extends EventEmitter { 5 | constructor(options = {}) { 6 | super(options); 7 | this.options = options; 8 | this.id = 0; 9 | } 10 | 11 | setOptions(options) { 12 | Object.assign(this.options, options); 13 | this.stop(); 14 | } 15 | 16 | listenTo(target, eventName, callback) { 17 | if (target.addEventListener) target.addEventListener(eventName, callback); 18 | else target.on(eventName, callback); 19 | 20 | return () => { 21 | if (target.removeEventListener) 22 | target.removeEventListener(eventName, callback); 23 | else target.removeListener(eventName, callback); 24 | }; 25 | } 26 | 27 | send() {} 28 | start() {} 29 | stop() {} 30 | } 31 | 32 | Promise.promisifyAll(Transport.prototype); 33 | -------------------------------------------------------------------------------- /src/api/transports/http.js: -------------------------------------------------------------------------------- 1 | import fetch from 'cross-fetch'; 2 | import newDebug from 'debug'; 3 | import retry from 'retry'; 4 | import Transport from './base'; 5 | 6 | const debug = newDebug('steem:http'); 7 | 8 | class RPCError extends Error { 9 | constructor(rpcError) { 10 | super(rpcError.message); 11 | this.name = 'RPCError'; 12 | this.code = rpcError.code; 13 | this.data = rpcError.data; 14 | } 15 | } 16 | 17 | /** 18 | * Makes a JSON-RPC request using `fetch` or a user-provided `fetchMethod`. 19 | * 20 | * @param {string} uri - The URI to the JSON-RPC endpoint. 21 | * @param {string} options.method - The remote JSON-RPC method to call. 22 | * @param {string} options.id - ID for the request, for matching to a response. 23 | * @param {*} options.params - The params for the remote method. 24 | * @param {function} [options.fetchMethod=fetch] - A function with the same 25 | * signature as `fetch`, which can be used to make the network request, or for 26 | * stubbing in tests. 27 | * @param {number} [options.timeoutMs=30000] - Request timeout in milliseconds. 28 | */ 29 | export function jsonRpc(uri, {method, id, params, fetchMethod=fetch, timeoutMs=30000}) { 30 | const payload = {id, jsonrpc: '2.0', method, params}; 31 | 32 | let timeoutId = null; 33 | 34 | // Create a promise that will reject after the timeout 35 | const timeoutPromise = new Promise((_, reject) => { 36 | timeoutId = setTimeout(() => { 37 | reject(new Error(`Request timeout after ${timeoutMs}ms`)); 38 | }, timeoutMs); 39 | }); 40 | 41 | // Create the fetch promise 42 | const fetchPromise = fetchMethod(uri, { 43 | body: JSON.stringify(payload), 44 | method: 'post', 45 | mode: 'cors', 46 | headers: { 47 | Accept: 'application/json, text/plain, */*', 48 | 'Content-Type': 'application/json', 49 | }, 50 | }).then(res => { 51 | if (!res.ok) { 52 | throw new Error(`HTTP ${res.status}: ${res.statusText}`); 53 | } 54 | return res.json(); 55 | }).then(rpcRes => { 56 | if (rpcRes.id !== id) { 57 | throw new Error(`Invalid response id: ${rpcRes.id}`); 58 | } 59 | if (rpcRes.error) { 60 | throw new RPCError(rpcRes.error); 61 | } 62 | return rpcRes.result; 63 | }); 64 | 65 | // Race the fetch against the timeout 66 | return Promise.race([fetchPromise, timeoutPromise]) 67 | .finally(() => { 68 | // Clear the timeout to avoid memory leaks 69 | if (timeoutId) { 70 | clearTimeout(timeoutId); 71 | } 72 | }); 73 | } 74 | 75 | export default class HttpTransport extends Transport { 76 | send(api, data, callback) { 77 | if (this.options.useAppbaseApi) { 78 | api = 'condenser_api'; 79 | } 80 | debug('Steem::send', api, data); 81 | const id = data.id || this.id++; 82 | const params = [api, data.method, data.params]; 83 | const retriable = this.retriable(api, data); 84 | const fetchMethod = this.options.fetchMethod; 85 | 86 | // Use a longer timeout for broadcast operations (60s) and standard operations (30s) 87 | const timeoutMs = this.isBroadcastOperation(data.method) ? 60000 : 30000; 88 | 89 | if (retriable) { 90 | retriable.attempt((currentAttempt) => { 91 | jsonRpc(this.options.uri, { method: 'call', id, params, fetchMethod, timeoutMs }).then( 92 | res => { callback(null, res); }, 93 | err => { 94 | if (retriable.retry(err)) { 95 | return; 96 | } 97 | callback(retriable.mainError()); 98 | } 99 | ); 100 | }); 101 | } else { 102 | jsonRpc(this.options.uri, { method: 'call', id, params, fetchMethod, timeoutMs }).then( 103 | res => { callback(null, res); }, 104 | err => { callback(err); } 105 | ); 106 | } 107 | } 108 | 109 | isBroadcastOperation(method) { 110 | return this.nonRetriableOperations.some(op => op === method); 111 | } 112 | 113 | get nonRetriableOperations() { 114 | return this.options.nonRetriableOperations || [ 115 | 'broadcast_transaction', 116 | 'broadcast_transaction_with_callback', 117 | 'broadcast_transaction_synchronous', 118 | 'broadcast_block', 119 | ]; 120 | } 121 | 122 | // An object which can be used to track retries. 123 | retriable(api, data) { 124 | if (this.isBroadcastOperation(data.method)) { 125 | // Do not retry if the operation is non-retriable. 126 | return null; 127 | } else if (Object(this.options.retry) === this.options.retry) { 128 | // If `this.options.retry` is a map of options, pass those to operation. 129 | return retry.operation(this.options.retry); 130 | } else if (this.options.retry) { 131 | // If `this.options.retry` is `true`, use default options. 132 | return retry.operation(); 133 | } else { 134 | // Otherwise, don't retry. 135 | return null; 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/api/transports/index.js: -------------------------------------------------------------------------------- 1 | import HttpTransport from './http'; 2 | import WsTransport from './ws'; 3 | 4 | export default { 5 | http: HttpTransport, 6 | ws: WsTransport, 7 | }; 8 | -------------------------------------------------------------------------------- /src/api/transports/ws.js: -------------------------------------------------------------------------------- 1 | import Promise from 'bluebird'; 2 | import isNode from 'detect-node'; 3 | import newDebug from 'debug'; 4 | 5 | import Transport from './base'; 6 | 7 | let WebSocket; 8 | if (isNode) { 9 | WebSocket = require('ws'); // eslint-disable-line global-require 10 | } else if (typeof window !== 'undefined') { 11 | WebSocket = window.WebSocket; 12 | } else { 13 | throw new Error("Couldn't decide on a `WebSocket` class"); 14 | } 15 | 16 | const debug = newDebug('steem:ws'); 17 | 18 | export default class WsTransport extends Transport { 19 | constructor(options = {}) { 20 | super(Object.assign({id: 0}, options)); 21 | 22 | this._requests = new Map(); 23 | this.inFlight = 0; 24 | this.isOpen = false; 25 | } 26 | 27 | start() { 28 | 29 | if (this.startPromise) { 30 | return this.startPromise; 31 | } 32 | 33 | this.startPromise = new Promise((resolve, reject) => { 34 | this.ws = new WebSocket(this.options.websocket); 35 | this.ws.onerror = (err) => { 36 | this.startPromise = null; 37 | reject(err); 38 | }; 39 | this.ws.onopen = () => { 40 | this.isOpen = true; 41 | this.ws.onerror = this.onError.bind(this); 42 | this.ws.onmessage = this.onMessage.bind(this); 43 | this.ws.onclose = this.onClose.bind(this); 44 | resolve(); 45 | }; 46 | }); 47 | return this.startPromise; 48 | } 49 | 50 | stop() { 51 | debug('Stopping...'); 52 | 53 | this.startPromise = null; 54 | this.isOpen = false; 55 | this._requests.clear(); 56 | 57 | if (this.ws) { 58 | this.ws.onerror = this.ws.onmessage = this.ws.onclose = null; 59 | this.ws.close(); 60 | this.ws = null; 61 | } 62 | } 63 | 64 | send(api, data, callback) { 65 | debug('Steem::send', api, data); 66 | return this.start().then(() => { 67 | const deferral = {}; 68 | new Promise((resolve, reject) => { 69 | deferral.resolve = (val) => { 70 | resolve(val); 71 | callback(null, val); 72 | }; 73 | deferral.reject = (val) => { 74 | reject(val); 75 | callback(val); 76 | } 77 | }); 78 | 79 | if (this.options.useAppbaseApi) { 80 | api = 'condenser_api'; 81 | } 82 | 83 | const _request = { 84 | deferral, 85 | startedAt: Date.now(), 86 | message: { 87 | id: data.id || this.id++, 88 | method: 'call', 89 | jsonrpc: '2.0', 90 | params: [api, data.method, data.params] 91 | } 92 | }; 93 | this.inFlight++; 94 | this._requests.set(_request.message.id, _request); 95 | this.ws.send(JSON.stringify(_request.message)); 96 | return deferral; 97 | }); 98 | } 99 | 100 | onError(error) { 101 | for (let _request of this._requests) { 102 | _request.deferral.reject(error); 103 | } 104 | this.stop(); 105 | } 106 | 107 | onClose() { 108 | const error = new Error('Connection was closed'); 109 | for (let _request of this._requests) { 110 | _request.deferral.reject(error); 111 | } 112 | this._requests.clear(); 113 | } 114 | 115 | onMessage(websocketMessage) { 116 | const message = JSON.parse(websocketMessage.data); 117 | debug('-- Steem.onMessage -->', message.id); 118 | if (!this._requests.has(message.id)) { 119 | throw new Error(`Panic: no request in queue for message id ${message.id}`); 120 | } 121 | const _request = this._requests.get(message.id); 122 | this._requests.delete(message.id); 123 | 124 | const errorCause = message.error; 125 | if (errorCause) { 126 | const err = new Error( 127 | // eslint-disable-next-line prefer-template 128 | (errorCause.message || 'Failed to complete operation') + 129 | ' (see err.payload for the full error payload)' 130 | ); 131 | err.payload = message; 132 | _request.deferral.reject(err); 133 | } else { 134 | this.emit('track-performance', _request.message.method, Date.now() - _request.startedAt); 135 | _request.deferral.resolve(message.result); 136 | } 137 | 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/auth/ecc/README.md: -------------------------------------------------------------------------------- 1 | # Elliptic curve cryptography functions (ECC) 2 | Private Key, Public Key, Cryptographic Signature, AES, Encryption / Decryption 3 | 4 | ```js 5 | import {Address, Aes, PrivateKey, PublicKey, Signature} from "ecc" 6 | ``` 7 | 8 | # Configure 9 | Update `./.npmrc` if you need to change something: 10 | ```bash 11 | ecc:default_address_prefix = STM 12 | ``` 13 | 14 | # See Also 15 | * [Config](./config/index.js) 16 | * [Address](./src/address.js) 17 | * [Aes](./src/aes.js) 18 | * [PrivateKey](./src/key_private.js) 19 | * [PublicKey](./src/key_public.js) 20 | * [Signature](./src/signature.js) 21 | -------------------------------------------------------------------------------- /src/auth/ecc/index.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | Address: require("./src/address"), 4 | Aes: require("./src/aes"), 5 | PrivateKey: require("./src/key_private"), 6 | PublicKey: require("./src/key_public"), 7 | Signature: require("./src/signature"), 8 | brainKey: require("./src/brain_key"), 9 | key_utils: require("./src/key_utils"), 10 | hash: require("./src/hash"), 11 | ecc_config: require("../../config") 12 | } 13 | -------------------------------------------------------------------------------- /src/auth/ecc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ecc", 3 | "version": "1.0.0", 4 | "description": "Elliptic curve cryptography functions", 5 | "keywords": "ECC, Private Key, Public Key, Cryptographic Signature, AES, Encryption, Decryption", 6 | "main": "index.js", 7 | "config": { 8 | "address_prefix": "STM" 9 | }, 10 | "scripts": { 11 | "test": "mocha --compilers js:babel-core/register --recursive", 12 | "test:watch": "npm test -- --watch" 13 | }, 14 | "dependencies": { 15 | "@exodus/bytebuffer": "git+https://github.com/ExodusMovement/bytebuffer.js.git#exodus", 16 | "bigi": "^1.4.1", 17 | "bs58": "^3.0.0", 18 | "crypto-js": "^3.1.5", 19 | "ecurve": "^1.0.2", 20 | "secure-random": "^1.1.1" 21 | }, 22 | "author": "cryptonomex", 23 | "license": "BSD-2-Clause-FreeBSD", 24 | "devDependencies": { 25 | "assert": "^1.3.0", 26 | "babel-cli": "^6.2.0", 27 | "babel-core": "^6.2.0", 28 | "babel-preset-es2015": "^6.1.18", 29 | "mocha": "^2.3.4" 30 | }, 31 | "_babel": { 32 | "presets": [ 33 | "es2015" 34 | ] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/auth/ecc/src/address.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const config = require('../../../config'); 3 | const hash = require('./hash'); 4 | const base58 = require('bs58'); 5 | 6 | /** Addresses are shortened non-reversable hashes of a public key. The full PublicKey is preferred. 7 | @deprecated 8 | */ 9 | class Address { 10 | 11 | constructor(addy) { this.addy = addy; } 12 | 13 | static fromBuffer(buffer) { 14 | const _hash = hash.sha512(buffer); 15 | const addy = hash.ripemd160(_hash); 16 | return new Address(addy); 17 | } 18 | 19 | static fromString(string, address_prefix = config.get('address_prefix')) { 20 | const prefix = string.slice(0, address_prefix.length); 21 | assert.equal(address_prefix, prefix, `Expecting key to begin with ${address_prefix}, instead got ${prefix}`); 22 | let addy = string.slice(address_prefix.length); 23 | addy = new Buffer.from(base58.decode(addy), 'binary'); 24 | const checksum = addy.slice(-4); 25 | addy = addy.slice(0, -4); 26 | let new_checksum = hash.ripemd160(addy); 27 | new_checksum = new_checksum.slice(0, 4); 28 | assert.deepEqual(checksum, new_checksum, 'Checksum did not match'); 29 | return new Address(addy); 30 | } 31 | 32 | /** @return Address - Compressed PTS format (by default) */ 33 | static fromPublic(public_key, compressed = true, version = 56) { 34 | const sha2 = hash.sha256(public_key.toBuffer(compressed)); 35 | const rep = hash.ripemd160(sha2); 36 | const versionBuffer = new Buffer.alloc(1); 37 | versionBuffer.writeUInt8((0xFF & version), 0); 38 | const addr = Buffer.concat([versionBuffer, rep]); 39 | let check = hash.sha256(addr); 40 | check = hash.sha256(check); 41 | const buffer = Buffer.concat([addr, check.slice(0, 4)]); 42 | return new Address(hash.ripemd160(buffer)); 43 | } 44 | 45 | toBuffer() { 46 | return this.addy; 47 | } 48 | 49 | toString(address_prefix = config.get('address_prefix')) { 50 | const checksum = hash.ripemd160(this.addy); 51 | const addy = Buffer.concat([this.addy, checksum.slice(0, 4)]); 52 | return address_prefix + base58.encode(addy); 53 | } 54 | } 55 | 56 | module.exports = Address; 57 | -------------------------------------------------------------------------------- /src/auth/ecc/src/aes.js: -------------------------------------------------------------------------------- 1 | const secureRandom = require('secure-random'); 2 | const ByteBuffer = require('bytebuffer'); 3 | const crypto = require('browserify-aes'); 4 | const assert = require('assert'); 5 | const PublicKey = require('./key_public'); 6 | const PrivateKey = require('./key_private'); 7 | const hash = require('./hash'); 8 | 9 | const Long = ByteBuffer.Long; 10 | 11 | /** 12 | Spec: http://localhost:3002/steem/@dantheman/how-to-encrypt-a-memo-when-transferring-steem 13 | @throws {Error|TypeError} - "Invalid Key, ..." 14 | @arg {PrivateKey} private_key - required and used for decryption 15 | @arg {PublicKey} public_key - required and used to calculate the shared secret 16 | @arg {string} [nonce = uniqueNonce()] - assigned a random unique uint64 17 | 18 | @return {object} 19 | @property {string} nonce - random or unique uint64, provides entropy when re-using the same private/public keys. 20 | @property {Buffer} message - Plain text message 21 | @property {number} checksum - shared secret checksum 22 | */ 23 | function encrypt(private_key, public_key, message, nonce = uniqueNonce()) { 24 | return crypt(private_key, public_key, nonce, message) 25 | } 26 | 27 | /** 28 | Spec: http://localhost:3002/steem/@dantheman/how-to-encrypt-a-memo-when-transferring-steem 29 | @arg {PrivateKey} private_key - required and used for decryption 30 | @arg {PublicKey} public_key - required and used to calculate the shared secret 31 | @arg {string} nonce - random or unique uint64, provides entropy when re-using the same private/public keys. 32 | @arg {Buffer} message - Encrypted or plain text message 33 | @arg {number} checksum - shared secret checksum 34 | @throws {Error|TypeError} - "Invalid Key, ..." 35 | @return {Buffer} - message 36 | */ 37 | function decrypt(private_key, public_key, nonce, message, checksum) { 38 | return crypt(private_key, public_key, nonce, message, checksum).message 39 | } 40 | 41 | /** 42 | @arg {Buffer} message - Encrypted or plain text message (see checksum) 43 | @arg {number} checksum - shared secret checksum (null to encrypt, non-null to decrypt) 44 | */ 45 | function crypt(private_key, public_key, nonce, message, checksum) { 46 | private_key = toPrivateObj(private_key) 47 | if (!private_key) 48 | throw new TypeError('private_key is required') 49 | 50 | public_key = toPublicObj(public_key) 51 | if (!public_key) 52 | throw new TypeError('public_key is required') 53 | 54 | nonce = toLongObj(nonce) 55 | if (!nonce) 56 | throw new TypeError('nonce is required') 57 | 58 | if (!Buffer.isBuffer(message)) { 59 | if (typeof message !== 'string') 60 | throw new TypeError('message should be buffer or string') 61 | message = new Buffer.from(message, 'binary') 62 | } 63 | if (checksum && typeof checksum !== 'number') 64 | throw new TypeError('checksum should be a number') 65 | 66 | const S = private_key.get_shared_secret(public_key); 67 | let ebuf = new ByteBuffer(ByteBuffer.DEFAULT_CAPACITY, ByteBuffer.LITTLE_ENDIAN) 68 | ebuf.writeUint64(nonce) 69 | ebuf.append(S.toString('binary'), 'binary') 70 | ebuf = new Buffer.from(ebuf.copy(0, ebuf.offset).toBinary(), 'binary') 71 | const encryption_key = hash.sha512(ebuf) 72 | 73 | // D E B U G 74 | // console.log('crypt', { 75 | // priv_to_pub: private_key.toPublicKey().toString(), 76 | // pub: public_key.toString(), 77 | // nonce: nonce.toString(), 78 | // message: message.length, 79 | // checksum, 80 | // S: S.toString('hex'), 81 | // encryption_key: encryption_key.toString('hex'), 82 | // }) 83 | 84 | const iv = encryption_key.slice(32, 48) 85 | const key = encryption_key.slice(0, 32) 86 | 87 | // check is first 64 bit of sha256 hash treated as uint64_t truncated to 32 bits. 88 | let check = hash.sha256(encryption_key) 89 | check = check.slice(0, 4) 90 | const cbuf = ByteBuffer.fromBinary(check.toString('binary'), ByteBuffer.DEFAULT_CAPACITY, ByteBuffer.LITTLE_ENDIAN) 91 | check = cbuf.readUint32() 92 | 93 | if (checksum) { 94 | if (check !== checksum) 95 | throw new Error('Invalid key') 96 | message = cryptoJsDecrypt(message, key, iv) 97 | } else { 98 | message = cryptoJsEncrypt(message, key, iv) 99 | } 100 | return {nonce, message, checksum: check} 101 | } 102 | 103 | /** This method does not use a checksum, the returned data must be validated some other way. 104 | @arg {string|Buffer} ciphertext - binary format 105 | @return {Buffer} 106 | */ 107 | function cryptoJsDecrypt(message, key, iv) { 108 | assert(message, "Missing cipher text") 109 | message = toBinaryBuffer(message) 110 | const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv) 111 | // decipher.setAutoPadding(true) 112 | message = Buffer.concat([decipher.update(message), decipher.final()]) 113 | return message 114 | } 115 | 116 | /** This method does not use a checksum, the returned data must be validated some other way. 117 | @arg {string|Buffer} plaintext - binary format 118 | @return {Buffer} binary 119 | */ 120 | function cryptoJsEncrypt(message, key, iv) { 121 | assert(message, "Missing plain text") 122 | message = toBinaryBuffer(message) 123 | const cipher = crypto.createCipheriv('aes-256-cbc', key, iv) 124 | // cipher.setAutoPadding(true) 125 | message = Buffer.concat([cipher.update(message), cipher.final()]) 126 | return message 127 | } 128 | 129 | /** @return {string} unique 64 bit unsigned number string. Being time based, this is careful to never choose the same nonce twice. This value could be recorded in the blockchain for a long time. 130 | */ 131 | function uniqueNonce() { 132 | if(unique_nonce_entropy === null) { 133 | const b = secureRandom.randomUint8Array(2) 134 | unique_nonce_entropy = parseInt(b[0] << 8 | b[1], 10) 135 | } 136 | let long = Long.fromNumber(Date.now()) 137 | const entropy = ++unique_nonce_entropy % 0xFFFF 138 | // console.log('uniqueNonce date\t', ByteBuffer.allocate(8).writeUint64(long).toHex(0)) 139 | // console.log('uniqueNonce entropy\t', ByteBuffer.allocate(8).writeUint64(Long.fromNumber(entropy)).toHex(0)) 140 | long = long.shiftLeft(16).or(Long.fromNumber(entropy)); 141 | // console.log('uniqueNonce final\t', ByteBuffer.allocate(8).writeUint64(long).toHex(0)) 142 | return long.toString() 143 | } 144 | let unique_nonce_entropy = null 145 | // for(let i=1; i < 10; i++) key.uniqueNonce() 146 | 147 | const toPrivateObj = o => (o ? o.d ? o : PrivateKey.fromWif(o) : o/*null or undefined*/) 148 | const toPublicObj = o => (o ? o.Q ? o : PublicKey.fromString(o) : o/*null or undefined*/) 149 | const toLongObj = o => (o ? Long.isLong(o) ? o : Long.fromString(o) : o) 150 | const toBinaryBuffer = o => (o ? Buffer.isBuffer(o) ? o : new Buffer.from(o, 'binary') : o) 151 | 152 | module.exports = { 153 | encrypt, 154 | decrypt 155 | } 156 | -------------------------------------------------------------------------------- /src/auth/ecc/src/brain_key.js: -------------------------------------------------------------------------------- 1 | 2 | export function normalize(brain_key) { 3 | if (typeof brain_key !== 'string') { 4 | throw new Error("string required for brain_key"); 5 | } 6 | brain_key = brain_key.trim(); 7 | return brain_key.split(/[\t\n\v\f\r ]+/).join(' '); 8 | } 9 | -------------------------------------------------------------------------------- /src/auth/ecc/src/ecdsa.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') // from github.com/bitcoinjs/bitcoinjs-lib from github.com/cryptocoinjs/ecdsa 2 | var crypto = require('./hash') 3 | var enforceType = require('./enforce_types') 4 | 5 | var BigInteger = require('bigi') 6 | var ECSignature = require('./ecsignature') 7 | 8 | // https://tools.ietf.org/html/rfc6979#section-3.2 9 | function deterministicGenerateK(curve, hash, d, checkSig, nonce) { 10 | 11 | enforceType('Buffer', hash) 12 | enforceType(BigInteger, d) 13 | 14 | if (nonce) { 15 | hash = crypto.sha256(Buffer.concat([hash, new Buffer.alloc(nonce)])) 16 | } 17 | 18 | // sanity check 19 | assert.equal(hash.length, 32, 'Hash must be 256 bit') 20 | 21 | var x = d.toBuffer(32) 22 | var k = new Buffer.alloc(32) 23 | var v = new Buffer.alloc(32) 24 | 25 | // Step B 26 | v.fill(1) 27 | 28 | // Step C 29 | k.fill(0) 30 | 31 | // Step D 32 | k = crypto.HmacSHA256(Buffer.concat([v, new Buffer.from([0]), x, hash]), k) 33 | 34 | // Step E 35 | v = crypto.HmacSHA256(v, k) 36 | 37 | // Step F 38 | k = crypto.HmacSHA256(Buffer.concat([v, new Buffer.from([1]), x, hash]), k) 39 | 40 | // Step G 41 | v = crypto.HmacSHA256(v, k) 42 | 43 | // Step H1/H2a, ignored as tlen === qlen (256 bit) 44 | // Step H2b 45 | v = crypto.HmacSHA256(v, k) 46 | 47 | var T = BigInteger.fromBuffer(v) 48 | 49 | // Step H3, repeat until T is within the interval [1, n - 1] 50 | while ((T.signum() <= 0) || (T.compareTo(curve.n) >= 0) || !checkSig(T)) { 51 | k = crypto.HmacSHA256(Buffer.concat([v, new Buffer.from([0])]), k) 52 | v = crypto.HmacSHA256(v, k) 53 | 54 | // Step H1/H2a, again, ignored as tlen === qlen (256 bit) 55 | // Step H2b again 56 | v = crypto.HmacSHA256(v, k) 57 | 58 | T = BigInteger.fromBuffer(v) 59 | } 60 | 61 | return T 62 | 63 | } 64 | 65 | function sign(curve, hash, d, nonce) { 66 | 67 | var e = BigInteger.fromBuffer(hash) 68 | var n = curve.n 69 | var G = curve.G 70 | 71 | var r, s 72 | var k = deterministicGenerateK(curve, hash, d, function (k) { 73 | // find canonically valid signature 74 | var Q = G.multiply(k) 75 | 76 | if (curve.isInfinity(Q)) return false 77 | 78 | r = Q.affineX.mod(n) 79 | if (r.signum() === 0) return false 80 | 81 | s = k.modInverse(n).multiply(e.add(d.multiply(r))).mod(n) 82 | if (s.signum() === 0) return false 83 | 84 | return true 85 | }, nonce) 86 | 87 | var N_OVER_TWO = n.shiftRight(1) 88 | 89 | // enforce low S values, see bip62: 'low s values in signatures' 90 | if (s.compareTo(N_OVER_TWO) > 0) { 91 | s = n.subtract(s) 92 | } 93 | 94 | return new ECSignature(r, s) 95 | } 96 | 97 | function verifyRaw(curve, e, signature, Q) { 98 | var n = curve.n 99 | var G = curve.G 100 | 101 | var r = signature.r 102 | var s = signature.s 103 | 104 | // 1.4.1 Enforce r and s are both integers in the interval [1, n − 1] 105 | if (r.signum() <= 0 || r.compareTo(n) >= 0) return false 106 | if (s.signum() <= 0 || s.compareTo(n) >= 0) return false 107 | 108 | // c = s^-1 mod n 109 | var c = s.modInverse(n) 110 | 111 | // 1.4.4 Compute u1 = es^−1 mod n 112 | // u2 = rs^−1 mod n 113 | var u1 = e.multiply(c).mod(n) 114 | var u2 = r.multiply(c).mod(n) 115 | 116 | // 1.4.5 Compute R = (xR, yR) = u1G + u2Q 117 | var R = G.multiplyTwo(u1, Q, u2) 118 | 119 | // 1.4.5 (cont.) Enforce R is not at infinity 120 | if (curve.isInfinity(R)) return false 121 | 122 | // 1.4.6 Convert the field element R.x to an integer 123 | var xR = R.affineX 124 | 125 | // 1.4.7 Set v = xR mod n 126 | var v = xR.mod(n) 127 | 128 | // 1.4.8 If v = r, output "valid", and if v != r, output "invalid" 129 | return v.equals(r) 130 | } 131 | 132 | function verify(curve, hash, signature, Q) { 133 | // 1.4.2 H = Hash(M), already done by the user 134 | // 1.4.3 e = H 135 | var e = BigInteger.fromBuffer(hash) 136 | return verifyRaw(curve, e, signature, Q) 137 | } 138 | 139 | /** 140 | * Recover a public key from a signature. 141 | * 142 | * See SEC 1: Elliptic Curve Cryptography, section 4.1.6, "Public 143 | * Key Recovery Operation". 144 | * 145 | * http://www.secg.org/download/aid-780/sec1-v2.pdf 146 | */ 147 | function recoverPubKey(curve, e, signature, i) { 148 | assert.strictEqual(i & 3, i, 'Recovery param is more than two bits') 149 | 150 | var n = curve.n 151 | var G = curve.G 152 | 153 | var r = signature.r 154 | var s = signature.s 155 | 156 | assert(r.signum() > 0 && r.compareTo(n) < 0, 'Invalid r value') 157 | assert(s.signum() > 0 && s.compareTo(n) < 0, 'Invalid s value') 158 | 159 | // A set LSB signifies that the y-coordinate is odd 160 | var isYOdd = i & 1 161 | 162 | // The more significant bit specifies whether we should use the 163 | // first or second candidate key. 164 | var isSecondKey = i >> 1 165 | 166 | // 1.1 Let x = r + jn 167 | var x = isSecondKey ? r.add(n) : r 168 | var R = curve.pointFromX(isYOdd, x) 169 | 170 | // 1.4 Check that nR is at infinity 171 | var nR = R.multiply(n) 172 | assert(curve.isInfinity(nR), 'nR is not a valid curve point') 173 | 174 | // Compute -e from e 175 | var eNeg = e.negate().mod(n) 176 | 177 | // 1.6.1 Compute Q = r^-1 (sR - eG) 178 | // Q = r^-1 (sR + -eG) 179 | var rInv = r.modInverse(n) 180 | 181 | var Q = R.multiplyTwo(s, G, eNeg).multiply(rInv) 182 | curve.validate(Q) 183 | 184 | return Q 185 | } 186 | 187 | /** 188 | * Calculate pubkey extraction parameter. 189 | * 190 | * When extracting a pubkey from a signature, we have to 191 | * distinguish four different cases. Rather than putting this 192 | * burden on the verifier, Bitcoin includes a 2-bit value with the 193 | * signature. 194 | * 195 | * This function simply tries all four cases and returns the value 196 | * that resulted in a successful pubkey recovery. 197 | */ 198 | function calcPubKeyRecoveryParam(curve, e, signature, Q) { 199 | for (var i = 0; i < 4; i++) { 200 | var Qprime = recoverPubKey(curve, e, signature, i) 201 | 202 | // 1.6.2 Verify Q 203 | if (Qprime.equals(Q)) { 204 | return i 205 | } 206 | } 207 | 208 | throw new Error('Unable to find valid recovery factor') 209 | } 210 | 211 | module.exports = { 212 | calcPubKeyRecoveryParam: calcPubKeyRecoveryParam, 213 | deterministicGenerateK: deterministicGenerateK, 214 | recoverPubKey: recoverPubKey, 215 | sign: sign, 216 | verify: verify, 217 | verifyRaw: verifyRaw 218 | } 219 | -------------------------------------------------------------------------------- /src/auth/ecc/src/ecsignature.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') // from https://github.com/bitcoinjs/bitcoinjs-lib 2 | var enforceType = require('./enforce_types') 3 | 4 | var BigInteger = require('bigi') 5 | 6 | function ECSignature(r, s) { 7 | enforceType(BigInteger, r) 8 | enforceType(BigInteger, s) 9 | 10 | this.r = r 11 | this.s = s 12 | } 13 | 14 | // Import operations 15 | ECSignature.parseCompact = function(buffer) { 16 | assert.equal(buffer.length, 65, 'Invalid signature length') 17 | var i = buffer.readUInt8(0) - 27 18 | 19 | // At most 3 bits 20 | assert.equal(i, i & 7, 'Invalid signature parameter') 21 | var compressed = !!(i & 4) 22 | 23 | // Recovery param only 24 | i = i & 3 25 | 26 | var r = BigInteger.fromBuffer(buffer.slice(1, 33)) 27 | var s = BigInteger.fromBuffer(buffer.slice(33)) 28 | 29 | return { 30 | compressed: compressed, 31 | i: i, 32 | signature: new ECSignature(r, s) 33 | } 34 | } 35 | 36 | ECSignature.fromDER = function(buffer) { 37 | assert.equal(buffer.readUInt8(0), 0x30, 'Not a DER sequence') 38 | assert.equal(buffer.readUInt8(1), buffer.length - 2, 'Invalid sequence length') 39 | assert.equal(buffer.readUInt8(2), 0x02, 'Expected a DER integer') 40 | 41 | var rLen = buffer.readUInt8(3) 42 | assert(rLen > 0, 'R length is zero') 43 | 44 | var offset = 4 + rLen 45 | assert.equal(buffer.readUInt8(offset), 0x02, 'Expected a DER integer (2)') 46 | 47 | var sLen = buffer.readUInt8(offset + 1) 48 | assert(sLen > 0, 'S length is zero') 49 | 50 | var rB = buffer.slice(4, offset) 51 | var sB = buffer.slice(offset + 2) 52 | offset += 2 + sLen 53 | 54 | if (rLen > 1 && rB.readUInt8(0) === 0x00) { 55 | assert(rB.readUInt8(1) & 0x80, 'R value excessively padded') 56 | } 57 | 58 | if (sLen > 1 && sB.readUInt8(0) === 0x00) { 59 | assert(sB.readUInt8(1) & 0x80, 'S value excessively padded') 60 | } 61 | 62 | assert.equal(offset, buffer.length, 'Invalid DER encoding') 63 | var r = BigInteger.fromDERInteger(rB) 64 | var s = BigInteger.fromDERInteger(sB) 65 | 66 | assert(r.signum() >= 0, 'R value is negative') 67 | assert(s.signum() >= 0, 'S value is negative') 68 | 69 | return new ECSignature(r, s) 70 | } 71 | 72 | // FIXME: 0x00, 0x04, 0x80 are SIGHASH_* boundary constants, importing Transaction causes a circular dependency 73 | ECSignature.parseScriptSignature = function(buffer) { 74 | var hashType = buffer.readUInt8(buffer.length - 1) 75 | var hashTypeMod = hashType & ~0x80 76 | 77 | assert(hashTypeMod > 0x00 && hashTypeMod < 0x04, 'Invalid hashType') 78 | 79 | return { 80 | signature: ECSignature.fromDER(buffer.slice(0, -1)), 81 | hashType: hashType 82 | } 83 | } 84 | 85 | // Export operations 86 | ECSignature.prototype.toCompact = function(i, compressed) { 87 | if (compressed) i += 4 88 | i += 27 89 | 90 | var buffer = new Buffer.alloc(65) 91 | buffer.writeUInt8(i, 0) 92 | 93 | this.r.toBuffer(32).copy(buffer, 1) 94 | this.s.toBuffer(32).copy(buffer, 33) 95 | 96 | return buffer 97 | } 98 | 99 | ECSignature.prototype.toDER = function() { 100 | var rBa = this.r.toDERInteger() 101 | var sBa = this.s.toDERInteger() 102 | 103 | var sequence = [] 104 | 105 | // INTEGER 106 | sequence.push(0x02, rBa.length) 107 | sequence = sequence.concat(rBa) 108 | 109 | // INTEGER 110 | sequence.push(0x02, sBa.length) 111 | sequence = sequence.concat(sBa) 112 | 113 | // SEQUENCE 114 | sequence.unshift(0x30, sequence.length) 115 | 116 | return new Buffer.from(sequence) 117 | } 118 | 119 | ECSignature.prototype.toScriptSignature = function(hashType) { 120 | var hashTypeBuffer = new Buffer.alloc(1) 121 | hashTypeBuffer.writeUInt8(hashType, 0) 122 | 123 | return Buffer.concat([this.toDER(), hashTypeBuffer]) 124 | } 125 | 126 | module.exports = ECSignature 127 | -------------------------------------------------------------------------------- /src/auth/ecc/src/enforce_types.js: -------------------------------------------------------------------------------- 1 | module.exports = function enforce(type, value) { // Copied from https://github.com/bitcoinjs/bitcoinjs-lib 2 | switch (type) { 3 | case 'Array': { 4 | if (Array.isArray(value)) return 5 | break 6 | } 7 | 8 | case 'Boolean': { 9 | if (typeof value === 'boolean') return 10 | break 11 | } 12 | 13 | case 'Buffer': { 14 | if (Buffer.isBuffer(value)) return 15 | break 16 | } 17 | 18 | case 'Number': { 19 | if (typeof value === 'number') return 20 | break 21 | } 22 | 23 | case 'String': { 24 | if (typeof value === 'string') return 25 | break 26 | } 27 | 28 | default: { 29 | if (getName(value.constructor) === getName(type)) return 30 | } 31 | } 32 | 33 | throw new TypeError('Expected ' + (getName(type) || type) + ', got ' + value) 34 | } 35 | 36 | function getName(fn) { 37 | // Why not fn.name: https://kangax.github.io/compat-table/es6/#function_name_property 38 | var match = fn.toString().match(/function (.*?)\(/) 39 | return match ? match[1] : null 40 | } 41 | -------------------------------------------------------------------------------- /src/auth/ecc/src/hash.js: -------------------------------------------------------------------------------- 1 | const createHash = require('create-hash'); 2 | const createHmac = require('create-hmac'); 3 | 4 | /** @arg {string|Buffer} data 5 | @arg {string} [digest = null] - 'hex', 'binary' or 'base64' 6 | @return {string|Buffer} - Buffer when digest is null, or string 7 | */ 8 | function sha1(data, encoding) { 9 | return createHash('sha1').update(data).digest(encoding) 10 | } 11 | 12 | /** @arg {string|Buffer} data 13 | @arg {string} [digest = null] - 'hex', 'binary' or 'base64' 14 | @return {string|Buffer} - Buffer when digest is null, or string 15 | */ 16 | function sha256(data, encoding) { 17 | return createHash('sha256').update(data).digest(encoding) 18 | } 19 | 20 | /** @arg {string|Buffer} data 21 | @arg {string} [digest = null] - 'hex', 'binary' or 'base64' 22 | @return {string|Buffer} - Buffer when digest is null, or string 23 | */ 24 | function sha512(data, encoding) { 25 | return createHash('sha512').update(data).digest(encoding) 26 | } 27 | 28 | function HmacSHA256(buffer, secret) { 29 | return createHmac('sha256', secret).update(buffer).digest() 30 | } 31 | 32 | function ripemd160(data) { 33 | return createHash('rmd160').update(data).digest() 34 | } 35 | 36 | // function hash160(buffer) { 37 | // return ripemd160(sha256(buffer)) 38 | // } 39 | // 40 | // function hash256(buffer) { 41 | // return sha256(sha256(buffer)) 42 | // } 43 | 44 | // 45 | // function HmacSHA512(buffer, secret) { 46 | // return crypto.createHmac('sha512', secret).update(buffer).digest() 47 | // } 48 | 49 | module.exports = { 50 | sha1: sha1, 51 | sha256: sha256, 52 | sha512: sha512, 53 | HmacSHA256: HmacSHA256, 54 | ripemd160: ripemd160 55 | // hash160: hash160, 56 | // hash256: hash256, 57 | // HmacSHA512: HmacSHA512 58 | } 59 | -------------------------------------------------------------------------------- /src/auth/ecc/src/key_private.js: -------------------------------------------------------------------------------- 1 | var ecurve = require('ecurve'); 2 | var Point = ecurve.Point; 3 | var secp256k1 = ecurve.getCurveByName('secp256k1'); 4 | var BigInteger = require('bigi'); 5 | var base58 = require('bs58'); 6 | var assert = require('assert'); 7 | var hash = require('./hash'); 8 | var PublicKey = require('./key_public'); 9 | 10 | var G = secp256k1.G 11 | var n = secp256k1.n 12 | 13 | class PrivateKey { 14 | 15 | /** 16 | @private see static functions 17 | @param {BigInteger} 18 | */ 19 | constructor(d) { this.d = d; } 20 | 21 | static fromBuffer(buf) { 22 | if (!Buffer.isBuffer(buf)) { 23 | throw new Error("Expecting parameter to be a Buffer type"); 24 | } 25 | if (32 !== buf.length) { 26 | console.log(`WARN: Expecting 32 bytes, instead got ${buf.length}, stack trace:`, new Error().stack); 27 | } 28 | if (buf.length === 0) { 29 | throw new Error("Empty buffer"); 30 | } 31 | return new PrivateKey(BigInteger.fromBuffer(buf)); 32 | } 33 | 34 | /** @arg {string} seed - any length string. This is private, the same seed produces the same private key every time. */ 35 | static fromSeed(seed) { // generate_private_key 36 | if (!(typeof seed === 'string')) { 37 | throw new Error('seed must be of type string'); 38 | } 39 | return PrivateKey.fromBuffer(hash.sha256(seed)); 40 | } 41 | 42 | static isWif(text) { 43 | try { 44 | this.fromWif(text) 45 | return true 46 | } catch(e) { 47 | return false 48 | } 49 | } 50 | 51 | /** 52 | @throws {AssertError|Error} parsing key 53 | @return {string} Wallet Import Format (still a secret, Not encrypted) 54 | */ 55 | static fromWif(_private_wif) { 56 | var private_wif = new Buffer.from(base58.decode(_private_wif)); 57 | var version = private_wif.readUInt8(0); 58 | assert.equal(0x80, version, `Expected version ${0x80}, instead got ${version}`); 59 | // checksum includes the version 60 | var private_key = private_wif.slice(0, -4); 61 | var checksum = private_wif.slice(-4); 62 | var new_checksum = hash.sha256(private_key); 63 | new_checksum = hash.sha256(new_checksum); 64 | new_checksum = new_checksum.slice(0, 4); 65 | if (checksum.toString() !== new_checksum.toString()) 66 | throw new Error('Invalid WIF key (checksum miss-match)') 67 | 68 | private_key = private_key.slice(1); 69 | return PrivateKey.fromBuffer(private_key); 70 | } 71 | 72 | toWif() { 73 | var private_key = this.toBuffer(); 74 | // checksum includes the version 75 | private_key = Buffer.concat([new Buffer.from([0x80]), private_key]); 76 | var checksum = hash.sha256(private_key); 77 | checksum = hash.sha256(checksum); 78 | checksum = checksum.slice(0, 4); 79 | var private_wif = Buffer.concat([private_key, checksum]); 80 | return base58.encode(private_wif); 81 | } 82 | 83 | /** Alias for {@link toWif} */ 84 | toString() { 85 | return this.toWif() 86 | } 87 | 88 | /** 89 | @return {Point} 90 | */ 91 | toPublicKeyPoint() { 92 | var Q; 93 | return Q = secp256k1.G.multiply(this.d); 94 | } 95 | 96 | toPublic() { 97 | if (this.public_key) { return this.public_key; } 98 | return this.public_key = PublicKey.fromPoint(this.toPublicKeyPoint()); 99 | } 100 | 101 | toBuffer() { 102 | return this.d.toBuffer(32); 103 | } 104 | 105 | /** ECIES */ 106 | get_shared_secret(public_key) { 107 | public_key = toPublic(public_key) 108 | let KB = public_key.toUncompressed().toBuffer() 109 | let KBP = Point.fromAffine( 110 | secp256k1, 111 | BigInteger.fromBuffer( KB.slice( 1,33 )), // x 112 | BigInteger.fromBuffer( KB.slice( 33,65 )) // y 113 | ) 114 | let r = this.toBuffer() 115 | let P = KBP.multiply(BigInteger.fromBuffer(r)) 116 | let S = P.affineX.toBuffer({size: 32}) 117 | // SHA512 used in ECIES 118 | return hash.sha512(S) 119 | } 120 | 121 | // /** ECIES (does not always match the Point.fromAffine version above) */ 122 | // get_shared_secret(public_key){ 123 | // public_key = toPublic(public_key) 124 | // var P = public_key.Q.multiply( this.d ); 125 | // var S = P.affineX.toBuffer({size: 32}); 126 | // // ECIES, adds an extra sha512 127 | // return hash.sha512(S); 128 | // } 129 | 130 | /** @throws {Error} - overflow of the key could not be derived */ 131 | child( offset ) { 132 | offset = Buffer.concat([ this.toPublicKey().toBuffer(), offset ]) 133 | offset = hash.sha256( offset ) 134 | let c = BigInteger.fromBuffer(offset) 135 | 136 | if (c.compareTo(n) >= 0) 137 | throw new Error("Child offset went out of bounds, try again") 138 | 139 | let derived = this.d.add(c)//.mod(n) 140 | 141 | if( derived.signum() === 0 ) 142 | throw new Error("Child offset derived to an invalid key, try again") 143 | 144 | return new PrivateKey( derived ) 145 | } 146 | 147 | // toByteBuffer() { 148 | // var b = new ByteBuffer(ByteBuffer.DEFAULT_CAPACITY, ByteBuffer.LITTLE_ENDIAN); 149 | // this.appendByteBuffer(b); 150 | // return b.copy(0, b.offset); 151 | // } 152 | 153 | static fromHex(hex) { 154 | return PrivateKey.fromBuffer(new Buffer.from(hex, 'hex')); 155 | } 156 | 157 | toHex() { 158 | return this.toBuffer().toString('hex'); 159 | } 160 | 161 | toPublicKey() { 162 | return this.toPublic() 163 | } 164 | 165 | /* */ 166 | } 167 | 168 | module.exports = PrivateKey; 169 | 170 | const toPublic = data => data == null ? data : 171 | data.Q ? data : PublicKey.fromStringOrThrow(data) 172 | -------------------------------------------------------------------------------- /src/auth/ecc/src/key_public.js: -------------------------------------------------------------------------------- 1 | var BigInteger = require('bigi'); 2 | var ecurve = require('ecurve'); 3 | var secp256k1 = ecurve.getCurveByName('secp256k1'); 4 | BigInteger = require('bigi'); 5 | var base58 = require('bs58'); 6 | var hash = require('./hash'); 7 | var config = require('../../../config'); 8 | var assert = require('assert'); 9 | 10 | var G = secp256k1.G 11 | var n = secp256k1.n 12 | 13 | class PublicKey { 14 | 15 | /** @param {ecurve.Point} public key */ 16 | constructor(Q) { this.Q = Q; } 17 | 18 | static fromBinary(bin) { 19 | return PublicKey.fromBuffer(new Buffer.from(bin, 'binary')); 20 | } 21 | 22 | static fromBuffer(buffer) { 23 | if ( 24 | buffer.toString("hex") === 25 | "000000000000000000000000000000000000000000000000000000000000000000" 26 | ) 27 | return new PublicKey(null); 28 | return new PublicKey(ecurve.Point.decodeFrom(secp256k1, buffer)); 29 | } 30 | 31 | toBuffer(compressed = this.Q ? this.Q.compressed : null) { 32 | if (this.Q === null) 33 | return Buffer.from( 34 | "000000000000000000000000000000000000000000000000000000000000000000", 35 | "hex" 36 | ); 37 | return this.Q.getEncoded(compressed); 38 | } 39 | 40 | static fromPoint(point) { 41 | return new PublicKey(point); 42 | } 43 | 44 | toUncompressed() { 45 | var buf = this.Q.getEncoded(false); 46 | var point = ecurve.Point.decodeFrom(secp256k1, buf); 47 | return PublicKey.fromPoint(point); 48 | } 49 | 50 | /** bts::blockchain::address (unique but not a full public key) */ 51 | toBlockchainAddress() { 52 | var pub_buf = this.toBuffer(); 53 | var pub_sha = hash.sha512(pub_buf); 54 | return hash.ripemd160(pub_sha); 55 | } 56 | 57 | toString(address_prefix = config.get('address_prefix')) { 58 | return this.toPublicKeyString(address_prefix) 59 | } 60 | 61 | /** 62 | Full public key 63 | {return} string 64 | */ 65 | toPublicKeyString(address_prefix = config.get('address_prefix')) { 66 | if(this.pubdata) return address_prefix + this.pubdata 67 | const pub_buf = this.toBuffer(); 68 | const checksum = hash.ripemd160(pub_buf); 69 | const addy = Buffer.concat([pub_buf, checksum.slice(0, 4)]); 70 | this.pubdata = base58.encode(addy) 71 | return address_prefix + this.pubdata; 72 | } 73 | 74 | /** 75 | @arg {string} public_key - like STMXyz... 76 | @arg {string} address_prefix - like STM 77 | @return PublicKey or `null` (if the public_key string is invalid) 78 | @deprecated fromPublicKeyString (use fromString instead) 79 | */ 80 | static fromString(public_key, address_prefix = config.get('address_prefix')) { 81 | try { 82 | return PublicKey.fromStringOrThrow(public_key, address_prefix) 83 | } catch (e) { 84 | return null; 85 | } 86 | } 87 | 88 | /** 89 | @arg {string} public_key - like STMXyz... 90 | @arg {string} address_prefix - like STM 91 | @throws {Error} if public key is invalid 92 | @return PublicKey 93 | */ 94 | static fromStringOrThrow(public_key, address_prefix = config.get('address_prefix')) { 95 | var prefix = public_key.slice(0, address_prefix.length); 96 | assert.equal( 97 | address_prefix, prefix, 98 | `Expecting key to begin with ${address_prefix}, instead got ${prefix}`); 99 | public_key = public_key.slice(address_prefix.length); 100 | 101 | public_key = new Buffer.from(base58.decode(public_key), 'binary'); 102 | var checksum = public_key.slice(-4); 103 | public_key = public_key.slice(0, -4); 104 | var new_checksum = hash.ripemd160(public_key); 105 | new_checksum = new_checksum.slice(0, 4); 106 | assert.deepEqual(checksum, new_checksum, 'Checksum did not match'); 107 | return PublicKey.fromBuffer(public_key); 108 | } 109 | 110 | toAddressString(address_prefix = config.get('address_prefix')) { 111 | var pub_buf = this.toBuffer(); 112 | var pub_sha = hash.sha512(pub_buf); 113 | var addy = hash.ripemd160(pub_sha); 114 | var checksum = hash.ripemd160(addy); 115 | addy = Buffer.concat([addy, checksum.slice(0, 4)]); 116 | return address_prefix + base58.encode(addy); 117 | } 118 | 119 | toPtsAddy() { 120 | var pub_buf = this.toBuffer(); 121 | var pub_sha = hash.sha256(pub_buf); 122 | var addy = hash.ripemd160(pub_sha); 123 | addy = Buffer.concat([new Buffer.from([0x38]), addy]); //version 56(decimal) 124 | 125 | var checksum = hash.sha256(addy); 126 | checksum = hash.sha256(checksum); 127 | 128 | addy = Buffer.concat([addy, checksum.slice(0, 4)]); 129 | return base58.encode(addy); 130 | } 131 | 132 | child( offset ) { 133 | 134 | assert(Buffer.isBuffer(offset), "Buffer required: offset") 135 | assert.equal(offset.length, 32, "offset length") 136 | 137 | offset = Buffer.concat([ this.toBuffer(), offset ]) 138 | offset = hash.sha256( offset ) 139 | 140 | let c = BigInteger.fromBuffer( offset ) 141 | 142 | if (c.compareTo(n) >= 0) 143 | throw new Error("Child offset went out of bounds, try again") 144 | 145 | 146 | let cG = G.multiply(c) 147 | let Qprime = this.Q.add(cG) 148 | 149 | if( secp256k1.isInfinity(Qprime) ) 150 | throw new Error("Child offset derived to an invalid key, try again") 151 | 152 | return PublicKey.fromPoint(Qprime) 153 | } 154 | 155 | // toByteBuffer() { 156 | // var b = new ByteBuffer(ByteBuffer.DEFAULT_CAPACITY, ByteBuffer.LITTLE_ENDIAN); 157 | // this.appendByteBuffer(b); 158 | // return b.copy(0, b.offset); 159 | // } 160 | 161 | static fromHex(hex) { 162 | return PublicKey.fromBuffer(new Buffer.from(hex, 'hex')); 163 | } 164 | 165 | toHex() { 166 | return this.toBuffer().toString('hex'); 167 | } 168 | 169 | static fromStringHex(hex) { 170 | return PublicKey.fromString(new Buffer.from(hex, 'hex')); 171 | } 172 | 173 | /* */ 174 | } 175 | 176 | 177 | module.exports = PublicKey; 178 | -------------------------------------------------------------------------------- /src/auth/ecc/src/key_utils.js: -------------------------------------------------------------------------------- 1 | 2 | const PrivateKey = require('./key_private'); 3 | const hash = require('./hash'); 4 | const secureRandom = require('secure-random'); 5 | 6 | // hash for .25 second 7 | const HASH_POWER_MILLS = 250; 8 | 9 | let entropyPos = 0, entropyCount = 0 10 | const entropyArray = secureRandom.randomBuffer(101) 11 | 12 | module.exports = { 13 | 14 | addEntropy(...ints) { 15 | entropyCount++ 16 | for(const i of ints) { 17 | const pos = entropyPos++ % 101 18 | const i2 = entropyArray[pos] += i 19 | if(i2 > 9007199254740991) 20 | entropyArray[pos] = 0 21 | } 22 | }, 23 | 24 | /** 25 | A week random number generator can run out of entropy. This should ensure even the worst random number implementation will be reasonably safe. 26 | 27 | @param1 string entropy of at least 32 bytes 28 | */ 29 | random32ByteBuffer(entropy = this.browserEntropy()) { 30 | 31 | if (!(typeof entropy === 'string')) { 32 | throw new Error("string required for entropy"); 33 | } 34 | 35 | if (entropy.length < 32) { 36 | throw new Error("expecting at least 32 bytes of entropy"); 37 | } 38 | 39 | const start_t = Date.now(); 40 | 41 | while (Date.now() - start_t < HASH_POWER_MILLS) 42 | entropy = hash.sha256(entropy); 43 | 44 | const hash_array = []; 45 | hash_array.push(entropy); 46 | 47 | // Hashing for 1 second may helps the computer is not low on entropy (this method may be called back-to-back). 48 | hash_array.push(secureRandom.randomBuffer(32)); 49 | 50 | return hash.sha256(Buffer.concat(hash_array)); 51 | }, 52 | 53 | get_random_key(entropy) { 54 | return PrivateKey.fromBuffer(this.random32ByteBuffer(entropy)); 55 | }, 56 | 57 | // Turn invisible space like characters into a single space 58 | // normalize_brain_key(brain_key){ 59 | // if (!(typeof brain_key === 'string')) { 60 | // throw new Error("string required for brain_key"); 61 | // } 62 | // brain_key = brain_key.trim(); 63 | // return brain_key.split(/[\t\n\v\f\r ]+/).join(' '); 64 | // }, 65 | 66 | browserEntropy() { 67 | let entropyStr = Array(entropyArray).join() 68 | try { 69 | entropyStr += (new Date()).toString() + " " + window.screen.height + " " + window.screen.width + " " + 70 | window.screen.colorDepth + " " + " " + window.screen.availHeight + " " + window.screen.availWidth + " " + 71 | window.screen.pixelDepth + navigator.language + " " + window.location + " " + window.history.length; 72 | 73 | for (let i = 0, mimeType; i < navigator.mimeTypes.length; i++) { 74 | mimeType = navigator.mimeTypes[i]; 75 | entropyStr += mimeType.description + " " + mimeType.type + " " + mimeType.suffixes + " "; 76 | } 77 | console.log("INFO\tbrowserEntropy gathered", entropyCount, 'events') 78 | } catch(error) { 79 | //nodejs:ReferenceError: window is not defined 80 | entropyStr += hash.sha256((new Date()).toString()) 81 | } 82 | 83 | const b = new Buffer(entropyStr); 84 | entropyStr += b.toString('binary') + " " + (new Date()).toString(); 85 | return entropyStr; 86 | }, 87 | 88 | }; 89 | -------------------------------------------------------------------------------- /src/auth/ecc/src/signature.js: -------------------------------------------------------------------------------- 1 | var ecdsa = require('./ecdsa'); 2 | var hash = require('./hash'); 3 | var curve = require('ecurve').getCurveByName('secp256k1'); 4 | var assert = require('assert'); 5 | var BigInteger = require('bigi'); 6 | var PublicKey = require('./key_public'); 7 | var PrivateKey = require('./key_private'); 8 | 9 | class Signature { 10 | 11 | constructor(r1, s1, i1) { 12 | this.r = r1; 13 | this.s = s1; 14 | this.i = i1; 15 | assert.equal(this.r != null, true, 'Missing parameter'); 16 | assert.equal(this.s != null, true, 'Missing parameter'); 17 | assert.equal(this.i != null, true, 'Missing parameter'); 18 | } 19 | 20 | static fromBuffer(buf) { 21 | var i, r, s; 22 | assert.equal(buf.length, 65, 'Invalid signature length'); 23 | i = buf.readUInt8(0); 24 | assert.equal(i - 27, i - 27 & 7, 'Invalid signature parameter'); 25 | r = BigInteger.fromBuffer(buf.slice(1, 33)); 26 | s = BigInteger.fromBuffer(buf.slice(33)); 27 | return new Signature(r, s, i); 28 | }; 29 | 30 | toBuffer() { 31 | var buf; 32 | buf = new Buffer.alloc(65); 33 | buf.writeUInt8(this.i, 0); 34 | this.r.toBuffer(32).copy(buf, 1); 35 | this.s.toBuffer(32).copy(buf, 33); 36 | return buf; 37 | }; 38 | 39 | recoverPublicKeyFromBuffer(buffer) { 40 | return this.recoverPublicKey(hash.sha256(buffer)); 41 | }; 42 | 43 | /** 44 | @return {PublicKey} 45 | */ 46 | recoverPublicKey(sha256_buffer) { 47 | let Q, e, i; 48 | e = BigInteger.fromBuffer(sha256_buffer); 49 | i = this.i; 50 | i -= 27; 51 | i = i & 3; 52 | Q = ecdsa.recoverPubKey(curve, e, this, i); 53 | return PublicKey.fromPoint(Q); 54 | }; 55 | 56 | 57 | /** 58 | @param {Buffer} buf 59 | @param {PrivateKey} private_key 60 | @return {Signature} 61 | */ 62 | static signBuffer(buf, private_key) { 63 | var _hash = hash.sha256(buf); 64 | return Signature.signBufferSha256(_hash, private_key) 65 | } 66 | 67 | /** Sign a buffer of exactally 32 bytes in size (sha256(text)) 68 | @param {Buffer} buf - 32 bytes binary 69 | @param {PrivateKey} private_key 70 | @return {Signature} 71 | */ 72 | static signBufferSha256(buf_sha256, private_key) { 73 | if( buf_sha256.length !== 32 || ! Buffer.isBuffer(buf_sha256) ) 74 | throw new Error("buf_sha256: 32 byte buffer required") 75 | private_key = toPrivateObj(private_key) 76 | assert(private_key, 'private_key required') 77 | 78 | var der, e, ecsignature, i, lenR, lenS, nonce; 79 | i = null; 80 | nonce = 0; 81 | e = BigInteger.fromBuffer(buf_sha256); 82 | while (true) { 83 | ecsignature = ecdsa.sign(curve, buf_sha256, private_key.d, nonce++); 84 | der = ecsignature.toDER(); 85 | lenR = der[3]; 86 | lenS = der[5 + lenR]; 87 | if (lenR === 32 && lenS === 32) { 88 | i = ecdsa.calcPubKeyRecoveryParam(curve, e, ecsignature, private_key.toPublicKey().Q); 89 | i += 4; // compressed 90 | i += 27; // compact // 24 or 27 :( forcing odd-y 2nd key candidate) 91 | break; 92 | } 93 | if (nonce % 10 === 0) { 94 | console.log("WARN: " + nonce + " attempts to find canonical signature"); 95 | } 96 | } 97 | return new Signature(ecsignature.r, ecsignature.s, i); 98 | }; 99 | 100 | static sign(string, private_key) { 101 | return Signature.signBuffer(new Buffer.from(string), private_key); 102 | }; 103 | 104 | 105 | /** 106 | @param {Buffer} un-hashed 107 | @param {./PublicKey} 108 | @return {boolean} 109 | */ 110 | verifyBuffer(buf, public_key) { 111 | var _hash = hash.sha256(buf); 112 | return this.verifyHash(_hash, public_key); 113 | }; 114 | 115 | verifyHash(hash, public_key) { 116 | assert.equal(hash.length, 32, "A SHA 256 should be 32 bytes long, instead got " + hash.length); 117 | return ecdsa.verify(curve, hash, { 118 | r: this.r, 119 | s: this.s 120 | }, public_key.Q); 121 | }; 122 | 123 | 124 | // toByteBuffer() { 125 | // var b; 126 | // b = new ByteBuffer(ByteBuffer.DEFAULT_CAPACITY, ByteBuffer.LITTLE_ENDIAN); 127 | // this.appendByteBuffer(b); 128 | // return b.copy(0, b.offset); 129 | // }; 130 | 131 | static fromHex(hex) { 132 | return Signature.fromBuffer(new Buffer.from(hex, "hex")); 133 | }; 134 | 135 | toHex() { 136 | return this.toBuffer().toString("hex"); 137 | }; 138 | 139 | static signHex(hex, private_key) { 140 | var buf; 141 | buf = new Buffer.from(hex, 'hex'); 142 | return Signature.signBuffer(buf, private_key); 143 | }; 144 | 145 | verifyHex(hex, public_key) { 146 | var buf; 147 | buf = new Buffer.from(hex, 'hex'); 148 | return this.verifyBuffer(buf, public_key); 149 | }; 150 | 151 | } 152 | const toPrivateObj = o => (o ? o.d ? o : PrivateKey.fromWif(o) : o/*null or undefined*/) 153 | module.exports = Signature; 154 | -------------------------------------------------------------------------------- /src/auth/index.js: -------------------------------------------------------------------------------- 1 | var bigi = require('bigi'), 2 | bs58 = require('bs58'), 3 | ecurve = require('ecurve'), 4 | Point = ecurve.Point, 5 | secp256k1 = ecurve.getCurveByName('secp256k1'), 6 | config = require('../config'), 7 | operations = require('./serializer/src/operations'), 8 | Signature = require('./ecc/src/signature'), 9 | KeyPrivate = require('./ecc/src/key_private'), 10 | PublicKey = require('./ecc/src/key_public'), 11 | hash = require('./ecc/src/hash'); 12 | 13 | var Auth = {}; 14 | var transaction = operations.transaction; 15 | var signed_transaction = operations.signed_transaction; 16 | 17 | Auth.verify = function (name, password, auths) { 18 | var hasKey = false; 19 | var roles = []; 20 | for (var role in auths) { 21 | roles.push(role); 22 | } 23 | var pubKeys = this.generateKeys(name, password, roles); 24 | roles.forEach(function (role) { 25 | if (auths[role][0][0] === pubKeys[role]) { 26 | hasKey = true; 27 | } 28 | }); 29 | return hasKey; 30 | }; 31 | 32 | Auth.generateKeys = function (name, password, roles) { 33 | var pubKeys = {}; 34 | roles.forEach(function (role) { 35 | var seed = name + role + password; 36 | var brainKey = seed.trim().split(/[\t\n\v\f\r ]+/).join(' '); 37 | var hashSha256 = hash.sha256(brainKey); 38 | var bigInt = bigi.fromBuffer(hashSha256); 39 | var toPubKey = secp256k1.G.multiply(bigInt); 40 | var point = new Point(toPubKey.curve, toPubKey.x, toPubKey.y, toPubKey.z); 41 | var pubBuf = point.getEncoded(toPubKey.compressed); 42 | var checksum = hash.ripemd160(pubBuf); 43 | var addy = Buffer.concat([pubBuf, checksum.slice(0, 4)]); 44 | pubKeys[role] = config.get('address_prefix') + bs58.encode(addy); 45 | }); 46 | return pubKeys; 47 | }; 48 | 49 | /** 50 | @arg {string} name - blockchain account name 51 | @arg {string} password - very strong password typically no shorter than a private key 52 | @arg {array} roles - defaults to standard Steem blockchain-level roles 53 | */ 54 | Auth.getPrivateKeys = function (name, password, roles = ['owner', 'active', 'posting', 'memo']) { 55 | var privKeys = {}; 56 | roles.forEach(function (role) { 57 | privKeys[role] = this.toWif(name, password, role); 58 | privKeys[role + 'Pubkey'] = this.wifToPublic(privKeys[role]); 59 | }.bind(this)); 60 | return privKeys; 61 | }; 62 | 63 | Auth.isWif = function (privWif) { 64 | var isWif = false; 65 | try { 66 | var bufWif = new Buffer.from(bs58.decode(privWif)); 67 | var privKey = bufWif.slice(0, -4); 68 | var checksum = bufWif.slice(-4); 69 | var newChecksum = hash.sha256(privKey); 70 | newChecksum = hash.sha256(newChecksum); 71 | newChecksum = newChecksum.slice(0, 4); 72 | if (checksum.toString() == newChecksum.toString()) { 73 | isWif = true; 74 | } 75 | } catch (e) { } 76 | return isWif; 77 | }; 78 | 79 | Auth.toWif = function (name, password, role) { 80 | var seed = name + role + password; 81 | var brainKey = seed.trim().split(/[\t\n\v\f\r ]+/).join(' '); 82 | var hashSha256 = hash.sha256(brainKey); 83 | var privKey = Buffer.concat([new Buffer.from([0x80]), hashSha256]); 84 | var checksum = hash.sha256(privKey); 85 | checksum = hash.sha256(checksum); 86 | checksum = checksum.slice(0, 4); 87 | var privWif = Buffer.concat([privKey, checksum]); 88 | return bs58.encode(privWif); 89 | }; 90 | 91 | Auth.wifIsValid = function (privWif, pubWif) { 92 | return (this.wifToPublic(privWif) == pubWif); 93 | }; 94 | 95 | Auth.wifToPublic = function (privWif) { 96 | var pubWif = KeyPrivate.fromWif(privWif); 97 | pubWif = pubWif.toPublic().toString(); 98 | return pubWif; 99 | }; 100 | 101 | Auth.isPubkey = function(pubkey, address_prefix) { 102 | return PublicKey.fromString(pubkey, address_prefix) != null 103 | } 104 | 105 | Auth.signTransaction = function (trx, keys) { 106 | var signatures = []; 107 | if (trx.signatures) { 108 | signatures = [].concat(trx.signatures); 109 | } 110 | 111 | var cid = new Buffer.from(config.get('chain_id'), 'hex'); 112 | var buf = transaction.toBuffer(trx); 113 | 114 | for (var key in keys) { 115 | var sig = Signature.signBuffer(Buffer.concat([cid, buf]), keys[key]); 116 | signatures.push(sig.toBuffer()) 117 | } 118 | 119 | return signed_transaction.toObject(Object.assign(trx, { signatures: signatures })) 120 | }; 121 | 122 | module.exports = Auth; 123 | -------------------------------------------------------------------------------- /src/auth/memo.js: -------------------------------------------------------------------------------- 1 | const ByteBuffer = require('bytebuffer'); 2 | const assert = require('assert'); 3 | const base58 = require('bs58'); 4 | const ecc = require('./ecc'); 5 | const Aes = ecc.Aes; 6 | const PrivateKey = ecc.PrivateKey; 7 | const PublicKey = ecc.PublicKey; 8 | const serializer = require('./serializer'); 9 | const ops = serializer.ops; 10 | 11 | const encMemo = ops.encrypted_memo; 12 | 13 | /** 14 | Some fields are only required if the memo is marked for decryption (starts with a hash). 15 | @arg {string|PrivateKey} private_key - WIF or PrivateKey object 16 | @arg {string} memo - plain text is returned, hash prefix base58 is decrypted 17 | @return {string} - utf8 decoded string (hash prefix) 18 | */ 19 | function decode(private_key, memo) { 20 | assert(memo, 'memo is required'); 21 | assert.equal(typeof memo, 'string', 'memo'); 22 | if(!/^#/.test(memo)) return memo; 23 | memo = memo.substring(1); 24 | 25 | assert(private_key, 'private_key is required'); 26 | checkEncryption(); 27 | 28 | private_key = toPrivateObj(private_key); 29 | 30 | memo = base58.decode(memo); 31 | memo = encMemo.fromBuffer(new Buffer.from(memo, 'binary')); 32 | 33 | const {from, to, nonce, check, encrypted} = memo; 34 | const pubkey = private_key.toPublicKey().toString(); 35 | const otherpub = pubkey === from.toString() ? to.toString() : from.toString(); 36 | memo = Aes.decrypt(private_key, otherpub, nonce, encrypted, check); 37 | 38 | // remove varint length prefix 39 | const mbuf = ByteBuffer.fromBinary(memo.toString('binary'), ByteBuffer.DEFAULT_CAPACITY, ByteBuffer.LITTLE_ENDIAN); 40 | try { 41 | mbuf.mark(); 42 | return '#' + mbuf.readVString(); 43 | } catch(e) { 44 | mbuf.reset(); 45 | // Sender did not length-prefix the memo 46 | memo = new Buffer.from(mbuf.toString('binary'), 'binary').toString('utf-8'); 47 | return '#' + memo; 48 | } 49 | } 50 | 51 | /** 52 | Some fields are only required if the memo is marked for encryption (starts with a hash). 53 | @arg {string|PrivateKey} private_key - WIF or PrivateKey object 54 | @arg {string|PublicKey} public_key - Recipient 55 | @arg {string} memo - plain text is returned, hash prefix text is encrypted 56 | @arg {string} [testNonce = undefined] - just for testing 57 | @return {string} - base64 decoded string (or plain text) 58 | */ 59 | function encode(private_key, public_key, memo, testNonce) { 60 | assert(memo, 'memo is required'); 61 | assert.equal(typeof memo, 'string', 'memo'); 62 | if(!/^#/.test(memo)) return memo; 63 | memo = memo.substring(1); 64 | 65 | assert(private_key, 'private_key is required'); 66 | assert(public_key, 'public_key is required'); 67 | checkEncryption(); 68 | 69 | private_key = toPrivateObj(private_key); 70 | public_key = toPublicObj(public_key); 71 | 72 | const mbuf = new ByteBuffer(ByteBuffer.DEFAULT_CAPACITY, ByteBuffer.LITTLE_ENDIAN); 73 | mbuf.writeVString(memo); 74 | memo = new Buffer.from(mbuf.copy(0, mbuf.offset).toBinary(), 'binary'); 75 | 76 | const {nonce, message, checksum} = Aes.encrypt(private_key, public_key, memo, testNonce); 77 | memo = encMemo.fromObject({ 78 | from: private_key.toPublicKey(), 79 | to: public_key, 80 | nonce, 81 | check: checksum, 82 | encrypted: message 83 | }); 84 | // serialize 85 | memo = encMemo.toBuffer(memo); 86 | return '#' + base58.encode(new Buffer.from(memo, 'binary')); 87 | } 88 | 89 | let encodeTest = undefined; 90 | 91 | /** 92 | Memo encryption has failed in the browser before. An Error will be thrown 93 | if a memo can't be encrypted and decrypted. 94 | */ 95 | function checkEncryption() { 96 | if(encodeTest === undefined) { 97 | let plaintext; 98 | encodeTest = true; // prevent infinate looping 99 | try { 100 | const wif = '5JdeC9P7Pbd1uGdFVEsJ41EkEnADbbHGq6p1BwFxm6txNBsQnsw'; 101 | const pubkey = 'STM8m5UgaFAAYQRuaNejYdS8FVLVp9Ss3K1qAVk5de6F8s3HnVbvA'; 102 | const cyphertext = encode(wif, pubkey, '#memo爱'); 103 | plaintext = decode(wif, cyphertext); 104 | } catch(e) { 105 | console.error(e); 106 | } finally { 107 | encodeTest = plaintext === '#memo爱'; 108 | } 109 | } 110 | if(encodeTest === false) 111 | throw new Error('This environment does not support encryption.'); 112 | } 113 | 114 | const toPrivateObj = o => (o ? o.d ? o : PrivateKey.fromWif(o) : o/*null or undefined*/); 115 | const toPublicObj = o => (o ? o.Q ? o : PublicKey.fromString(o) : o/*null or undefined*/); 116 | 117 | module.exports = { 118 | decode, 119 | encode 120 | }; 121 | -------------------------------------------------------------------------------- /src/auth/serializer/.npmrc: -------------------------------------------------------------------------------- 1 | # @graphene/serializer:hex_dump = true 2 | -------------------------------------------------------------------------------- /src/auth/serializer/README.md: -------------------------------------------------------------------------------- 1 | # About 2 | Binary serialization and deserialization library 3 | 4 | ```js 5 | import {} from "serializer" 6 | ``` 7 | 8 | # Configure 9 | Update `./.npmrc` if you need to change something: 10 | ```bash 11 | serializer:hex_dump = false 12 | 13 | ``` 14 | -------------------------------------------------------------------------------- /src/auth/serializer/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | // Primary class for creating operations 4 | Serializer: require('./src/serializer'), 5 | 6 | // helper functions for creating operations 7 | fp: require('./src/fast_parser'), 8 | 9 | // Low level types 10 | types: require('./src/types'), 11 | 12 | // Higher level operations (made out of generic types) 13 | ops: require('./src/operations'), 14 | 15 | // Utility that generates JSON examples 16 | template: require('./src/template'), 17 | 18 | number_utils: require('./src/number_utils'), 19 | } 20 | -------------------------------------------------------------------------------- /src/auth/serializer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serializer", 3 | "version": "1.0.0", 4 | "description": "Binary serialization and deserialization library", 5 | "main": "index.js", 6 | "config": { 7 | "hex_dump": false 8 | }, 9 | "scripts": { 10 | "test": "mocha --compilers js:babel-core/register --recursive", 11 | "test:watch": "npm test -- --watch" 12 | }, 13 | "author": "", 14 | "license": "BSD-2-Clause-FreeBSD", 15 | "dependencies": { 16 | "@exodus/bytebuffer": "git+https://github.com/ExodusMovement/bytebuffer.js.git#exodus", 17 | "bigi": "^1.4.1", 18 | "ecc": "^1.0.0" 19 | }, 20 | "devDependencies": { 21 | "assert": "^1.3.0", 22 | "babel-cli": "^6.2.0", 23 | "babel-core": "^6.2.0", 24 | "babel-preset-es2015": "^6.1.18", 25 | "mocha": "^2.3.4" 26 | }, 27 | "_babel": { 28 | "presets": [ 29 | "es2015" 30 | ] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/auth/serializer/src/ChainTypes.js: -------------------------------------------------------------------------------- 1 | var ChainTypes; 2 | 3 | module.exports = ChainTypes = {}; 4 | 5 | ChainTypes.reserved_spaces = { 6 | relative_protocol_ids: 0, 7 | protocol_ids: 1, 8 | implementation_ids: 2 9 | }; 10 | 11 | ChainTypes.operations= { 12 | vote: 0, 13 | comment: 1, 14 | transfer: 2, 15 | transfer_to_vesting: 3, 16 | withdraw_vesting: 4, 17 | limit_order_create: 5, 18 | limit_order_cancel: 6, 19 | feed_publish: 7, 20 | convert: 8, 21 | account_create: 9, 22 | account_update: 10, 23 | witness_update: 11, 24 | account_witness_vote: 12, 25 | account_witness_proxy: 13, 26 | pow: 14, 27 | custom: 15, 28 | report_over_production: 16, 29 | delete_comment: 17, 30 | custom_json: 18, 31 | comment_options: 19, 32 | set_withdraw_vesting_route: 20, 33 | limit_order_create2: 21, 34 | claim_account: 22, 35 | create_claimed_account: 23, 36 | request_account_recovery: 24, 37 | recover_account: 25, 38 | change_recovery_account: 26, 39 | escrow_transfer: 27, 40 | escrow_dispute: 28, 41 | escrow_release: 29, 42 | pow2: 30, 43 | escrow_approve: 31, 44 | transfer_to_savings: 32, 45 | transfer_from_savings: 33, 46 | cancel_transfer_from_savings: 34, 47 | custom_binary: 35, 48 | decline_voting_rights: 36, 49 | reset_account: 37, 50 | set_reset_account: 38, 51 | claim_reward_balance: 39, 52 | delegate_vesting_shares: 40, 53 | account_create_with_delegation: 41, 54 | witness_set_properties: 42, 55 | account_update2: 43, 56 | create_proposal: 44, 57 | update_proposal_votes: 45, 58 | remove_proposal: 46, 59 | claim_reward_balance2: 47, 60 | vote2: 48, 61 | smt_setup: 49, 62 | smt_setup_emissions: 50, 63 | smt_setup_ico_tier: 51, 64 | smt_set_setup_parameters: 52, 65 | smt_set_runtime_parameters: 53, 66 | smt_create: 54, 67 | smt_contribute: 55, 68 | fill_convert_request: 56, 69 | author_reward: 57, 70 | curation_reward: 58, 71 | comment_reward: 59, 72 | liquidity_reward: 60, 73 | interest: 61, 74 | fill_vesting_withdraw: 62, 75 | fill_order: 63, 76 | shutdown_witness: 64, 77 | fill_transfer_from_savings: 65, 78 | hardfork: 66, 79 | comment_payout_update: 67, 80 | return_vesting_delegation: 68, 81 | comment_benefactor_reward: 69 82 | }; 83 | 84 | //types.hpp 85 | ChainTypes.object_type = { 86 | "null": 0, 87 | base: 1, 88 | }; 89 | -------------------------------------------------------------------------------- /src/auth/serializer/src/convert.js: -------------------------------------------------------------------------------- 1 | var ByteBuffer = require('bytebuffer'); 2 | 3 | module.exports=function(type){ 4 | 5 | return {fromHex(hex) { 6 | var b = ByteBuffer.fromHex(hex, ByteBuffer.LITTLE_ENDIAN); 7 | return type.fromByteBuffer(b); 8 | }, 9 | 10 | toHex(object) { 11 | var b=toByteBuffer(type, object); 12 | return b.toHex(); 13 | }, 14 | 15 | fromBuffer(buffer){ 16 | var b = ByteBuffer.fromBinary(buffer.toString(), ByteBuffer.LITTLE_ENDIAN); 17 | return type.fromByteBuffer(b); 18 | }, 19 | 20 | toBuffer(object){ 21 | return new Buffer(toByteBuffer(type, object).toBinary(), 'binary'); 22 | }, 23 | 24 | fromBinary(string){ 25 | var b = ByteBuffer.fromBinary(string, ByteBuffer.LITTLE_ENDIAN); 26 | return type.fromByteBuffer(b); 27 | }, 28 | 29 | toBinary(object) { 30 | return toByteBuffer(type, object).toBinary(); 31 | } 32 | }; 33 | }; 34 | 35 | var toByteBuffer=function(type, object){ 36 | var b = new ByteBuffer(ByteBuffer.DEFAULT_CAPACITY, ByteBuffer.LITTLE_ENDIAN); 37 | type.appendByteBuffer(b, object); 38 | return b.copy(0, b.offset); 39 | }; 40 | -------------------------------------------------------------------------------- /src/auth/serializer/src/error_with_cause.js: -------------------------------------------------------------------------------- 1 | /** Exception nesting. */ 2 | class ErrorWithCause { 3 | 4 | constructor(message, cause){ 5 | this.message = message; 6 | if ((typeof cause !== "undefined" && cause !== null) ? cause.message : undefined) { 7 | this.message = `cause\t${cause.message}\t` + this.message; 8 | } 9 | 10 | var stack = "";//(new Error).stack 11 | if ((typeof cause !== "undefined" && cause !== null) ? cause.stack : undefined) { 12 | stack = `caused by\n\t${cause.stack}\t` + stack; 13 | } 14 | 15 | this.stack = this.message + "\n" + stack; 16 | } 17 | 18 | static throw(message, cause){ 19 | var msg = message; 20 | if ((typeof cause !== "undefined" && cause !== null) ? cause.message : undefined) { msg += `\t cause: ${cause.message} `; } 21 | if ((typeof cause !== "undefined" && cause !== null) ? cause.stack : undefined) { msg += `\n stack: ${cause.stack} `; } 22 | throw new Error(msg); 23 | } 24 | } 25 | 26 | module.exports = ErrorWithCause; 27 | -------------------------------------------------------------------------------- /src/auth/serializer/src/fast_parser.js: -------------------------------------------------------------------------------- 1 | const ecc = require("../../ecc"); 2 | const PublicKey = ecc.PublicKey; 3 | 4 | class FastParser { 5 | 6 | static fixed_data(b, len, buffer) { 7 | if (!b) { 8 | return; 9 | } 10 | if (buffer) { 11 | let data = buffer.slice(0, len).toString('binary'); 12 | b.append(data, 'binary'); 13 | while (len-- > data.length) { 14 | b.writeUint8(0); 15 | } 16 | } else { 17 | let b_copy = b.copy(b.offset, b.offset + len); 18 | b.skip(len); 19 | return new Buffer.from(b_copy.toBinary(), 'binary'); 20 | } 21 | } 22 | 23 | 24 | static public_key(b, public_key) { 25 | if (!b) { return; } 26 | if (public_key) { 27 | var buffer = public_key.toBuffer(); 28 | b.append(buffer.toString('binary'), 'binary'); 29 | return; 30 | } else { 31 | buffer = FastParser.fixed_data(b, 33); 32 | return PublicKey.fromBuffer(buffer); 33 | } 34 | } 35 | 36 | static ripemd160(b, ripemd160) { 37 | if (!b) { return; } 38 | if (ripemd160) { 39 | FastParser.fixed_data(b, 20, ripemd160); 40 | return; 41 | } else { 42 | return FastParser.fixed_data(b, 20); 43 | } 44 | } 45 | 46 | static time_point_sec(b, epoch) { 47 | if (epoch) { 48 | epoch = Math.ceil(epoch / 1000); 49 | b.writeInt32(epoch); 50 | return; 51 | } else { 52 | epoch = b.readInt32(); // fc::time_point_sec 53 | return new Date(epoch * 1000); 54 | } 55 | } 56 | } 57 | 58 | module.exports = FastParser; 59 | -------------------------------------------------------------------------------- /src/auth/serializer/src/number_utils.js: -------------------------------------------------------------------------------- 1 | const assert = require("assert"); 2 | 3 | /** 4 | Convert 12.34 with a precision of 3 into 12340 5 | 6 | @arg {number|string} number - Use strings for large numbers. This may contain one decimal but no sign 7 | @arg {number} precision - number of implied decimal places (usually causes right zero padding) 8 | @return {string} - 9 | */ 10 | function toImpliedDecimal(number, precision) { 11 | 12 | if(typeof number === "number") { 13 | assert(number <= 9007199254740991, "overflow"); 14 | number = ""+number; 15 | } else 16 | if( number.toString ) 17 | number = number.toString(); 18 | 19 | assert(typeof number === "string", "number should be an actual number or string: " + (typeof number)); 20 | number = number.trim(); 21 | assert(/^[0-9]*\.?[0-9]*$/.test(number), "Invalid decimal number " + number); 22 | 23 | let [ whole = "", decimal = ""] = number.split("."); 24 | 25 | let padding = precision - decimal.length; 26 | assert(padding >= 0, "Too many decimal digits in " + number + " to create an implied decimal of " + precision); 27 | 28 | for(let i = 0; i < padding; i++) 29 | decimal += "0"; 30 | 31 | while(whole.charAt(0) === "0") 32 | whole = whole.substring(1); 33 | 34 | return whole + decimal; 35 | } 36 | 37 | function fromImpliedDecimal(number, precision) { 38 | if(typeof number === "number") { 39 | assert(number <= 9007199254740991, "overflow"); 40 | number = ""+number; 41 | } else 42 | if( number.toString ) 43 | number = number.toString(); 44 | 45 | while(number.length < precision + 1)// 0.123 46 | number = "0" + number; 47 | 48 | // 44000 => 44.000 49 | let dec_string = number.substring(number.length - precision); 50 | return number.substring(0, number.length - precision) + 51 | (dec_string ? "." + dec_string : ""); 52 | } 53 | 54 | module.exports = { 55 | toImpliedDecimal, 56 | fromImpliedDecimal 57 | }; 58 | -------------------------------------------------------------------------------- /src/auth/serializer/src/object_id.js: -------------------------------------------------------------------------------- 1 | var Long = (require('bytebuffer')).Long; 2 | 3 | var v = require('./validation'); 4 | var DB_MAX_INSTANCE_ID = Long.fromNumber(((Math.pow(2,48))-1)); 5 | 6 | class ObjectId { 7 | 8 | constructor(space,type,instance){ 9 | this.space = space; 10 | this.type = type; 11 | this.instance = instance; 12 | var instance_string = this.instance.toString(); 13 | var object_id = `${this.space}.${this.type}.${instance_string}`; 14 | if (!v.is_digits(instance_string)) { 15 | throw new `Invalid object id ${object_id}`(); 16 | } 17 | } 18 | 19 | static fromString(value){ 20 | if ( 21 | value.space !== undefined && 22 | value.type !== undefined && 23 | value.instance !== undefined 24 | ) { 25 | return value; 26 | } 27 | var params = v.require_match( 28 | /^([0-9]+)\.([0-9]+)\.([0-9]+)$/, 29 | v.required(value, "object_id"), 30 | "object_id" 31 | ); 32 | return new ObjectId( 33 | parseInt(params[1]), 34 | parseInt(params[2]), 35 | Long.fromString(params[3]) 36 | ); 37 | } 38 | 39 | static fromLong(long){ 40 | var space = long.shiftRight(56).toInt(); 41 | var type = long.shiftRight(48).toInt() & 0x00ff; 42 | var instance = long.and(DB_MAX_INSTANCE_ID); 43 | return new ObjectId(space, type, instance); 44 | } 45 | 46 | static fromByteBuffer(b){ 47 | return ObjectId.fromLong(b.readUint64()); 48 | } 49 | 50 | toLong() { 51 | return Long.fromNumber(this.space).shiftLeft(56).or( 52 | Long.fromNumber(this.type).shiftLeft(48).or(this.instance) 53 | ); 54 | } 55 | 56 | appendByteBuffer(b){ 57 | return b.writeUint64(this.toLong()); 58 | } 59 | 60 | toString() { 61 | return `${this.space}.${this.type}.${this.instance.toString()}`; 62 | } 63 | } 64 | 65 | module.exports = ObjectId; 66 | -------------------------------------------------------------------------------- /src/auth/serializer/src/precision.js: -------------------------------------------------------------------------------- 1 | var _my; 2 | var _internal; 3 | var v = require('./validation'); 4 | var BigInteger = require('bigi'); 5 | 6 | module.exports = _my = 7 | 8 | // Result may be used for int64 types (like transfer amount). Asset's 9 | // precision is used to convert the number to a whole number with an implied 10 | // decimal place. 11 | 12 | // "1.01" with a precision of 2 returns long 101 13 | // See http://cryptocoinjs.com/modules/misc/bigi/#example 14 | {to_bigint64(number_or_string, precision, error_info = ""){ 15 | var long = _internal.to_long64(number_or_string, precision, error_info); 16 | return BigInteger(long.toString()); 17 | }, 18 | 19 | // 101 string or long with a precision of 2 returns "1.01" 20 | to_string64(number_or_string, precision, error_info = ""){ 21 | v.required(number_or_string, error_info); 22 | v.number(precision, error_info); 23 | var number_long = v.to_long(number_or_string, error_info); 24 | var string64 = _internal.decimal_precision_string( 25 | number_long, 26 | precision, 27 | error_info 28 | ); 29 | v.no_overflow64(string64, error_info); 30 | return string64; 31 | } 32 | }; 33 | 34 | // _internal is for low-level transaction code 35 | module.exports._internal = _internal = 36 | 37 | // Warning: Long operations may over-flow without detection 38 | {to_long64(number_or_string, precision, error_info = ""){ 39 | v.required(number_or_string, "number_or_string " + error_info); 40 | v.required(precision, "precision " + error_info); 41 | return v.to_long(_internal.decimal_precision_string( 42 | number_or_string, 43 | precision, 44 | error_info 45 | )); 46 | }, 47 | 48 | decimal_precision_string(number, precision, error_info = ""){ 49 | v.required(number, "number " + error_info); 50 | v.required(precision, "precision " + error_info); 51 | 52 | var number_string = v.to_string(number); 53 | number_string = number_string.trim(); 54 | precision = v.to_number(precision); 55 | 56 | // remove leading zeros (not suffixing) 57 | var number_parts = number_string.match(/^-?0*([0-9]*)\.?([0-9]*)$/); 58 | if (!number_parts) { 59 | throw new Error(`Invalid number: ${number_string} ${error_info}`); 60 | } 61 | 62 | var sign = number_string.charAt(0) === '-' ? '-' : ''; 63 | var int_part = number_parts[1]; 64 | var decimal_part = number_parts[2]; 65 | if (!decimal_part) { decimal_part = ""; } 66 | 67 | // remove trailing zeros 68 | while (/0$/.test(decimal_part)) { 69 | decimal_part = decimal_part.substring(0, decimal_part.length - 1); 70 | } 71 | 72 | var zero_pad_count = precision - decimal_part.length; 73 | if (zero_pad_count < 0) { 74 | throw new Error(`overflow, up to ${precision} decimals may be used ${error_info}`); 75 | } 76 | 77 | if (sign === "-" && !/[1-9]/.test(int_part + decimal_part)) { sign = ""; } 78 | if (int_part === "") { int_part = "0"; } 79 | for (var i = 0; 0 < zero_pad_count ? i < zero_pad_count : i > zero_pad_count; 0 < zero_pad_count ? i++ : i++) { 80 | decimal_part += "0"; 81 | } 82 | 83 | return sign + int_part + decimal_part; 84 | } 85 | }; 86 | -------------------------------------------------------------------------------- /src/auth/serializer/src/serializer.js: -------------------------------------------------------------------------------- 1 | var ByteBuffer = require('bytebuffer'); 2 | var EC = require('./error_with_cause'); 3 | 4 | const HEX_DUMP = process.env.npm_config__graphene_serializer_hex_dump 5 | 6 | class Serializer { 7 | 8 | constructor(operation_name, types) { 9 | this.operation_name = operation_name 10 | this.types = types 11 | if(this.types) 12 | this.keys = Object.keys(this.types) 13 | 14 | Serializer.printDebug = true 15 | } 16 | 17 | fromByteBuffer(b) { 18 | var object = {}; 19 | var field = null; 20 | try { 21 | var iterable = this.keys; 22 | for (var i = 0, field; i < iterable.length; i++) { 23 | field = iterable[i]; 24 | var type = this.types[field]; 25 | try { 26 | if (HEX_DUMP) { 27 | if (type.operation_name) { 28 | console.error(type.operation_name); 29 | } else { 30 | var o1 = b.offset; 31 | type.fromByteBuffer(b); 32 | var o2 = b.offset; 33 | b.offset = o1; 34 | //b.reset() 35 | var _b = b.copy(o1, o2); 36 | console.error( 37 | `${this.operation_name}.${field}\t`, 38 | _b.toHex() 39 | ); 40 | } 41 | } 42 | object[field] = type.fromByteBuffer(b); 43 | } catch (e) { 44 | if(Serializer.printDebug) { 45 | console.error(`Error reading ${this.operation_name}.${field} in data:`); 46 | b.printDebug(); 47 | } 48 | throw e; 49 | } 50 | } 51 | 52 | } catch (error) { 53 | EC.throw(this.operation_name+'.'+field, error); 54 | } 55 | 56 | return object; 57 | } 58 | 59 | appendByteBuffer(b, object) { 60 | var field = null; 61 | try { 62 | var iterable = this.keys; 63 | for (var i = 0, field; i < iterable.length; i++) { 64 | field = iterable[i]; 65 | var type = this.types[field]; 66 | type.appendByteBuffer(b, object[field]); 67 | } 68 | 69 | } catch (error) { 70 | try { 71 | EC.throw(this.operation_name+'.'+field+" = "+ JSON.stringify(object[field]), error); 72 | } catch (e) { // circular ref 73 | EC.throw(this.operation_name+'.'+field+" = "+ object[field], error); 74 | } 75 | } 76 | return; 77 | } 78 | 79 | fromObject(serialized_object){ 80 | var result = {}; 81 | var field = null; 82 | try { 83 | var iterable = this.keys; 84 | for (var i = 0, field; i < iterable.length; i++) { 85 | field = iterable[i]; 86 | var type = this.types[field]; 87 | var value = serialized_object[field]; 88 | //DEBUG value = value.resolve if value.resolve 89 | //DEBUG console.log('... value',field,value) 90 | var object = type.fromObject(value); 91 | result[field] = object; 92 | } 93 | 94 | } catch (error) { 95 | EC.throw(this.operation_name+'.'+field, error); 96 | } 97 | 98 | return result; 99 | } 100 | 101 | /** 102 | @arg {boolean} [debug.use_default = false] - more template friendly 103 | @arg {boolean} [debug.annotate = false] - add user-friendly information 104 | */ 105 | toObject(serialized_object = {}, debug = { use_default: false, annotate: false }){ 106 | var result = {}; 107 | var field = null; 108 | try { 109 | if( ! this.types ) 110 | return result; 111 | 112 | var iterable = this.keys; 113 | for (var i = 0, field; i < iterable.length; i++) { 114 | field = iterable[i]; 115 | var type = this.types[field]; 116 | var object = type.toObject(((typeof serialized_object !== "undefined" && serialized_object !== null) ? serialized_object[field] : undefined), debug); 117 | result[field] = object; 118 | if(HEX_DUMP) { 119 | var b = new ByteBuffer(ByteBuffer.DEFAULT_CAPACITY, ByteBuffer.LITTLE_ENDIAN); 120 | let has_value = typeof serialized_object !== "undefined" && serialized_object !== null 121 | if(has_value) { 122 | let value = serialized_object[field] 123 | if(value) 124 | type.appendByteBuffer(b, value); 125 | } 126 | b = b.copy(0, b.offset); 127 | console.error( 128 | this.operation_name+'.'+field, 129 | b.toHex() 130 | ); 131 | } 132 | } 133 | } catch (error) { 134 | EC.throw(this.operation_name+'.'+field, error); 135 | } 136 | 137 | return result; 138 | } 139 | 140 | /** Sort by the first element in a operation */ 141 | compare(a, b) { 142 | 143 | let first_key = this.keys[0] 144 | let first_type = this.types[first_key] 145 | 146 | let valA = a[first_key] 147 | let valB = b[first_key] 148 | 149 | if(first_type.compare) 150 | return first_type.compare(valA, valB) 151 | 152 | if(typeof valA === "number" && typeof valB === "number") 153 | return valA - valB 154 | 155 | let encoding 156 | if(Buffer.isBuffer(valA) && Buffer.isBuffer(valB)) { 157 | // A binary string compare does not work. If localeCompare is well supported that could replace HEX. Performanance is very good so comparing HEX works. 158 | encoding = "hex" 159 | } 160 | 161 | let strA = valA.toString(encoding) 162 | let strB = valB.toString(encoding) 163 | return strA > strB ? 1 : strA < strB ? -1 : 0 164 | } 165 | 166 | // 167 | 168 | fromHex(hex) { 169 | var b = ByteBuffer.fromHex(hex, ByteBuffer.LITTLE_ENDIAN); 170 | return this.fromByteBuffer(b); 171 | } 172 | 173 | fromBuffer(buffer){ 174 | var b = ByteBuffer.fromBinary(buffer.toString("binary"), ByteBuffer.LITTLE_ENDIAN); 175 | return this.fromByteBuffer(b); 176 | } 177 | 178 | toHex(object) { 179 | // return this.toBuffer(object).toString("hex") 180 | var b=this.toByteBuffer(object); 181 | return b.toHex(); 182 | } 183 | 184 | toByteBuffer(object) { 185 | var b = new ByteBuffer(ByteBuffer.DEFAULT_CAPACITY, ByteBuffer.LITTLE_ENDIAN); 186 | this.appendByteBuffer(b, object); 187 | return b.copy(0, b.offset); 188 | } 189 | 190 | toBuffer(object){ 191 | return new Buffer.from(this.toByteBuffer(object).toBinary(), 'binary'); 192 | } 193 | } 194 | 195 | module.exports = Serializer 196 | -------------------------------------------------------------------------------- /src/auth/serializer/src/template.js: -------------------------------------------------------------------------------- 1 | 2 | /** Console print any transaction object with zero default values. */ 3 | module.exports = function template(op) { 4 | 5 | var object = op.toObject(void 0, {use_default: true, annotate: true}) 6 | 7 | // visual (with descriptions) 8 | console.error(JSON.stringify(object,null,4)) 9 | 10 | // usable in a copy-paste 11 | 12 | object = op.toObject(void 0, {use_default: true, annotate: false}) 13 | 14 | // copy-paste one-lineer 15 | console.error(JSON.stringify(object)) 16 | } 17 | -------------------------------------------------------------------------------- /src/auth/serializer/src/validation.js: -------------------------------------------------------------------------------- 1 | var _my; 2 | var is_empty; 3 | var is_digits; 4 | var to_number; 5 | var require_match; 6 | var require_object_id; 7 | var require_object_type; 8 | var get_instance; 9 | var require_relative_type; 10 | var get_relative_instance; 11 | var require_protocol_type; 12 | var get_protocol_instance; 13 | var get_protocol_type; 14 | var require_implementation_type; 15 | var get_implementation_instance; 16 | var Long = require('bytebuffer').Long; 17 | // var BigInteger = require('bigi'); 18 | 19 | var chain_types = require('./ChainTypes'); 20 | 21 | var MAX_SAFE_INT = 9007199254740991; 22 | var MIN_SAFE_INT =-9007199254740991; 23 | 24 | /** 25 | Most validations are skipped and the value returned unchanged when an empty string, null, or undefined is encountered (except "required"). 26 | 27 | Validations support a string format for dealing with large numbers. 28 | */ 29 | module.exports = _my = { 30 | 31 | is_empty: is_empty=function(value){ 32 | return value === null || value === undefined; 33 | }, 34 | 35 | required(value, field_name=""){ 36 | if (is_empty(value) ){ 37 | throw new Error(`value required ${field_name} ${value}`); 38 | } 39 | return value; 40 | }, 41 | 42 | require_long(value, field_name=""){ 43 | if (!Long.isLong(value)) { 44 | throw new Error(`Long value required ${field_name} ${value}`); 45 | } 46 | return value; 47 | }, 48 | 49 | string(value){ 50 | if (is_empty(value) ){ return value; } 51 | if (typeof value !== "string") { 52 | throw new Error(`string required: ${value}`); 53 | } 54 | return value; 55 | }, 56 | 57 | number(value){ 58 | if (is_empty(value) ){ return value; } 59 | if (typeof value !== "number") { 60 | throw new Error(`number required: ${value}`); 61 | } 62 | return value; 63 | }, 64 | 65 | whole_number(value, field_name=""){ 66 | if (is_empty(value) ){ return value; } 67 | if (/\./.test(value) ){ 68 | throw new Error(`whole number required ${field_name} ${value}`); 69 | } 70 | return value; 71 | }, 72 | 73 | unsigned(value, field_name=""){ 74 | if (is_empty(value) ){ return value; } 75 | if (/-/.test(value) ){ 76 | throw new Error(`unsigned required ${field_name} ${value}`); 77 | } 78 | return value; 79 | }, 80 | 81 | is_digits: is_digits=function(value){ 82 | if (typeof value === "numeric") { return true; } 83 | return /^[0-9]+$/.test(value); 84 | }, 85 | 86 | to_number: to_number=function(value, field_name=""){ 87 | if (is_empty(value) ){ return value; } 88 | _my.no_overflow53(value, field_name); 89 | var int_value = (() => { 90 | if (typeof value === "number") { 91 | return value; 92 | } else { 93 | return parseInt(value); 94 | } 95 | })(); 96 | return int_value; 97 | }, 98 | 99 | to_long(value, field_name=""){ 100 | if (is_empty(value) ){ return value; } 101 | if (Long.isLong(value) ){ return value; } 102 | 103 | _my.no_overflow64(value, field_name); 104 | if (typeof value === "number") { 105 | value = ""+value; 106 | } 107 | return Long.fromString(value); 108 | }, 109 | 110 | to_string(value, field_name=""){ 111 | if (is_empty(value) ){ return value; } 112 | if (typeof value === "string") { return value; } 113 | if (typeof value === "number") { 114 | _my.no_overflow53(value, field_name); 115 | return ""+value; 116 | } 117 | if (Long.isLong(value) ){ 118 | return value.toString(); 119 | } 120 | throw `unsupported type ${field_name}: (${typeof value}) ${value}`; 121 | }, 122 | 123 | require_test(regex, value, field_name=""){ 124 | if (is_empty(value) ){ return value; } 125 | if (!regex.test(value)) { 126 | throw new Error(`unmatched ${regex} ${field_name} ${value}`); 127 | } 128 | return value; 129 | }, 130 | 131 | require_match: require_match=function(regex, value, field_name=""){ 132 | if (is_empty(value) ){ return value; } 133 | var match = value.match(regex); 134 | if (match === null) { 135 | throw new Error(`unmatched ${regex} ${field_name} ${value}`); 136 | } 137 | return match; 138 | }, 139 | 140 | // require_object_id: require_object_id=function(value, field_name){ 141 | // return require_match( 142 | // /^([0-9]+)\.([0-9]+)\.([0-9]+)$/, 143 | // value, 144 | // field_name 145 | // ); 146 | // }, 147 | 148 | // Does not support over 53 bits 149 | require_range(min,max,value, field_name=""){ 150 | if (is_empty(value) ){ return value; } 151 | var number = to_number(value); 152 | if (value < min || value > max) { 153 | throw new Error(`out of range ${value} ${field_name} ${value}`); 154 | } 155 | return value; 156 | }, 157 | 158 | require_object_type: require_object_type=function( 159 | reserved_spaces = 1, type, value, 160 | field_name="" 161 | ){ 162 | if (is_empty(value) ){ return value; } 163 | var object_type = chain_types.object_type[type]; 164 | if (!object_type) { 165 | throw new Error(`Unknown object type: ${type}, ${field_name}, ${value}`); 166 | } 167 | var re = new RegExp(`${reserved_spaces}\.${object_type}\.[0-9]+$`); 168 | if (!re.test(value)) { 169 | throw new Error(`Expecting ${type} in format `+ `${reserved_spaces}.${object_type}.[0-9]+ `+ `instead of ${value} ${field_name} ${value}`); 170 | } 171 | return value; 172 | }, 173 | 174 | get_instance: get_instance=function(reserve_spaces, type, value, field_name){ 175 | if (is_empty(value) ){ return value; } 176 | require_object_type(reserve_spaces, type, value, field_name); 177 | return to_number(value.split('.')[2]); 178 | }, 179 | 180 | require_relative_type: require_relative_type=function(type, value, field_name){ 181 | require_object_type(0, type, value, field_name); 182 | return value; 183 | }, 184 | 185 | get_relative_instance: get_relative_instance=function(type, value, field_name){ 186 | if (is_empty(value) ){ return value; } 187 | require_object_type(0, type, value, field_name); 188 | return to_number(value.split('.')[2]); 189 | }, 190 | 191 | require_protocol_type: require_protocol_type=function(type, value, field_name){ 192 | require_object_type(1, type, value, field_name); 193 | return value; 194 | }, 195 | 196 | get_protocol_instance: get_protocol_instance=function(type, value, field_name){ 197 | if (is_empty(value) ){ return value; } 198 | require_object_type(1, type, value, field_name); 199 | return to_number(value.split('.')[2]); 200 | }, 201 | 202 | get_protocol_type: get_protocol_type=function(value, field_name){ 203 | if (is_empty(value) ){ return value; } 204 | require_object_id(value, field_name); 205 | var values = value.split('.'); 206 | return to_number(values[1]); 207 | }, 208 | 209 | get_protocol_type_name(value, field_name){ 210 | if (is_empty(value) ){ return value; } 211 | var type_id = get_protocol_type(value, field_name); 212 | return (Object.keys(chain_types.object_type))[type_id]; 213 | }, 214 | 215 | require_implementation_type: require_implementation_type=function(type, value, field_name){ 216 | require_object_type(2, type, value, field_name); 217 | return value; 218 | }, 219 | 220 | get_implementation_instance: get_implementation_instance=function(type, value, field_name){ 221 | if (is_empty(value) ){ return value; } 222 | require_object_type(2, type, value, field_name); 223 | return to_number(value.split('.')[2]); 224 | }, 225 | 226 | // signed / unsigned decimal 227 | no_overflow53(value, field_name=""){ 228 | if (typeof value === "number") { 229 | if (value > MAX_SAFE_INT || value < MIN_SAFE_INT) { 230 | throw new Error(`overflow ${field_name} ${value}`); 231 | } 232 | return; 233 | } 234 | if (typeof value === "string") { 235 | var int = parseInt(value); 236 | if (value > MAX_SAFE_INT || value < MIN_SAFE_INT) { 237 | throw new Error(`overflow ${field_name} ${value}`); 238 | } 239 | return; 240 | } 241 | if (Long.isLong(value) ){ 242 | // typeof value.toInt() is 'number' 243 | _my.no_overflow53(value.toInt(), field_name); 244 | return; 245 | } 246 | throw `unsupported type ${field_name}: (${typeof value}) ${value}`; 247 | }, 248 | 249 | // signed / unsigned whole numbers only 250 | no_overflow64(value, field_name=""){ 251 | // https://github.com/dcodeIO/Long.js/issues/20 252 | if (Long.isLong(value) ){ return; } 253 | 254 | // BigInteger#isBigInteger https://github.com/cryptocoinjs/bigi/issues/20 255 | if (value.t !== undefined && value.s !== undefined) { 256 | _my.no_overflow64(value.toString(), field_name); 257 | return; 258 | } 259 | 260 | if (typeof value === "string") { 261 | // remove leading zeros, will cause a false positive 262 | value = value.replace(/^0+/,''); 263 | // remove trailing zeros 264 | while (/0$/.test(value) ){ 265 | value = value.substring(0, value.length - 1); 266 | } 267 | if (/\.$/.test(value) ){ 268 | // remove trailing dot 269 | value = value.substring(0, value.length - 1); 270 | } 271 | if (value === "") { value = "0"; } 272 | var long_string = Long.fromString(value).toString(); 273 | if (long_string !== value.trim()) { 274 | throw new Error(`overflow ${field_name} ${value}`); 275 | } 276 | return; 277 | } 278 | if (typeof value === "number") { 279 | if (value > MAX_SAFE_INT || value < MIN_SAFE_INT) { 280 | throw new Error(`overflow ${field_name} ${value}`); 281 | } 282 | return; 283 | } 284 | 285 | throw `unsupported type ${field_name}: (${typeof value}) ${value}`; 286 | } 287 | }; 288 | -------------------------------------------------------------------------------- /src/broadcast/helpers.js: -------------------------------------------------------------------------------- 1 | import api from '../api'; 2 | 3 | exports = module.exports = steemBroadcast => { 4 | steemBroadcast.addAccountAuth = ({ signingKey, username, authorizedUsername, role = 'posting', weight }, cb) => { 5 | api.getAccounts([username], (err, [userAccount]) => { 6 | if (err) { return cb(new Error(err), null); } 7 | if (!userAccount) { return cb(new Error('Invalid account name'), null); } 8 | 9 | const updatedAuthority = userAccount[role]; 10 | 11 | /** Release callback if the account already exist in the account_auths array */ 12 | const authorizedAccounts = updatedAuthority.account_auths.map(auth => auth[0]); 13 | const hasAuthority = authorizedAccounts.indexOf(authorizedUsername) !== -1; 14 | if (hasAuthority) { 15 | return cb(null, null); 16 | } 17 | 18 | /** Use weight_thresold as default weight */ 19 | weight = weight || userAccount[role].weight_threshold; 20 | updatedAuthority.account_auths.push([authorizedUsername, weight]); 21 | const owner = role === 'owner' ? updatedAuthority : undefined; 22 | const active = role === 'active' ? updatedAuthority : undefined; 23 | const posting = role === 'posting' ? updatedAuthority : undefined; 24 | 25 | /** Add authority on user account */ 26 | steemBroadcast.accountUpdate( 27 | signingKey, 28 | userAccount.name, 29 | owner, 30 | active, 31 | posting, 32 | userAccount.memo_key, 33 | userAccount.json_metadata, 34 | cb 35 | ); 36 | }); 37 | }; 38 | 39 | steemBroadcast.removeAccountAuth = ({ signingKey, username, authorizedUsername, role = 'posting' }, cb) => { 40 | api.getAccounts([username], (err, [userAccount]) => { 41 | if (err) { return cb(new Error(err), null); } 42 | if (!userAccount) { return cb(new Error('Invalid account name'), null); } 43 | 44 | const updatedAuthority = userAccount[role]; 45 | const totalAuthorizedUser = updatedAuthority.account_auths.length; 46 | for (let i = 0; i < totalAuthorizedUser; i++) { 47 | const user = updatedAuthority.account_auths[i]; 48 | if (user[0] === authorizedUsername) { 49 | updatedAuthority.account_auths.splice(i, 1); 50 | break; 51 | } 52 | } 53 | 54 | /** Release callback if the account does not exist in the account_auths array */ 55 | if (totalAuthorizedUser === updatedAuthority.account_auths.length) { 56 | return cb(null, null); 57 | } 58 | 59 | const owner = role === 'owner' ? updatedAuthority : undefined; 60 | const active = role === 'active' ? updatedAuthority : undefined; 61 | const posting = role === 'posting' ? updatedAuthority : undefined; 62 | 63 | steemBroadcast.accountUpdate( 64 | signingKey, 65 | userAccount.name, 66 | owner, 67 | active, 68 | posting, 69 | userAccount.memo_key, 70 | userAccount.json_metadata, 71 | cb 72 | ); 73 | }); 74 | }; 75 | 76 | steemBroadcast.addKeyAuth = ({ signingKey, username, authorizedKey, role = 'posting', weight }, cb) => { 77 | api.getAccounts([username], (err, [userAccount]) => { 78 | if (err) { return cb(new Error(err), null); } 79 | if (!userAccount) { return cb(new Error('Invalid account name'), null); } 80 | 81 | const updatedAuthority = userAccount[role]; 82 | 83 | /** Release callback if the key already exist in the key_auths array */ 84 | const authorizedKeys = updatedAuthority.key_auths.map(auth => auth[0]); 85 | const hasAuthority = authorizedKeys.indexOf(authorizedKey) !== -1; 86 | if (hasAuthority) { 87 | return cb(null, null); 88 | } 89 | 90 | /** Use weight_thresold as default weight */ 91 | weight = weight || userAccount[role].weight_threshold; 92 | updatedAuthority.key_auths.push([authorizedKey, weight]); 93 | const owner = role === 'owner' ? updatedAuthority : undefined; 94 | const active = role === 'active' ? updatedAuthority : undefined; 95 | const posting = role === 'posting' ? updatedAuthority : undefined; 96 | 97 | /** Add authority on user account */ 98 | steemBroadcast.accountUpdate( 99 | signingKey, 100 | userAccount.name, 101 | owner, 102 | active, 103 | posting, 104 | userAccount.memo_key, 105 | userAccount.json_metadata, 106 | cb 107 | ); 108 | }); 109 | }; 110 | 111 | steemBroadcast.removeKeyAuth = ({ signingKey, username, authorizedKey, role = 'posting' }, cb) => { 112 | api.getAccounts([username], (err, [userAccount]) => { 113 | if (err) { return cb(new Error(err), null); } 114 | if (!userAccount) { return cb(new Error('Invalid account name'), null); } 115 | 116 | const updatedAuthority = userAccount[role]; 117 | const totalAuthorizedKey = updatedAuthority.key_auths.length; 118 | for (let i = 0; i < totalAuthorizedKey; i++) { 119 | const user = updatedAuthority.key_auths[i]; 120 | if (user[0] === authorizedKey) { 121 | updatedAuthority.key_auths.splice(i, 1); 122 | break; 123 | } 124 | } 125 | 126 | /** Release callback if the key does not exist in the key_auths array */ 127 | if (totalAuthorizedKey === updatedAuthority.key_auths.length) { 128 | return cb(null, null); 129 | } 130 | 131 | const owner = role === 'owner' ? updatedAuthority : undefined; 132 | const active = role === 'active' ? updatedAuthority : undefined; 133 | const posting = role === 'posting' ? updatedAuthority : undefined; 134 | 135 | steemBroadcast.accountUpdate( 136 | signingKey, 137 | userAccount.name, 138 | owner, 139 | active, 140 | posting, 141 | userAccount.memo_key, 142 | userAccount.json_metadata, 143 | cb 144 | ); 145 | }); 146 | }; 147 | }; 148 | -------------------------------------------------------------------------------- /src/broadcast/index.js: -------------------------------------------------------------------------------- 1 | import Promise from 'bluebird'; 2 | import newDebug from 'debug'; 3 | 4 | import broadcastHelpers from './helpers'; 5 | import formatterFactory from '../formatter'; 6 | import operations from './operations'; 7 | import steemApi from '../api'; 8 | import steemAuth from '../auth'; 9 | import { camelCase } from '../utils'; 10 | 11 | const debug = newDebug('steem:broadcast'); 12 | const noop = function() {} 13 | const formatter = formatterFactory(steemApi); 14 | 15 | const steemBroadcast = {}; 16 | 17 | // Base transaction logic ----------------------------------------------------- 18 | 19 | /** 20 | * Sign and broadcast transactions on the steem network 21 | * @param {Object} tx - Transaction object 22 | * @param {Object|String} privKeys - Private keys or key string 23 | * @param {Function} [callback] - Optional callback function 24 | * @return {Promise} - Returns a promise if no callback is provided 25 | */ 26 | steemBroadcast.send = function steemBroadcast$send(tx, privKeys, callback) { 27 | const resultP = steemBroadcast._prepareTransaction(tx) 28 | .then((transaction) => { 29 | debug( 30 | 'Signing transaction (transaction, transaction.operations)', 31 | transaction, transaction.operations 32 | ); 33 | return Promise.join( 34 | transaction, 35 | steemAuth.signTransaction(transaction, privKeys) 36 | ); 37 | }) 38 | .spread((transaction, signedTransaction) => { 39 | debug( 40 | 'Broadcasting transaction (transaction, transaction.operations)', 41 | transaction, transaction.operations 42 | ); 43 | return steemApi.broadcastTransactionSynchronousAsync( 44 | signedTransaction 45 | ).then((result) => { 46 | return Object.assign({}, result, signedTransaction); 47 | }); 48 | }); 49 | 50 | if (callback) { 51 | resultP.nodeify(callback); 52 | return undefined; 53 | } else { 54 | return resultP; 55 | } 56 | }; 57 | 58 | steemBroadcast._prepareTransaction = function steemBroadcast$_prepareTransaction(tx) { 59 | const propertiesP = steemApi.getDynamicGlobalPropertiesAsync(); 60 | return propertiesP 61 | .then((properties) => { 62 | // Set defaults on the transaction 63 | const chainDate = new Date(properties.time + 'Z'); 64 | const refBlockNum = (properties.last_irreversible_block_num - 1) & 0xFFFF; 65 | return steemApi.getBlockHeaderAsync(properties.last_irreversible_block_num).then((block) => { 66 | const headBlockId = block ? block.previous : '0000000000000000000000000000000000000000'; 67 | return Object.assign({ 68 | ref_block_num: refBlockNum, 69 | ref_block_prefix: new Buffer.from(headBlockId, 'hex').readUInt32LE(4), 70 | expiration: new Date( 71 | chainDate.getTime() + 72 | 600 * 1000 73 | ), 74 | }, tx); 75 | }); 76 | }); 77 | }; 78 | 79 | // Generated wrapper ---------------------------------------------------------- 80 | 81 | // Generate operations from operations.json 82 | operations.forEach((operation) => { 83 | const operationName = camelCase(operation.operation); 84 | const operationParams = operation.params || []; 85 | 86 | const useCommentPermlink = 87 | operationParams.indexOf('parent_author') !== -1 && 88 | operationParams.indexOf('parent_permlink') !== -1; 89 | 90 | steemBroadcast[`${operationName}With`] = 91 | function steemBroadcast$specializedSendWith(wif, options, callback) { 92 | debug(`Sending operation "${operationName}" with`, {options, callback}); 93 | const keys = {}; 94 | if (operation.roles && operation.roles.length) { 95 | keys[operation.roles[0]] = wif; // TODO - Automatically pick a role? Send all? 96 | } 97 | return steemBroadcast.send({ 98 | extensions: [], 99 | operations: [[operation.operation, Object.assign( 100 | {}, 101 | options, 102 | options.json_metadata != null ? { 103 | json_metadata: toString(options.json_metadata), 104 | } : {}, 105 | useCommentPermlink && options.permlink == null ? { 106 | permlink: formatter.commentPermlink(options.parent_author, options.parent_permlink), 107 | } : {} 108 | )]], 109 | }, keys, callback); 110 | }; 111 | 112 | steemBroadcast[operationName] = 113 | function steemBroadcast$specializedSend(wif, ...args) { 114 | debug(`Parsing operation "${operationName}" with`, {args}); 115 | const options = operationParams.reduce((memo, param, i) => { 116 | memo[param] = args[i]; // eslint-disable-line no-param-reassign 117 | return memo; 118 | }, {}); 119 | // Check if the last argument is a function (callback) 120 | let callback = null; 121 | if (args.length > operationParams.length && typeof args[operationParams.length] === 'function') { 122 | callback = args[operationParams.length]; 123 | } 124 | return steemBroadcast[`${operationName}With`](wif, options, callback); 125 | }; 126 | }); 127 | 128 | const toString = obj => typeof obj === 'object' ? JSON.stringify(obj) : obj; 129 | broadcastHelpers(steemBroadcast); 130 | 131 | // For backwards compatibility, maintain the Async versions 132 | Promise.promisifyAll(steemBroadcast); 133 | 134 | exports = module.exports = steemBroadcast; 135 | -------------------------------------------------------------------------------- /src/browser.js: -------------------------------------------------------------------------------- 1 | const api = require("./api"); 2 | const auth = require("./auth"); 3 | const memo = require("./auth/memo"); 4 | const broadcast = require("./broadcast"); 5 | const config = require("./config"); 6 | const formatter = require("./formatter")(api); 7 | const utils = require("./utils"); 8 | 9 | const steem = { 10 | api, 11 | auth, 12 | memo, 13 | broadcast, 14 | config, 15 | formatter, 16 | utils 17 | }; 18 | 19 | if (typeof window !== "undefined") { 20 | window.steem = steem; 21 | } 22 | 23 | if (typeof global !== "undefined") { 24 | global.steem = steem; 25 | } 26 | 27 | exports = module.exports = steem; 28 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | import each from 'lodash/each'; 2 | const defaultConfig = require('../config.json'); 3 | 4 | class Config { 5 | constructor(c) { 6 | each(c, (value, key) => { 7 | this[key] = value; 8 | }); 9 | } 10 | 11 | get(k) { 12 | return this[k]; 13 | } 14 | 15 | set(k, v) { 16 | this[k] = v; 17 | } 18 | } 19 | 20 | module.exports = new Config(defaultConfig); 21 | if(typeof module.exports.Config !== 'undefined') { 22 | throw new Error("default config.json file may not contain a property 'Config'"); 23 | } 24 | module.exports.Config = Config; 25 | -------------------------------------------------------------------------------- /src/formatter.js: -------------------------------------------------------------------------------- 1 | import get from "lodash/get"; 2 | import { key_utils } from "./auth/ecc"; 3 | 4 | module.exports = steemAPI => { 5 | function numberWithCommas(x) { 6 | return x.replace(/\B(?=(\d{3})+(?!\d))/g, ","); 7 | } 8 | 9 | function vestingSteem(account, gprops) { 10 | const vests = parseFloat(account.vesting_shares.split(" ")[0]); 11 | const total_vests = parseFloat(gprops.total_vesting_shares.split(" ")[0]); 12 | const total_vest_steem = parseFloat( 13 | gprops.total_vesting_fund_steem.split(" ")[0] 14 | ); 15 | const vesting_steemf = total_vest_steem * (vests / total_vests); 16 | return vesting_steemf; 17 | } 18 | 19 | function processOrders(open_orders, assetPrecision) { 20 | const sbdOrders = !open_orders 21 | ? 0 22 | : open_orders.reduce((o, order) => { 23 | if (order.sell_price.base.indexOf("SBD") !== -1) { 24 | o += order.for_sale; 25 | } 26 | return o; 27 | }, 0) / assetPrecision; 28 | 29 | const steemOrders = !open_orders 30 | ? 0 31 | : open_orders.reduce((o, order) => { 32 | if (order.sell_price.base.indexOf("STEEM") !== -1) { 33 | o += order.for_sale; 34 | } 35 | return o; 36 | }, 0) / assetPrecision; 37 | 38 | return { steemOrders, sbdOrders }; 39 | } 40 | 41 | function calculateSaving(savings_withdraws) { 42 | let savings_pending = 0; 43 | let savings_sbd_pending = 0; 44 | savings_withdraws.forEach(withdraw => { 45 | const [amount, asset] = withdraw.amount.split(" "); 46 | if (asset === "STEEM") savings_pending += parseFloat(amount); 47 | else { 48 | if (asset === "SBD") savings_sbd_pending += parseFloat(amount); 49 | } 50 | }); 51 | return { savings_pending, savings_sbd_pending }; 52 | } 53 | 54 | function pricePerSteem(feed_price) { 55 | let price_per_steem = undefined; 56 | const { base, quote } = feed_price; 57 | if (/ SBD$/.test(base) && / STEEM$/.test(quote)) { 58 | price_per_steem = parseFloat(base.split(" ")[0]) / parseFloat(quote.split(" ")[0]); 59 | } 60 | return price_per_steem; 61 | } 62 | 63 | function estimateAccountValue( 64 | account, 65 | { gprops, feed_price, open_orders, savings_withdraws, vesting_steem } = {} 66 | ) { 67 | const promises = []; 68 | const username = account.name; 69 | const assetPrecision = 1000; 70 | let orders, savings; 71 | 72 | if (!vesting_steem || !feed_price) { 73 | if (!gprops || !feed_price) { 74 | promises.push( 75 | steemAPI.getStateAsync(`/@${username}`).then(data => { 76 | gprops = data.props; 77 | feed_price = data.feed_price; 78 | vesting_steem = vestingSteem(account, gprops); 79 | }) 80 | ); 81 | } else { 82 | vesting_steem = vestingSteem(account, gprops); 83 | } 84 | } 85 | 86 | if (!open_orders) { 87 | promises.push( 88 | steemAPI.getOpenOrdersAsync(username).then(open_orders => { 89 | orders = processOrders(open_orders, assetPrecision); 90 | }) 91 | ); 92 | } else { 93 | orders = processOrders(open_orders, assetPrecision); 94 | } 95 | 96 | if (!savings_withdraws) { 97 | promises.push( 98 | steemAPI 99 | .getSavingsWithdrawFromAsync(username) 100 | .then(savings_withdraws => { 101 | savings = calculateSaving(savings_withdraws); 102 | }) 103 | ); 104 | } else { 105 | savings = calculateSaving(savings_withdraws); 106 | } 107 | 108 | return Promise.all(promises).then(() => { 109 | let price_per_steem = pricePerSteem(feed_price); 110 | 111 | const savings_balance = account.savings_balance; 112 | const savings_sbd_balance = account.savings_sbd_balance; 113 | const balance_steem = parseFloat(account.balance.split(" ")[0]); 114 | const saving_balance_steem = parseFloat(savings_balance.split(" ")[0]); 115 | const sbd_balance = parseFloat(account.sbd_balance); 116 | const sbd_balance_savings = parseFloat(savings_sbd_balance.split(" ")[0]); 117 | 118 | let conversionValue = 0; 119 | const currentTime = new Date().getTime(); 120 | (account.other_history || []).reduce((out, item) => { 121 | if (get(item, [1, "op", 0], "") !== "convert") return out; 122 | 123 | const timestamp = new Date(get(item, [1, "timestamp"])).getTime(); 124 | const finishTime = timestamp + 86400000 * 3.5; // add 3.5day conversion delay 125 | if (finishTime < currentTime) return out; 126 | 127 | const amount = parseFloat( 128 | get(item, [1, "op", 1, "amount"]).replace(" SBD", "") 129 | ); 130 | conversionValue += amount; 131 | }, []); 132 | 133 | const total_sbd = 134 | sbd_balance + 135 | sbd_balance_savings + 136 | savings.savings_sbd_pending + 137 | orders.sbdOrders + 138 | conversionValue; 139 | 140 | const total_steem = 141 | vesting_steem + 142 | balance_steem + 143 | saving_balance_steem + 144 | savings.savings_pending + 145 | orders.steemOrders; 146 | 147 | return (total_steem * price_per_steem + total_sbd).toFixed(2); 148 | }); 149 | } 150 | 151 | function createSuggestedPassword() { 152 | const PASSWORD_LENGTH = 32; 153 | const privateKey = key_utils.get_random_key(); 154 | return privateKey.toWif().substring(3, 3 + PASSWORD_LENGTH); 155 | } 156 | 157 | return { 158 | reputation: function(reputation, decimal_places = 0) { 159 | if (reputation == 0) return 25; 160 | if (!reputation) return reputation; 161 | let neg = reputation < 0; 162 | let rep = String(reputation); 163 | rep = neg ? rep.substring(1) : rep; 164 | let v = (Math.log10((rep > 0 ? rep : -rep) - 10) - 9); 165 | v = neg ? -v : v; 166 | v = v * 9 + 25; 167 | if (decimal_places > 0) { 168 | return +(Math.round(v + "e+" + decimal_places) + "e-" + decimal_places); 169 | } 170 | return parseInt(v); 171 | }, 172 | 173 | vestToSteem: function( 174 | vestingShares, 175 | totalVestingShares, 176 | totalVestingFundSteem 177 | ) { 178 | return ( 179 | parseFloat(totalVestingFundSteem) * 180 | (parseFloat(vestingShares) / parseFloat(totalVestingShares)) 181 | ); 182 | }, 183 | 184 | commentPermlink: function(parentAuthor, parentPermlink) { 185 | const timeStr = new Date() 186 | .toISOString() 187 | .replace(/[^a-zA-Z0-9]+/g, "") 188 | .toLowerCase(); 189 | parentPermlink = parentPermlink.replace(/(-\d{8}t\d{9}z)/g, ""); 190 | let permLink = 191 | "re-" + parentAuthor + "-" + parentPermlink + "-" + timeStr; 192 | if (permLink.length > 255) { 193 | // pay respect to STEEMIT_MAX_PERMLINK_LENGTH 194 | permLink.substr(permLink.length - 255, permLink.length); 195 | } 196 | // permlinks must be lower case and not contain anything but 197 | // alphanumeric characters plus dashes 198 | return permLink.toLowerCase().replace(/[^a-z0-9-]+/g, ""); 199 | }, 200 | 201 | amount: function(amount, asset) { 202 | return amount.toFixed(3) + " " + asset; 203 | }, 204 | numberWithCommas, 205 | vestingSteem, 206 | estimateAccountValue, 207 | createSuggestedPassword, 208 | pricePerSteem 209 | }; 210 | }; 211 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const api = require('./api'); 2 | const auth = require('./auth'); 3 | const broadcast = require('./broadcast'); 4 | const formatter = require('./formatter')(api); 5 | const memo = require('./auth/memo'); 6 | const config = require('./config'); 7 | const utils = require('./utils'); 8 | 9 | module.exports = { 10 | api, 11 | auth, 12 | broadcast, 13 | formatter, 14 | memo, 15 | config, 16 | utils, 17 | }; 18 | 19 | process.on('warning', (warning) => { 20 | console.log('warning_stack: ', warning.stack); 21 | }); 22 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | const snakeCaseRe = /_([a-z])/g; 2 | export function camelCase(str) { 3 | return str.replace(snakeCaseRe, function(_m, l) { 4 | return l.toUpperCase(); 5 | }); 6 | } 7 | 8 | export function validateAccountName(value) { 9 | let i, label, len, suffix; 10 | 11 | suffix = "Account name should "; 12 | if (!value) { 13 | return suffix + "not be empty."; 14 | } 15 | const length = value.length; 16 | if (length < 3) { 17 | return suffix + "be longer."; 18 | } 19 | if (length > 16) { 20 | return suffix + "be shorter."; 21 | } 22 | if (/\./.test(value)) { 23 | suffix = "Each account segment should "; 24 | } 25 | const ref = value.split("."); 26 | for (i = 0, len = ref.length; i < len; i++) { 27 | label = ref[i]; 28 | if (!/^[a-z]/.test(label)) { 29 | return suffix + "start with a letter."; 30 | } 31 | if (!/^[a-z0-9-]*$/.test(label)) { 32 | return suffix + "have only letters, digits, or dashes."; 33 | } 34 | if (/--/.test(label)) { 35 | return suffix + "have only one dash in a row."; 36 | } 37 | if (!/[a-z0-9]$/.test(label)) { 38 | return suffix + "end with a letter or digit."; 39 | } 40 | if (!(label.length >= 3)) { 41 | return suffix + "be longer"; 42 | } 43 | } 44 | return null; 45 | } 46 | -------------------------------------------------------------------------------- /test-github-workflow.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM Test GitHub Actions workflow locally using 'act' 3 | REM Requires 'act' to be installed (https://github.com/nektos/act) 4 | REM Install with: winget install nektos.act 5 | 6 | echo Testing GitHub Actions workflow locally... 7 | act -j node18 --container-architecture linux/amd64 8 | 9 | REM Check the exit code to see if the workflow succeeded 10 | if %ERRORLEVEL% EQU 0 ( 11 | echo ✅ Workflow test passed! 12 | ) else ( 13 | echo ❌ Workflow test failed! 14 | echo Error code: %ERRORLEVEL% 15 | ) 16 | 17 | echo. 18 | echo Press any key to close this window... 19 | pause > nul -------------------------------------------------------------------------------- /test-github-workflow.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Test GitHub Actions workflow locally using 'act' 3 | # Requires 'act' to be installed (https://github.com/nektos/act) 4 | # Install with: curl https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash 5 | 6 | echo "Testing GitHub Actions workflow locally..." 7 | act -j node18 --container-architecture linux/amd64 8 | 9 | # Check the exit code to see if the workflow succeeded 10 | if [ $? -eq 0 ]; then 11 | echo "✅ Workflow test passed!" 12 | else 13 | echo "❌ Workflow test failed!" 14 | exit 1 15 | fi -------------------------------------------------------------------------------- /test/Crypto.js: -------------------------------------------------------------------------------- 1 | import config from "../src/config" 2 | import { Aes, PrivateKey, PublicKey, Signature } from "../src/auth/ecc" 3 | import assert from "assert" 4 | 5 | var secureRandom = require('secure-random'); 6 | var hash = require('../src/auth/ecc/src/hash'); 7 | var key = require('../src/auth/ecc/src/key_utils'); 8 | 9 | describe("steem.auth: Crypto", function() { 10 | 11 | /*it "Computes public key", -> 12 | private_key = PrivateKey.fromHex decrypted_key.substring 0, 64 13 | public_key = private_key.toPublicKey() 14 | console.log public_key.toHex());*/ 15 | 16 | it("sign", function() { 17 | this.timeout(10000); 18 | var private_key = PrivateKey.fromSeed("1"); 19 | return (() => { 20 | var result = []; 21 | for (var i = 0; i < 10; i++) { 22 | result.push(Signature.signBuffer((new Buffer.alloc(i)), private_key)); 23 | } 24 | return result; 25 | })(); 26 | }); 27 | 28 | }) 29 | 30 | describe("steem.auth: derives", ()=> { 31 | 32 | let prefix = config.get("address_prefix") 33 | let one_time_private = PrivateKey.fromHex("8fdfdde486f696fd7c6313325e14d3ff0c34b6e2c390d1944cbfe150f4457168") 34 | let to_public = PublicKey.fromStringOrThrow(prefix + "7vbxtK1WaZqXsiCHPcjVFBewVj8HFRd5Z5XZDpN6Pvb2dZcMqK") 35 | let secret = one_time_private.get_shared_secret( to_public ) 36 | let child = hash.sha256( secret ) 37 | 38 | // Check everything above with `wdump((child));` from the witness_node: 39 | assert.equal(child.toString('hex'), "1f296fa48172d9af63ef3fb6da8e369e6cc33c1fb7c164207a3549b39e8ef698") 40 | 41 | let nonce = hash.sha256( one_time_private.toBuffer() ) 42 | assert.equal(nonce.toString('hex'), "462f6c19ece033b5a3dba09f1e1d7935a5302e4d1eac0a84489cdc8339233fbf") 43 | 44 | it("child from public", ()=> assert.equal( 45 | to_public.child(child).toString(), 46 | "STM6XA72XARQCain961PCJnXiKYdEMrndNGago2PV5bcUiVyzJ6iL", 47 | "derive child public key" 48 | )) 49 | 50 | // child = hash.sha256( one_time_private.get_secret( to_public )) 51 | it("child from private", ()=> assert.equal( 52 | PrivateKey.fromSeed("alice-brain-key").child(child).toPublicKey().toString(), 53 | "STM6XA72XARQCain961PCJnXiKYdEMrndNGago2PV5bcUiVyzJ6iL", 54 | "derive child from private key" 55 | )) 56 | 57 | // "many keys" works, not really needed 58 | // it("many keys", function() { 59 | // 60 | // this.timeout(10 * 1000) 61 | // 62 | // for (var i = 0; i < 10; i++) { 63 | // let privkey1 = key.get_random_key() 64 | // let privkey2 = key.get_random_key() 65 | // 66 | // let secret1 = one_time_private.get_shared_secret( privkey1.toPublicKey() ) 67 | // let child1 = hash.sha256( secret1 ) 68 | // 69 | // let secret2 = privkey2.get_shared_secret( privkey2.toPublicKey() ) 70 | // let child2 = hash.sha256( secret2 ) 71 | // 72 | // it("child from public", ()=> assert.equal( 73 | // privkey1.toPublicKey().child(child1).toString(), 74 | // privkey2.toPublicKey().child(child2).toString(), 75 | // "derive child public key" 76 | // )) 77 | // 78 | // it("child from private", ()=> assert.equal( 79 | // privkey1.child(child1).toString(), 80 | // privkey2.child(child2).toString(), 81 | // "derive child private key" 82 | // )) 83 | // } 84 | // 85 | // }) 86 | 87 | }) 88 | 89 | var min_time_elapsed = function(f){ 90 | var start_t = Date.now(); 91 | var ret = f(); 92 | var elapsed = Date.now() - start_t; 93 | assert.equal( 94 | // repeat operations may take less time 95 | elapsed >= 250 * 0.8, true, 96 | `minimum time requirement was not met, instead only ${elapsed/1000.0} elapsed` 97 | ); 98 | return ret; 99 | }; 100 | 101 | -------------------------------------------------------------------------------- /test/KeyFormats.js: -------------------------------------------------------------------------------- 1 | import { PrivateKey, PublicKey, Address } from "../src/auth/ecc"; 2 | import assert from "assert" 3 | 4 | var test = function(key) { 5 | describe("steem.auth: key_formats", function() { 6 | 7 | it("Calculates public key from private key", function() { 8 | var private_key = PrivateKey.fromHex(key.private_key); 9 | var public_key = private_key.toPublicKey(); 10 | assert.equal(key.public_key, public_key.toPublicKeyString()); 11 | }); 12 | 13 | it("Create BTS short address", function() { 14 | var public_key = PublicKey.fromString(key.public_key); 15 | assert.equal(key.bts_address, public_key.toAddressString()); 16 | }) 17 | 18 | it("Blockchain Address", function() { 19 | var public_key = PublicKey.fromString(key.public_key); 20 | assert.equal(key.blockchain_address, public_key.toBlockchainAddress().toString('hex')); 21 | }); 22 | 23 | it("BTS public key import / export", function() { 24 | var public_key = PublicKey.fromString(key.public_key); 25 | assert.equal(key.public_key, public_key.toPublicKeyString()); 26 | }); 27 | 28 | it("PTS", function() { 29 | var private_key = PrivateKey.fromHex(key.private_key); 30 | var public_key = private_key.toPublicKey(); 31 | assert.equal(key.pts_address, public_key.toPtsAddy()); 32 | }); 33 | 34 | it("To WIF", function() { 35 | var private_key = PrivateKey.fromHex(key.private_key); 36 | assert.equal(key.private_key_WIF_format, private_key.toWif()); 37 | }); 38 | 39 | it("From WIF", function() { 40 | var private_key = PrivateKey.fromWif(key.private_key_WIF_format); 41 | assert.equal(private_key.toHex(), key.private_key); 42 | }); 43 | 44 | it("Calc public key", function() { 45 | var private_key = PrivateKey.fromHex(key.private_key); 46 | var public_key = private_key.toPublicKey(); 47 | assert.equal(key.bts_address, public_key.toAddressString()); 48 | }); 49 | 50 | it("BTS/BTC uncompressed", function() { 51 | var public_key = PublicKey.fromString(key.public_key); 52 | var address = Address.fromPublic(public_key, false, 0); 53 | assert.equal(key.Uncompressed_BTC, address.toString()); 54 | }); 55 | 56 | it("BTS/BTC compressed", function() { 57 | var public_key = PublicKey.fromString(key.public_key); 58 | var address = Address.fromPublic(public_key, true, 0); 59 | assert.equal(key.Compressed_BTC, address.toString()); 60 | }); 61 | 62 | it("BTS/PTS uncompressed", function() { 63 | var public_key = PublicKey.fromString(key.public_key); 64 | var address = Address.fromPublic(public_key, false, 56); 65 | assert.equal(key.Uncompressed_PTS, address.toString()); 66 | }); 67 | 68 | it("BTS/PTS compressed", function() { 69 | var public_key = PublicKey.fromString(key.public_key); 70 | var address = Address.fromPublic(public_key, true, 56); 71 | assert.equal(key.Compressed_PTS, address.toString()); 72 | }); 73 | 74 | it("null hex to pubkey", function() { 75 | var public_key = PublicKey.fromHex(key.null_hex); 76 | assert.equal(key.null_address, public_key.toPublicKeyString()); 77 | }); 78 | 79 | it("null pubkey to hex", function() { 80 | var public_key = PublicKey.fromString(key.null_address); 81 | assert.equal(key.null_hex, public_key.toHex()); 82 | }); 83 | }); 84 | }; 85 | 86 | test({ 87 | // delegate0 88 | // sourced from: ./bitshares/programs/utils/bts_create_key 89 | public_key: "STM7jDPoMwyjVH5obFmqzFNp4Ffp7G2nvC7FKFkrMBpo7Sy4uq5Mj", 90 | private_key: "20991828d456b389d0768ed7fb69bf26b9bb87208dd699ef49f10481c20d3e18", 91 | private_key_WIF_format: "5J4eFhjREJA7hKG6KcvHofHMXyGQZCDpQE463PAaKo9xXY6UDPq", 92 | bts_address: "STM8DvGQqzbgCR5FHiNsFf8kotEXr8VKD3mR", 93 | pts_address: "Po3mqkgMzBL4F1VXJArwQxeWf3fWEpxUf3", 94 | encrypted_private_key: "5e1ae410919c450dce1c476ae3ed3e5fe779ad248081d85b3dcf2888e698744d0a4b60efb7e854453bec3f6883bcbd1d", 95 | blockchain_address: "4f3a560442a05e4fbb257e8dc5859b736306bace", 96 | // https://github.com/BitShares/bitshares/blob/2602504998dcd63788e106260895769697f62b07/libraries/wallet/wallet_db.cpp#L103-L108 97 | Uncompressed_BTC: "STMLAFmEtM8as1mbmjVcj5dphLdPguXquimn", 98 | Compressed_BTC: "STMANNTSEaUviJgWLzJBersPmyFZBY4jJETY", 99 | Uncompressed_PTS: "STMEgj7RM6FBwSoccGaESJLC3Mi18785bM3T", 100 | Compressed_PTS: "STMD5rYtofD6D4UHJH6mo953P5wpBfMhdMEi", 101 | // https://github.com/steemit/steem-js/issues/267 102 | null_hex: "000000000000000000000000000000000000000000000000000000000000000000", 103 | null_address: "STM1111111111111111111111111111111114T1Anm" 104 | }); 105 | 106 | -------------------------------------------------------------------------------- /test/all_types.js: -------------------------------------------------------------------------------- 1 | import { PrivateKey, PublicKey, Address } from "../src/auth/ecc" 2 | var assert = require('assert'); 3 | var Serilizer = require("../src/auth/serializer/src/serializer") 4 | var types = require('../src/auth/serializer/src/types'); 5 | var ops = require('../src/auth/serializer/src/operations'); 6 | 7 | var { 8 | //varint32, 9 | uint8, uint16, uint32, int16, int64, uint64, 10 | string, string_binary, bytes, bool, array, fixed_array, 11 | protocol_id_type, object_id_type, vote_id, 12 | // future_extensions, 13 | static_variant, map, set, 14 | public_key, address, 15 | time_point_sec, 16 | optional, 17 | asset 18 | } = types 19 | 20 | var { price, transfer } = ops 21 | 22 | // Must stay in sync with allTypes below. 23 | let AllTypes = new Serilizer("all_types", { 24 | uint8, uint16, uint32, int16, int64, uint64, 25 | string, string_binary, bytes: bytes(1), bool, array: array(uint8), fixed_array: fixed_array(2, uint8), 26 | protocol_id_type: protocol_id_type("base"), object_id_type, //vote_id, 27 | 28 | static_variant: array(static_variant( [transfer, price] )), 29 | map: map(uint8, uint8), 30 | set: set(uint8), 31 | 32 | public_key, address, 33 | 34 | time_optional: optional( time_point_sec ), 35 | time_point_sec1: time_point_sec, 36 | time_point_sec2: time_point_sec, 37 | time_point_sec3: time_point_sec, 38 | }) 39 | 40 | // Must stay in sync with AllTypes above. 41 | let allTypes = { 42 | 43 | uint8: Math.pow(2,8)-1, uint16: Math.pow(2,16)-1, uint32: Math.pow(2,32)-1, 44 | int16: 30000, int64: "9223372036854775807", uint64: "9223372036854775807", 45 | 46 | string: "‘Quote’", string_binary: '\u0001', bytes: "ff", bool: true, array: [2, 1], fixed_array: [1, 0], 47 | protocol_id_type: "1.1.1", object_id_type: "1.1.1", //vote_id: "2:1", 48 | 49 | static_variant: [ 50 | ["transfer", {from:"alice", to:"bob", amount: "1.000 STEEM", memo: ""}], 51 | ["price", {base: "1.000 STEEM", quote: "1.000 STEEM"}], 52 | ], 53 | 54 | map: [[4,3], [2,1]], 55 | set: [2,1], 56 | 57 | public_key: PrivateKey.fromSeed("").toPublicKey().toString(), 58 | address: Address.fromPublic(PrivateKey.fromSeed("").toPublicKey()).toString(), 59 | 60 | time_optional: undefined, 61 | time_point_sec1: new Date(), 62 | time_point_sec2: Math.floor(Date.now()/1000), 63 | time_point_sec3: '2017-02-16T20:27:12', 64 | } 65 | 66 | describe("steem.auth: all types", ()=> { 67 | 68 | let { toObject, fromObject, toBuffer, fromBuffer } = AllTypes 69 | 70 | toObject = toObject.bind(AllTypes) 71 | fromObject = fromObject.bind(AllTypes) 72 | toBuffer = toBuffer.bind(AllTypes) 73 | fromBuffer = fromBuffer.bind(AllTypes) 74 | 75 | it("from object", ()=> { 76 | assert(fromObject(allTypes), "serializable" ) 77 | assert(fromObject(fromObject(allTypes)), "non-serializable") 78 | }) 79 | 80 | it("to object", ()=> { 81 | assert(toObject(allTypes), "serializable" ) 82 | assert.deepEqual(toObject(allTypes), toObject(allTypes), "serializable (single to)" ) 83 | assert.deepEqual(toObject(toObject(allTypes)), toObject(allTypes), "serializable (double to)" ) 84 | assert.deepEqual(toObject(fromObject(allTypes)), toObject(allTypes), "non-serializable" ) 85 | assert.deepEqual(toObject(fromObject(fromObject(allTypes))), toObject(allTypes), "non-serializable (double from)") 86 | }) 87 | 88 | it("to buffer", ()=>{ 89 | assert(toBuffer(allTypes), "serializable" ) 90 | assert(toBuffer(fromObject(allTypes)), "non-serializable") 91 | assert.equal( 92 | toBuffer( allTypes ).toString("hex"), // serializable 93 | toBuffer( fromObject( allTypes )).toString("hex"), // non-serializable 94 | "serializable and non-serializable" 95 | ) 96 | }) 97 | 98 | it("from buffer", ()=> { 99 | assert.deepEqual(toObject(fromBuffer(toBuffer(allTypes))), toObject(allTypes), "serializable" ) 100 | assert.deepEqual(toObject(fromBuffer(toBuffer(fromObject(allTypes)))), toObject(allTypes), "non-serializable" ) 101 | }) 102 | 103 | it("template", ()=> { 104 | assert(toObject(allTypes, {use_default: true})) 105 | assert(toObject(allTypes, {annotate: true})) 106 | assert(toObject({}, {use_default: true})) 107 | assert(toObject({}, {use_default: true, annotate: true})) 108 | }) 109 | 110 | // keep last 111 | it("visual check", ()=> { 112 | console.log(toObject(fromObject(allTypes))) 113 | }) 114 | 115 | }) 116 | -------------------------------------------------------------------------------- /test/api.test.js: -------------------------------------------------------------------------------- 1 | require('babel-polyfill'); 2 | import assert from 'assert'; 3 | import should from 'should'; 4 | import testPost from './test-post.json'; 5 | import steem from '../src'; 6 | import api from '../src/api'; 7 | 8 | describe('steem.api:', function () { 9 | this.timeout(30 * 1000); 10 | 11 | describe('setOptions', () => { 12 | it('works', () => { 13 | let url = steem.config.get('uri'); 14 | if(! url) url = steem.config.get('websocket'); 15 | steem.api.setOptions({ url: url, useAppbaseApi: true }); 16 | }); 17 | }); 18 | 19 | describe('getFollowers', () => { 20 | describe('getting ned\'s followers', () => { 21 | it('works', async () => { 22 | const result = await steem.api.getFollowersAsync('ned', 0, 'blog', 5); 23 | assert(result, 'getFollowersAsync resoved to null?'); 24 | result.should.have.lengthOf(5); 25 | }); 26 | 27 | it('the startFollower parameter has an impact on the result', async () => { 28 | // Get the first 5 29 | const result1 = await steem.api.getFollowersAsync('ned', 0, 'blog', 5) 30 | result1.should.have.lengthOf(5); 31 | const result2 = await steem.api.getFollowersAsync('ned', result1[result1.length - 1].follower, 'blog', 5) 32 | result2.should.have.lengthOf(5); 33 | result1.should.not.be.eql(result2); 34 | }); 35 | 36 | it('clears listeners', async () => { 37 | steem.api.listeners('message').should.have.lengthOf(0); 38 | }); 39 | }); 40 | }); 41 | 42 | describe('getContent', () => { 43 | describe('getting a random post', () => { 44 | it('works', async () => { 45 | const result = await steem.api.getContentAsync('yamadapc', 'test-1-2-3-4-5-6-7-9'); 46 | result.should.have.properties(testPost); 47 | }); 48 | 49 | it('clears listeners', async () => { 50 | steem.api.listeners('message').should.have.lengthOf(0); 51 | }); 52 | }); 53 | }); 54 | 55 | describe('streamBlockNumber', () => { 56 | it('streams steem transactions', (done) => { 57 | let i = 0; 58 | const release = steem.api.streamBlockNumber((err, block) => { 59 | should.exist(block); 60 | block.should.be.instanceOf(Number); 61 | i++; 62 | if (i === 2) { 63 | release(); 64 | done(); 65 | } 66 | }); 67 | }); 68 | }); 69 | 70 | describe('streamBlock', () => { 71 | it('streams steem blocks', (done) => { 72 | let i = 0; 73 | const release = steem.api.streamBlock((err, block) => { 74 | try { 75 | should.exist(block); 76 | block.should.have.properties([ 77 | 'previous', 78 | 'transactions', 79 | 'timestamp', 80 | ]); 81 | } catch (err2) { 82 | release(); 83 | done(err2); 84 | return; 85 | } 86 | 87 | i++; 88 | if (i === 2) { 89 | release(); 90 | done(); 91 | } 92 | }); 93 | }); 94 | }); 95 | 96 | describe('streamTransactions', () => { 97 | it('streams steem transactions', (done) => { 98 | let i = 0; 99 | const release = steem.api.streamTransactions((err, transaction) => { 100 | try { 101 | should.exist(transaction); 102 | transaction.should.have.properties([ 103 | 'ref_block_num', 104 | 'operations', 105 | 'extensions', 106 | ]); 107 | } catch (err2) { 108 | release(); 109 | done(err2); 110 | return; 111 | } 112 | 113 | i++; 114 | if (i === 2) { 115 | release(); 116 | done(); 117 | } 118 | }); 119 | }); 120 | }); 121 | 122 | describe('streamOperations', () => { 123 | it('streams steem operations', (done) => { 124 | let i = 0; 125 | const release = steem.api.streamOperations((err, operation) => { 126 | try { 127 | should.exist(operation); 128 | } catch (err2) { 129 | release(); 130 | done(err2); 131 | return; 132 | } 133 | 134 | i++; 135 | if (i === 2) { 136 | release(); 137 | done(); 138 | } 139 | }); 140 | }); 141 | }); 142 | 143 | describe('useApiOptions', () => { 144 | it('works ok with the prod instances', async() => { 145 | steem.api.setOptions({ useAppbaseApi: true, url: steem.config.get('uri') }); 146 | 147 | const result = await steem.api.getContentAsync('yamadapc', 'test-1-2-3-4-5-6-7-9'); 148 | steem.api.setOptions({ useAppbaseApi: false, url: steem.config.get('uri') }); 149 | 150 | result.should.have.properties(testPost); 151 | }); 152 | }); 153 | 154 | describe('with retry', () => { 155 | let steemApi; 156 | beforeEach(() => { 157 | steemApi = new api.Steem({}); 158 | }); 159 | 160 | it('works by default', async function() { 161 | let attempts = 0; 162 | steemApi.setOptions({ 163 | url: 'https://api.steemit.com', 164 | fetchMethod: (uri, req) => new Promise((res, rej) => { 165 | const data = JSON.parse(req.body); 166 | res({ 167 | ok: true, 168 | json: () => Promise.resolve({ 169 | jsonrpc: '2.0', 170 | id: data.id, 171 | result: ['ned'], 172 | }), 173 | }); 174 | attempts++; 175 | }), 176 | }); 177 | const result = await steemApi.getFollowersAsync('ned', 0, 'blog', 5) 178 | assert.equal(attempts, 1); 179 | assert.deepEqual(result, ['ned']); 180 | }); 181 | 182 | it('does not retry by default', async() => { 183 | let attempts = 0; 184 | steemApi.setOptions({ 185 | url: 'https://api.steemit.com', 186 | fetchMethod: (uri, req) => new Promise((res, rej) => { 187 | rej(new Error('Bad request')); 188 | attempts++; 189 | }), 190 | }); 191 | 192 | let result; 193 | let errored = false; 194 | try { 195 | result = await steemApi.getFollowersAsync('ned', 0, 'blog', 5) 196 | } catch (e) { 197 | errored = true; 198 | } 199 | assert.equal(attempts, 1); 200 | assert.equal(errored, true); 201 | }); 202 | 203 | it('works with retry passed as a boolean', async() => { 204 | let attempts = 0; 205 | steemApi.setOptions({ 206 | url: 'https://api.steemit.com', 207 | fetchMethod: (uri, req) => new Promise((res, rej) => { 208 | const data = JSON.parse(req.body); 209 | res({ 210 | ok: true, 211 | json: () => Promise.resolve({ 212 | jsonrpc: '2.0', 213 | id: data.id, 214 | result: ['ned'], 215 | }), 216 | }); 217 | attempts++; 218 | }), 219 | }); 220 | 221 | const result = await steemApi.getFollowersAsync('ned', 0, 'blog', 5) 222 | assert.equal(attempts, 1); 223 | assert.deepEqual(result, ['ned']); 224 | }); 225 | 226 | it('retries with retry passed as a boolean', async() => { 227 | let attempts = 0; 228 | steemApi.setOptions({ 229 | url: 'https://api.steemit.com', 230 | retry: true, 231 | fetchMethod: (uri, req) => new Promise((res, rej) => { 232 | if (attempts < 1) { 233 | rej(new Error('Bad request')); 234 | } else { 235 | const data = JSON.parse(req.body); 236 | res({ 237 | ok: true, 238 | json: () => Promise.resolve({ 239 | jsonrpc: '2.0', 240 | id: data.id, 241 | result: ['ned'], 242 | }), 243 | }); 244 | } 245 | attempts++; 246 | }), 247 | }); 248 | 249 | let result; 250 | let errored = false; 251 | try { 252 | result = await steemApi.getFollowersAsync('ned', 0, 'blog', 5); 253 | } catch (e) { 254 | errored = true; 255 | } 256 | assert.equal(attempts, 2); 257 | assert.equal(errored, false); 258 | assert.deepEqual(result, ['ned']); 259 | }); 260 | 261 | it('works with retry passed as an object', async() => { 262 | steemApi.setOptions({ 263 | url: 'https://api.steemit.com', 264 | retry: { 265 | retries: 3, 266 | minTimeout: 1, // 1ms 267 | }, 268 | fetchMethod: (uri, req) => new Promise((res, rej) => { 269 | const data = JSON.parse(req.body); 270 | res({ 271 | ok: 'true', 272 | json: () => Promise.resolve({ 273 | jsonrpc: '2.0', 274 | id: data.id, 275 | result: ['ned'], 276 | }), 277 | }); 278 | }), 279 | }); 280 | 281 | const result = await steemApi.getFollowersAsync('ned', 0, 'blog', 5); 282 | assert.deepEqual(result, ['ned']); 283 | }); 284 | 285 | it('retries with retry passed as an object', async() => { 286 | let attempts = 0; 287 | steemApi.setOptions({ 288 | url: 'https://api.steemit.com', 289 | retry: { 290 | retries: 3, 291 | minTimeout: 1, 292 | }, 293 | fetchMethod: (uri, req) => new Promise((res, rej) => { 294 | if (attempts < 1) { 295 | rej(new Error('Bad request')); 296 | } else { 297 | const data = JSON.parse(req.body); 298 | res({ 299 | ok: true, 300 | json: () => Promise.resolve({ 301 | jsonrpc: '2.0', 302 | id: data.id, 303 | result: ['ned'], 304 | }), 305 | }); 306 | } 307 | attempts++; 308 | }), 309 | }); 310 | 311 | let result; 312 | let errored = false; 313 | try { 314 | result = await steemApi.getFollowersAsync('ned', 0, 'blog', 5); 315 | } catch (e) { 316 | errored = true; 317 | } 318 | assert.equal(attempts, 2); 319 | assert.equal(errored, false); 320 | assert.deepEqual(result, ['ned']); 321 | }); 322 | 323 | it('does not retry non-retriable operations'); 324 | }); 325 | 326 | describe('getRC', () => { 327 | describe('getting a RC of an account', () => { 328 | it('works', async () => { 329 | const result = await steem.api.findRcAccountsAsync(["justinsunsteemit"]); 330 | result.should.have.properties("rc_accounts"); 331 | result["rc_accounts"][0].should.have.properties("rc_manabar"); 332 | }); 333 | it('clears listeners', async () => { 334 | steem.api.listeners('message').should.have.lengthOf(0); 335 | }); 336 | }); 337 | }); 338 | 339 | describe('getExpiringDelegations', () => { 340 | describe('getting expired delegation of an account', () => { 341 | it('works', async () => { 342 | const result = await steem.api.getExpiringVestingDelegationsAsync("justyy", "2004-01-02T00:11:22", 100); 343 | result.should.have.properties("length"); 344 | }); 345 | it('clears listeners', async () => { 346 | steem.api.listeners('message').should.have.lengthOf(0); 347 | }); 348 | }); 349 | }); 350 | 351 | describe('Account Recovery', () => { 352 | describe('findChangeRecoveryAccountRequests', () => { 353 | it('works', async () => { 354 | const result = await steem.api.findChangeRecoveryAccountRequestsAsync(["justyy", "ety001"]); 355 | result.should.have.properties("requests"); 356 | result.requests.should.have.properties("length"); 357 | }); 358 | it('clears listeners', async () => { 359 | steem.api.listeners('message').should.have.lengthOf(0); 360 | }); 361 | }); 362 | }); 363 | }); 364 | -------------------------------------------------------------------------------- /test/broadcast.test.js: -------------------------------------------------------------------------------- 1 | import Promise from 'bluebird'; 2 | import should from 'should'; 3 | import steem from '../src'; 4 | 5 | const username = process.env.STEEM_USERNAME || 'guest123'; 6 | const password = process.env.STEEM_PASSWORD; 7 | const postingWif = password 8 | ? steem.auth.toWif(username, password, 'posting') 9 | : '5JRaypasxMx1L97ZUX7YuC5Psb5EAbF821kkAGtBj7xCJFQcbLg'; 10 | 11 | describe('steem.broadcast:', () => { 12 | it('exists', () => { 13 | should.exist(steem.broadcast); 14 | }); 15 | 16 | it('has generated methods', () => { 17 | should.exist(steem.broadcast.vote); 18 | should.exist(steem.broadcast.voteWith); 19 | should.exist(steem.broadcast.comment); 20 | should.exist(steem.broadcast.transfer); 21 | }); 22 | 23 | it('has backing methods', () => { 24 | should.exist(steem.broadcast.send); 25 | }); 26 | 27 | it('has promise methods', () => { 28 | should.exist(steem.broadcast.sendAsync); 29 | should.exist(steem.broadcast.voteAsync); 30 | should.exist(steem.broadcast.transferAsync); 31 | }); 32 | 33 | describe('patching transaction with default global properties', () => { 34 | it('works', async () => { 35 | const tx = await steem.broadcast._prepareTransaction({ 36 | extensions: [], 37 | operations: [['vote', { 38 | voter: 'yamadapc', 39 | author: 'yamadapc', 40 | permlink: 'test-1-2-3-4-5-6-7-9', 41 | }]], 42 | }); 43 | 44 | tx.should.have.properties([ 45 | 'expiration', 46 | 'ref_block_num', 47 | 'ref_block_prefix', 48 | 'extensions', 49 | 'operations', 50 | ]); 51 | }); 52 | }); 53 | 54 | describe('no blocks on chain', () => { 55 | it('works', async () => { 56 | const newAccountName = username + '-' + Math.floor(Math.random() * 10000); 57 | const keys = steem.auth.generateKeys( 58 | username, password, ['posting', 'active', 'owner', 'memo']); 59 | 60 | const oldGetDynamicGlobalProperties = steem.api.getDynamicGlobalPropertiesAsync; 61 | steem.api.getDynamicGlobalPropertiesAsync = () => Promise.resolve({ 62 | time: '2019-04-14T21:30:56', 63 | last_irreversible_block_num: 32047459, 64 | }); 65 | 66 | // If the block returned is `null`, then no blocks are on the chain yet. 67 | const oldGetBlockAsync = steem.api.getBlockAsync; 68 | steem.api.getBlockAsync = () => Promise.resolve(null); 69 | 70 | try { 71 | const tx = await steem.broadcast._prepareTransaction({ 72 | extensions: [], 73 | operations: [[ 74 | 'account_create', 75 | { 76 | fee: '0.000 STEEM', 77 | creator: username, 78 | new_account_name: newAccountName, 79 | owner: { 80 | weight_threshold: 1, 81 | account_auths: [], 82 | key_auths: [[keys.owner, 1]], 83 | }, 84 | active: { 85 | weight_threshold: 1, 86 | account_auths: [], 87 | key_auths: [[keys.active, 1]], 88 | }, 89 | posting: { 90 | weight_threshold: 1, 91 | account_auths: [], 92 | key_auths: [[keys.posting, 1]], 93 | }, 94 | memo_key: keys.memo, 95 | json_metadata: '', 96 | extensions: [], 97 | } 98 | ]], 99 | }); 100 | 101 | tx.should.have.properties([ 102 | 'expiration', 103 | 'ref_block_num', 104 | 'ref_block_prefix', 105 | 'extensions', 106 | 'operations', 107 | ]); 108 | } finally { 109 | steem.api.getDynamicGlobalPropertiesAsync = oldGetDynamicGlobalProperties; 110 | steem.api.getBlockAsync = oldGetBlockAsync; 111 | } 112 | }); 113 | }); 114 | 115 | describe('downvoting', () => { 116 | it('works', async function() { 117 | this.timeout(10000); 118 | const tx = await steem.broadcast.voteAsync( 119 | postingWif, 120 | username, 121 | 'yamadapc', 122 | 'test-1-2-3-4-5-6-7-9', 123 | -1000 124 | ); 125 | tx.should.have.properties([ 126 | 'expiration', 127 | 'ref_block_num', 128 | 'ref_block_prefix', 129 | 'extensions', 130 | 'operations', 131 | 'signatures', 132 | ]); 133 | }); 134 | }); 135 | 136 | describe('voting', () => { 137 | beforeEach(function() { 138 | this.timeout(10000); 139 | return Promise.delay(3000); 140 | }); 141 | 142 | it('works', async function() { 143 | this.timeout(10000); 144 | const tx = await steem.broadcast.voteAsync( 145 | postingWif, 146 | username, 147 | 'yamadapc', 148 | 'test-1-2-3-4-5-6-7-9', 149 | 10000 150 | ); 151 | 152 | tx.should.have.properties([ 153 | 'expiration', 154 | 'ref_block_num', 155 | 'ref_block_prefix', 156 | 'extensions', 157 | 'operations', 158 | 'signatures', 159 | ]); 160 | }); 161 | 162 | it('works with callbacks', function(done) { 163 | this.timeout(10000); 164 | steem.broadcast.vote( 165 | postingWif, 166 | username, 167 | 'yamadapc', 168 | 'test-1-2-3-4-5-6-7-9', 169 | 5000, 170 | (err, tx) => { 171 | if (err) return done(err); 172 | tx.should.have.properties([ 173 | 'expiration', 174 | 'ref_block_num', 175 | 'ref_block_prefix', 176 | 'extensions', 177 | 'operations', 178 | 'signatures', 179 | ]); 180 | done(); 181 | } 182 | ); 183 | }); 184 | }); 185 | 186 | describe('customJson', () => { 187 | before(function() { 188 | this.timeout(10000); 189 | return Promise.delay(3000); 190 | }); 191 | 192 | it('works', async function() { 193 | this.timeout(10000); 194 | const tx = await steem.broadcast.customJsonAsync( 195 | postingWif, 196 | [], 197 | [username], 198 | 'follow', 199 | JSON.stringify([ 200 | 'follow', 201 | { 202 | follower: username, 203 | following: 'fabien', 204 | what: ['blog'], 205 | }, 206 | ]) 207 | ); 208 | 209 | tx.should.have.properties([ 210 | 'expiration', 211 | 'ref_block_num', 212 | 'ref_block_prefix', 213 | 'extensions', 214 | 'operations', 215 | 'signatures', 216 | ]); 217 | }); 218 | }); 219 | 220 | describe('writeOperations', () => { 221 | it('receives a properly formatted error response', () => { 222 | const wif = steem.auth.toWif('username', 'password', 'posting'); 223 | return steem.broadcast.voteAsync(wif, 'voter', 'author', 'permlink', 0). 224 | then(() => { 225 | throw new Error('writeOperation should have failed but it didn\'t'); 226 | }, (e) => { 227 | should.exist(e.message); 228 | }); 229 | }); 230 | }); 231 | }); 232 | -------------------------------------------------------------------------------- /test/browser/BrowserTests.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert' 2 | import {PrivateKey, PublicKey} from 'shared/ecc' 3 | import {encode, decode} from 'shared/chain/memo' 4 | import {serverApiRecordEvent} from 'app/utils/ServerApiClient' 5 | 6 | export const browserTests = {} 7 | 8 | export default function runTests() { 9 | let rpt = '' 10 | let pass = true 11 | function it(name, fn) { 12 | console.log('Testing', name) 13 | rpt += 'Testing ' + name + '\n' 14 | try { 15 | fn() 16 | } catch(error) { 17 | console.error(error) 18 | pass = false 19 | rpt += error.stack + '\n\n' 20 | serverApiRecordEvent('client_error', error) 21 | } 22 | } 23 | 24 | let private_key, public_key, encodedMemo 25 | const wif = '5JdeC9P7Pbd1uGdFVEsJ41EkEnADbbHGq6p1BwFxm6txNBsQnsw' 26 | const pubkey = 'STM8m5UgaFAAYQRuaNejYdS8FVLVp9Ss3K1qAVk5de6F8s3HnVbvA' 27 | 28 | it('create private key', () => { 29 | private_key = PrivateKey.fromSeed('1') 30 | assert.equal(private_key.toWif(), wif) 31 | }) 32 | it('supports WIF format', () => { 33 | assert(PrivateKey.fromWif(wif)) 34 | }) 35 | it('finds public from private key', () => { 36 | public_key = private_key.toPublicKey() 37 | // substring match ignore prefix 38 | assert.equal(public_key.toString(), pubkey, 'Public key did not match') 39 | }) 40 | it('parses public key', () => { 41 | assert(PublicKey.fromString(public_key.toString())) 42 | }) 43 | it('encrypts memo', () => { 44 | encodedMemo = encode(private_key, public_key, '#memo') 45 | assert(encodedMemo) 46 | }) 47 | it('decripts memo', () => { 48 | const dec = decode(private_key, encodedMemo) 49 | if(dec !== '#memo') { 50 | console.error('Decoded memo did not match (memo encryption is unavailable)') 51 | browserTests.memo_encryption = false 52 | } 53 | }) 54 | if(!pass) return rpt 55 | } 56 | -------------------------------------------------------------------------------- /test/comment.test.js: -------------------------------------------------------------------------------- 1 | import Promise from 'bluebird'; 2 | import should from 'should'; 3 | import steem from '../src'; 4 | import pkg from '../package.json'; 5 | import assert from 'assert' 6 | 7 | const username = process.env.STEEM_USERNAME || 'guest123'; 8 | const password = process.env.STEEM_PASSWORD; 9 | const postingWif = password 10 | ? steem.auth.toWif(username, password, 'posting') 11 | : '5JRaypasxMx1L97ZUX7YuC5Psb5EAbF821kkAGtBj7xCJFQcbLg'; 12 | 13 | describe('steem.broadcast:', () => { 14 | 15 | describe('comment with options', () => { 16 | before(function() { 17 | this.timeout(10000); 18 | return Promise.delay(3000); 19 | }); 20 | 21 | it('works', async function() { 22 | this.timeout(10000); 23 | const permlink = steem.formatter.commentPermlink('siol', 'test'); 24 | const operations = [ 25 | ['comment', 26 | { 27 | parent_author: 'siol', 28 | parent_permlink: 'test', 29 | author: username, 30 | permlink, 31 | title: 'Test', 32 | body: `This is a test using Steem.js v${pkg.version}.`, 33 | json_metadata : JSON.stringify({ 34 | tags: ['test'], 35 | app: `steemjs/${pkg.version}` 36 | }) 37 | } 38 | ], 39 | ['comment_options', { 40 | author: username, 41 | permlink, 42 | max_accepted_payout: '1000000.000 SBD', 43 | percent_steem_dollars: 10000, 44 | allow_votes: true, 45 | allow_curation_rewards: true, 46 | extensions: [ 47 | [0, { 48 | beneficiaries: [ 49 | { account: 'good-karma', weight: 2000 }, 50 | { account: 'null', weight: 5000 } 51 | ] 52 | }] 53 | ] 54 | }] 55 | ]; 56 | 57 | const tx = await steem.broadcast.sendAsync( 58 | { operations, extensions: [] }, 59 | { posting: postingWif } 60 | ); 61 | 62 | tx.should.have.properties([ 63 | 'expiration', 64 | 'ref_block_num', 65 | 'ref_block_prefix', 66 | 'extensions', 67 | 'operations', 68 | 'signatures', 69 | ]); 70 | }); 71 | }); 72 | }); 73 | 74 | describe('commentPermLink:', () => { 75 | it('does not return dots', () => { 76 | var commentPermlink = steem.formatter.commentPermlink( 77 | 'foo.bar', 78 | 'the-first-physical-foo-bar-ready-to-be-shipped' 79 | ); 80 | console.log(commentPermlink); 81 | assert.equal(-1, commentPermlink.indexOf('.')); 82 | }); 83 | }); -------------------------------------------------------------------------------- /test/hf20-accounts.test.js: -------------------------------------------------------------------------------- 1 | import Promise from 'bluebird'; 2 | import should from 'should'; 3 | import steem from '../src'; 4 | 5 | const username = process.env.STEEM_USERNAME || 'guest123'; 6 | const password = process.env.STEEM_PASSWORD; 7 | const activeWif = steem.auth.toWif(username, password, 'active'); 8 | 9 | describe('steem.hf20-accounts:', () => { 10 | it('has generated methods', () => { 11 | should.exist(steem.broadcast.claimAccount); 12 | should.exist(steem.broadcast.createClaimedAccount); 13 | }); 14 | 15 | it('has promise methods', () => { 16 | should.exist(steem.broadcast.claimAccountAsync); 17 | should.exist(steem.broadcast.createClaimedAccountAsync); 18 | }); 19 | 20 | 21 | describe('claimAccount', () => { 22 | 23 | /* Skip these tests. Steem-js test infrastructure not set up for testing active auths 24 | Blocked by Steem issue #3546 25 | it('signs and verifies auth', function(done) { 26 | let tx = { 27 | 'operations': [[ 28 | 'claim_account', { 29 | 'creator': username, 30 | 'fee': '0.000 STEEM'}]] 31 | } 32 | 33 | steem.api.callAsync('condenser_api.get_version', []).then((result) => { 34 | if(result['blockchain_version'] < '0.22.0') return done(); 35 | 36 | steem.broadcast._prepareTransaction(tx).then(function(tx){ 37 | tx = steem.auth.signTransaction(tx, [activeWif]); 38 | steem.api.verifyAuthorityAsync(tx).then( 39 | (result) => {result.should.equal(true); done();}, 40 | (err) => {done(err);} 41 | ); 42 | }); 43 | }); 44 | 45 | }); 46 | 47 | it('claims and creates account', function(done) { 48 | this.skip(); // (!) need test account with enough RC 49 | 50 | steem.api.callAsync('condenser_api.get_version', []).then((result) => { 51 | if(result['blockchain_version'] < '0.22.0') return done(); 52 | 53 | steem.broadcast.claimAccountAsync(activeWif, username, '0.000 STEEM', []).then((result) => { 54 | let newAccountName = username + '-' + Math.floor(Math.random() * 10000); 55 | let keys = steem.auth.generateKeys( 56 | username, password, ['posting', 'active', 'owner', 'memo']); 57 | 58 | steem.broadcast.createClaimedAccountAsync( 59 | activeWif, 60 | username, 61 | newAccountName, 62 | keys['owner'], 63 | keys['active'], 64 | keys['posting'], 65 | keys['memo'], 66 | {}, [] 67 | ).then((result) => { 68 | should.exist(result); 69 | done(); 70 | }, (err) => {done(err)}); 71 | }, (err) => {done(err)}); 72 | }); 73 | }); 74 | */ 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /test/hf21-sps.test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert" 2 | import Promise from 'bluebird'; 3 | import should from 'should'; 4 | import steem from '../src'; 5 | 6 | const username = process.env.STEEM_USERNAME || 'guest123'; 7 | const password = process.env.STEEM_PASSWORD; 8 | const activeWif = steem.auth.toWif(username, password, 'active'); 9 | 10 | describe('steem.hf21-accounts:', () => { 11 | it('has generated methods', () => { 12 | should.exist(steem.broadcast.createProposal); 13 | should.exist(steem.broadcast.updateProposalVotes); 14 | should.exist(steem.broadcast.removeProposal); 15 | }); 16 | 17 | it('has promise methods', () => { 18 | should.exist(steem.broadcast.createProposalAsync); 19 | should.exist(steem.broadcast.updateProposalVotesAsync); 20 | should.exist(steem.broadcast.removeProposalAsync); 21 | }); 22 | 23 | describe('create proposal ops', () => { 24 | /* Skip these tests. Steem-js test infrastructure not set up for testing active auths 25 | Blocked by Steem issue #3546 26 | it('signs and verifies create_proposal', function(done) { 27 | let permlink = 'test'; 28 | 29 | let tx = { 30 | 'operations': [[ 31 | 'create_proposal', { 32 | 'creator': username, 33 | 'receiver': username, 34 | 'start_date': '2019-09-01T00:00:00', 35 | 'end_date': '2019-10-01T00:00:00', 36 | 'daily_pay': '1.000 SBD', 37 | 'subject': 'testing', 38 | 'permlink': permlink 39 | }]] 40 | } 41 | 42 | steem.api.callAsync('condenser_api.get_version', []).then((result) => { 43 | if(result['blockchain_version'] < '0.22.0') return done(); 44 | result.should.have.property('blockchain_version'); 45 | 46 | steem.broadcast._prepareTransaction(tx).then(function(tx){ 47 | tx = steem.auth.signTransaction(tx, [activeWif]); 48 | steem.api.verifyAuthorityAsync(tx).then( 49 | (result) => {result.should.equal(true); done();}, 50 | (err) => {done(err);} 51 | ); 52 | }); 53 | }); 54 | }) 55 | 56 | it('signs and verifies update_proposal_votes', function(done) { 57 | let tx = { 58 | 'operations': [[ 59 | 'update_proposal_votes', { 60 | 'voter': username, 61 | 'proposal_ids': [7], 62 | 'approve': true 63 | }]] 64 | } 65 | 66 | return done(); 67 | 68 | steem.broadcast._prepareTransaction(tx).then(function(tx){ 69 | tx = steem.auth.signTransaction(tx, [activeWif]); 70 | steem.api.verifyAuthorityAsync(tx).then( 71 | (result) => {result.should.equal(true); done();}, 72 | (err) => {done(err);} 73 | ); 74 | }); 75 | }) 76 | */ 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /test/memo.test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert' 2 | import {encode, decode} from '../src/auth/memo'; 3 | import {PrivateKey} from '../src/auth/ecc'; 4 | 5 | 6 | const private_key = PrivateKey.fromSeed("") 7 | const public_key = private_key.toPublicKey() 8 | 9 | describe('steem.auth: memo', ()=> { 10 | it('plain text', () => { 11 | const plaintext1 = encode(null/*private_key*/, null/*public_key*/, 'memo') 12 | assert.equal(plaintext1, 'memo') 13 | 14 | const plaintext2 = decode(null/*private_key*/, plaintext1) 15 | assert.equal(plaintext2, 'memo') 16 | }) 17 | it('encryption obj params', () => { 18 | const cypertext = encode(private_key, public_key, '#memo') 19 | const plaintext = decode(private_key, cypertext) 20 | assert.equal(plaintext, '#memo') 21 | }) 22 | it('encryption string params', () => { 23 | const cypertext = encode(private_key.toWif(), public_key.toPublicKeyString(), '#memo2') 24 | const plaintext = decode(private_key.toWif(), cypertext) 25 | assert.equal(plaintext, '#memo2') 26 | }) 27 | it('known encryption', () => { 28 | const base58 = '#HU6pdQ4Hh8cFrDVooekRPVZu4BdrhAe9RxrWrei2CwfAApAPdM4PT5mSV9cV3tTuWKotYQF6suyM4JHFBZz4pcwyezPzuZ2na7uwhRcLqFoqCam1VU3eCLjVNqcgUNbH3' 29 | const nonce = '1462976530069648' 30 | const text = '#爱' 31 | 32 | const cypertext = encode(private_key, public_key, text, nonce) 33 | assert.equal(cypertext, base58) 34 | const plaintext = decode(private_key, cypertext) 35 | assert.equal(plaintext, text) 36 | }) 37 | }) 38 | -------------------------------------------------------------------------------- /test/number_utils.js: -------------------------------------------------------------------------------- 1 | import assert from "assert" 2 | import { toImpliedDecimal, fromImpliedDecimal } from "../src/auth/serializer/src/number_utils" 3 | 4 | describe("steem.auth: Number utils", () => { 5 | 6 | 7 | it("to implied decimal", ()=> { 8 | assert.equal("1", toImpliedDecimal(1, 0)) 9 | assert.equal("10", toImpliedDecimal(1, 1)) 10 | assert.equal("100", toImpliedDecimal(1, 2)) 11 | assert.equal("10", toImpliedDecimal(".1", 2)) 12 | assert.equal("10", toImpliedDecimal("0.1", 2)) 13 | assert.equal("10", toImpliedDecimal("00.1", 2)) 14 | assert.equal("10", toImpliedDecimal("00.10", 2)) 15 | assert.throws(()=> toImpliedDecimal("00.100", 2)) 16 | assert.throws(()=> toImpliedDecimal(9007199254740991 + 1, 1)) 17 | }) 18 | 19 | it("from implied decimal", ()=> { 20 | assert.equal("1", fromImpliedDecimal(1, 0)) 21 | assert.equal("0.1", fromImpliedDecimal(1, 1)) 22 | assert.equal("0.01", fromImpliedDecimal(1, 2)) 23 | // must have suffixing zeros 24 | assert.equal("0.100", fromImpliedDecimal(100, 3)) 25 | }) 26 | 27 | 28 | }) -------------------------------------------------------------------------------- /test/operations_test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var types = require('../src/auth/serializer/src/types'); 3 | var ops = require('../src/auth/serializer/src/operations'); 4 | 5 | describe("steem.auth: operation test", ()=> { 6 | 7 | it("templates", ()=> { 8 | for(let op in ops) { 9 | switch(op) { 10 | case "operation" : continue 11 | } 12 | template(ops[op]) 13 | } 14 | }) 15 | 16 | it("account_create", ()=> { 17 | let tx = {"ref_block_num": 19297,"ref_block_prefix": 1608085982,"expiration": "2016-03-23T22:41:21","operations": [ ["account_create",{"fee": "0.000 STEEM","creator": "initminer","new_account_name": "scott","owner": {"weight_threshold": 1,"account_auths": [],"key_auths": [ ["STM7DTS62msowgpAZJBNRMStMUt5bfRA4hc9j5wjwU4vKhi3KFkKb",1 ]]},"active": {"weight_threshold": 1,"account_auths": [],"key_auths": [ ["STM8k1f8fvHxLrCTqMdRUJcK2rCE3y7SQBb8PremyadWvVWMeedZy",1 ]]},"posting": {"weight_threshold": 1,"account_auths": [],"key_auths": [ ["STM6DgpKJqoVGg7o6J1jdiP45xxbgoUg5VGzs96YBxX42NZu2bZea",1 ]]},"memo_key": "STM6ppNVEFmvBW4jEkzxXnGKuKuwYjMUrhz2WX1kHeGSchGdWJEDQ","json_metadata": ""} ]],"extensions": [], "signatures": []} 18 | 19 | let tx_hex = "614bde71d95f911bf3560109000000000000000003535445454d000009696e69746d696e65720573636f74740100000000010332757668fa45c2bc21447a2ff1dc2bbed9d9dda1616fd7b700255bd28e9d674a010001000000000103fb8900a262d51b908846be54fcf04b3a80d12ee749b9446f976b58b220ba4eed010001000000000102af4963d0f034043f4b4b0c99220e6a4b5d8b9cc71e5cd7d110f7602f3a0a11d1010002ff0de11ef55b998daf88047f1a00a60ed5dffb0c23c3279f8bd42a733845c5da000000" 20 | 21 | // 03 53 54 45 45 4d 0000 22 | assert.equal("STEEM", new Buffer.from("535445454d", "hex").toString()) 23 | let tx_object1 = ops.signed_transaction.fromObject(tx) 24 | let tx_object2 = ops.signed_transaction.fromHex(tx_hex) 25 | assert.deepEqual(tx, ops.signed_transaction.toObject(tx_object1)) 26 | assert.deepEqual(tx, ops.signed_transaction.toObject(tx_object2)) 27 | assert.deepEqual(tx_hex, ops.signed_transaction.toHex(tx_object1)) 28 | assert.deepEqual(tx_hex, ops.signed_transaction.toHex(tx_object2)) 29 | }) 30 | 31 | }) 32 | 33 | function template(op) { 34 | 35 | assert(op.toObject({}, {use_default: true})) 36 | assert(op.toObject({}, {use_default: true, annotate: true})) 37 | 38 | 39 | } 40 | -------------------------------------------------------------------------------- /test/promise-broadcast.test.js: -------------------------------------------------------------------------------- 1 | import Promise from 'bluebird'; 2 | import should from 'should'; 3 | import steem from '../src'; 4 | 5 | const username = process.env.STEEM_USERNAME || 'guest123'; 6 | const password = process.env.STEEM_PASSWORD; 7 | const postingWif = password 8 | ? steem.auth.toWif(username, password, 'posting') 9 | : '5JRaypasxMx1L97ZUX7YuC5Psb5EAbF821kkAGtBj7xCJFQcbLg'; 10 | 11 | describe('steem.broadcast: Promise Support', () => { 12 | it('should support Promises without Async suffix', function() { 13 | this.timeout(10000); 14 | const votePromise = steem.broadcast.vote( 15 | postingWif, 16 | username, 17 | 'yamadapc', 18 | 'test-1-2-3-4-5-6-7-9', 19 | 1000 20 | ); 21 | 22 | should(votePromise).be.an.instanceof(Promise); 23 | 24 | return votePromise.then(tx => { 25 | tx.should.have.properties([ 26 | 'expiration', 27 | 'ref_block_num', 28 | 'ref_block_prefix', 29 | 'extensions', 30 | 'operations', 31 | 'signatures', 32 | ]); 33 | }); 34 | }); 35 | 36 | it('should still support callbacks', function(done) { 37 | this.timeout(10000); 38 | steem.broadcast.vote( 39 | postingWif, 40 | username, 41 | 'yamadapc', 42 | 'test-1-2-3-4-5-6-7-9', 43 | 1000, 44 | (err, tx) => { 45 | if (err) return done(err); 46 | tx.should.have.properties([ 47 | 'expiration', 48 | 'ref_block_num', 49 | 'ref_block_prefix', 50 | 'extensions', 51 | 'operations', 52 | 'signatures', 53 | ]); 54 | done(); 55 | } 56 | ); 57 | }); 58 | 59 | it('should support direct Promise on send method', function() { 60 | this.timeout(10000); 61 | const operations = [['vote', { 62 | voter: username, 63 | author: 'yamadapc', 64 | permlink: 'test-1-2-3-4-5-6-7-9', 65 | weight: 1000, 66 | }]]; 67 | 68 | const sendPromise = steem.broadcast.send( 69 | { operations, extensions: [] }, 70 | { posting: postingWif } 71 | ); 72 | 73 | should(sendPromise).be.an.instanceof(Promise); 74 | 75 | return sendPromise.then(tx => { 76 | tx.should.have.properties([ 77 | 'expiration', 78 | 'ref_block_num', 79 | 'ref_block_prefix', 80 | 'extensions', 81 | 'operations', 82 | 'signatures', 83 | ]); 84 | }); 85 | }); 86 | }); -------------------------------------------------------------------------------- /test/reputation.test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import steem from '../src'; 3 | 4 | describe('steem.format.reputation', ()=> { 5 | const reputation = steem.formatter.reputation; 6 | it('rep 0 => 25', () => { 7 | assert.equal(reputation(0), 25); 8 | }); 9 | it('rep 95832978796820 => 69', () => { 10 | assert.equal(reputation(95832978796820), 69); 11 | }); 12 | it('rep 10004392664120 => 61', () => { 13 | assert.equal(reputation(10004392664120), 61); 14 | }); 15 | it('rep 30999525306309 => 65', () => { 16 | assert.equal(reputation(30999525306309), 65); 17 | }); 18 | it('rep -37765258368568 => -16', () => { 19 | assert.equal(reputation(-37765258368568), -16); 20 | }); 21 | it('rep 334486135407077 => 74', () => { 22 | assert.equal(reputation(334486135407077), 74); 23 | }); 24 | it('rep null => null', () => { 25 | assert.equal(reputation(null), null); 26 | }); 27 | it('rep undefined => undefined', () => { 28 | assert.equal(reputation(undefined), undefined); 29 | }); 30 | it('rep -1234123412342234 => -29', () => { 31 | assert.equal(reputation(-1234123412342234), -29); 32 | }); 33 | it('rep -22233344455 => 12', () => { 34 | assert.equal(reputation(-22233344455), 12); 35 | }); 36 | 37 | // with decimal places 38 | it('rep 0 => 25', () => { 39 | assert.equal(reputation(0, 1), 25); 40 | }); 41 | it('rep 95832978796820 => 69.83', () => { 42 | assert.equal(reputation(95832978796820, 2), 69.83); 43 | }); 44 | it('rep 10004392664120 => 61.002', () => { 45 | assert.equal(reputation(10004392664120, 3), 61.002); 46 | }); 47 | it('rep 30999525306309 => 65.4222', () => { 48 | assert.equal(reputation(30999525306309, 4), 65.4222); 49 | }); 50 | it('rep -37765258368568 => -16.19383', () => { 51 | assert.equal(reputation(-37765258368568, 5), -16.19383); 52 | }); 53 | it('rep 334486135407077 => 74.719403', () => { 54 | assert.equal(reputation(334486135407077, 6), 74.719403); 55 | }); 56 | it('rep null => null', () => { 57 | assert.equal(reputation(null, 7), null); 58 | }); 59 | it('rep undefined => undefined', () => { 60 | assert.equal(reputation(undefined, 8), undefined); 61 | }); 62 | it('rep -1234123412342234 => -29.822227322', () => { 63 | assert.equal(reputation(-1234123412342234, 9), -29.822227322); 64 | }); 65 | it('rep -22233344455 => 12.8769568338', () => { 66 | assert.equal(reputation(-22233344455, 10), 12.8769568338); 67 | }); 68 | }) 69 | -------------------------------------------------------------------------------- /test/test-post.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "yamadapc", 3 | "permlink": "test-1-2-3-4-5-6-7-9", 4 | "category": "test", 5 | "parent_author": "", 6 | "parent_permlink": "test", 7 | "title": "test-1-2-3-4-5-6-7-9", 8 | "body": "", 9 | "allow_replies": true, 10 | "allow_votes": true, 11 | "allow_curation_rewards": true, 12 | "url": "/test/@yamadapc/test-1-2-3-4-5-6-7-9" 13 | } 14 | -------------------------------------------------------------------------------- /test/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | -------------------------------------------------------------------------------- /test/test_helper.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | 3 | module.exports = { 4 | 5 | error(message_substring, f){ 6 | var fail = false; 7 | try { 8 | f(); 9 | fail = true; 10 | } catch (e) { 11 | if (e.toString().indexOf(message_substring) === -1) { 12 | throw new Error("expecting " + message_substring); 13 | } 14 | } 15 | if (fail) { 16 | throw new Error("expecting " + message_substring); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /test/types_test.js: -------------------------------------------------------------------------------- 1 | var Convert = require('../src/auth/serializer/src/convert'); 2 | var Long = require('bytebuffer').Long; 3 | 4 | var assert = require('assert'); 5 | var type = require('../src/auth/serializer/src/types'); 6 | var p = require('../src/auth/serializer/src/precision'); 7 | var th = require('./test_helper'); 8 | 9 | describe("steem.auth: types", function() { 10 | 11 | it("vote_id",function() { 12 | var toHex=function(id){ 13 | var vote = type.vote_id.fromObject(id); 14 | return Convert(type.vote_id).toHex(vote); 15 | }; 16 | assert.equal("ff000000", toHex("255:0")); 17 | assert.equal("00ffffff", toHex("0:"+0xffffff)); 18 | var out_of_range=function(id){ 19 | try { 20 | toHex(id); 21 | return assert(false, 'should have been out of range'); 22 | } catch (e) { 23 | return assert(e.message.indexOf('out of range') !== -1); 24 | } 25 | }; 26 | out_of_range("0:"+(0xffffff+1)); 27 | out_of_range("256:0"); 28 | 29 | }); 30 | 31 | it("set sort", function() { 32 | var bool_set = type.set(type.bool); 33 | // Note, 1,0 sorts to 0,1 34 | assert.equal("020001", Convert(bool_set).toHex([1,0])); 35 | th.error("duplicate (set)", function() { return Convert(bool_set).toHex([1,1]); }); 36 | 37 | }); 38 | 39 | it("string sort", function() { 40 | var setType = type.set(type.string); 41 | var set = setType.fromObject(["a","z","m"]) 42 | var setObj = setType.toObject(set) 43 | assert.deepEqual(["a","m","z"], setObj, "not sorted") 44 | }); 45 | 46 | it("map sort", function() { 47 | var bool_map = type.map(type.bool, type.bool); 48 | // 1,1 0,0 sorts to 0,0 1,1 49 | assert.equal("0200000101", Convert(bool_map).toHex([[1,1],[0,0]])); 50 | th.error("duplicate (map)", function() { return Convert(bool_map).toHex([[1,1],[1,1]]); }); 51 | }) 52 | 53 | it("public_key sort", function() { 54 | let mapType = type.map(type.public_key, type.uint16) 55 | let map = mapType.fromObject([//not sorted 56 | ["STM8me6d9PqzTgcoHxx6b4rnvWVTqz11kafidRAZwfacJkcJtfd75",0], 57 | ["STM56ankGHKf6qUsQe7vPsXTSEqST6Dt1ff73aV3YQbedzRua8NLQ",0], 58 | ]) 59 | let mapObject = mapType.toObject(map) 60 | assert.deepEqual(mapObject, [ // sorted (uppercase comes first) 61 | ["STM56ankGHKf6qUsQe7vPsXTSEqST6Dt1ff73aV3YQbedzRua8NLQ",0], 62 | ["STM8me6d9PqzTgcoHxx6b4rnvWVTqz11kafidRAZwfacJkcJtfd75",0], 63 | ]) 64 | }) 65 | 66 | it("type_id sort", function() { 67 | // map (protocol_id_type "account"), (uint16) 68 | let t = type.map(type.protocol_id_type("account"), type.uint16); 69 | assert.deepEqual( t.fromObject([[1,1],[0,0]]), [[0,0],[1,1]], 'did not sort' ) 70 | assert.deepEqual( t.fromObject([[0,0],[1,1]]), [[0,0],[1,1]], 'did not sort' ) 71 | }); 72 | 73 | it("precision number strings", function() { 74 | var check=function(input_string, precision, output_string){ 75 | return assert.equal( 76 | output_string, 77 | p._internal.decimal_precision_string( 78 | input_string, 79 | precision 80 | ) 81 | ); 82 | }; 83 | 84 | check( 85 | "12345678901234567890123456789012345678901234567890.12345",5, 86 | "1234567890123456789012345678901234567890123456789012345" 87 | ); 88 | check("", 0, "0"); 89 | check("0", 0, "0"); 90 | check("-0", 0, "0"); 91 | check("-00", 0, "0"); 92 | check("-0.0", 0, "0"); 93 | check("-", 0, "0"); 94 | check("1", 0, "1"); 95 | check("11", 0, "11"); 96 | 97 | overflow(function(){ return check(".1", 0, ""); }); 98 | overflow(function(){ return check("-.1", 0, ""); }); 99 | overflow(function(){ return check("0.1", 0, ""); }); 100 | overflow(function(){ return check("1.1", 0, ""); }); 101 | overflow(function(){ return check("1.11", 1, ""); }); 102 | 103 | check("", 1, "00"); 104 | check("1", 1, "10"); 105 | check("1.1", 1, "11"); 106 | check("-1", 1, "-10"); 107 | check("-1.1", 1, "-11"); 108 | 109 | }); 110 | 111 | return it("precision number long", function() { 112 | var _precision; 113 | assert.equal( 114 | Long.MAX_VALUE.toString(), 115 | p.to_bigint64( 116 | Long.MAX_VALUE.toString(), _precision = 0 117 | ).toString(), 118 | "to_bigint64 MAX_VALUE mismatch" 119 | ); 120 | 121 | // Long.MAX_VALUE.toString() == 9223372036854775807 122 | // Long.MAX_VALUE.toString() +1 9223372036854775808 123 | overflow(function(){ return p.to_bigint64( 124 | '9223372036854775808', _precision = 0 125 | ); 126 | }); 127 | 128 | assert.equal("0", p.to_string64(Long.ZERO, 0)); 129 | assert.equal("00", p.to_string64(Long.ZERO, 1)); 130 | 131 | overflow(function(){ return p.to_bigint64( 132 | '92233720368547758075', _precision = 1 133 | ); 134 | }); 135 | 136 | }); 137 | }); 138 | 139 | var overflow = function(f){ return th.error("overflow", f); }; 140 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const makeConfig = require('./webpack/makeConfig'); 2 | exports = module.exports = makeConfig(); 3 | -------------------------------------------------------------------------------- /webpack/makeConfig.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // Removing the visualizer plugin import as it's not compatible with webpack 5 3 | // const Visualizer = require('webpack-visualizer-plugin'); 4 | const _ = require('lodash'); 5 | const path = require('path'); 6 | const webpack = require('webpack'); 7 | 8 | const DEFAULTS = { 9 | isDevelopment: process.env.NODE_ENV !== 'production', 10 | baseDir: path.join(__dirname, '..'), 11 | }; 12 | 13 | function makePlugins(options) { 14 | const isDevelopment = options.isDevelopment; 15 | 16 | let plugins = [ 17 | // Add node polyfills for browser compatibility 18 | new webpack.ProvidePlugin({ 19 | process: 'process/browser', 20 | Buffer: ['buffer', 'Buffer'], 21 | }), 22 | ]; 23 | 24 | if (!isDevelopment) { 25 | plugins = plugins.concat([ 26 | new webpack.optimize.AggressiveMergingPlugin(), 27 | ]); 28 | } 29 | 30 | return plugins; 31 | } 32 | 33 | function makeStyleLoaders(options) { 34 | if (options.isDevelopment) { 35 | return [ 36 | { 37 | test: /\.s[ac]ss$/, 38 | loaders: [ 39 | 'style', 40 | 'css?sourceMap', 41 | 'autoprefixer-loader?browsers=last 2 version', 42 | 'sass?sourceMap&sourceMapContents', 43 | ], 44 | }, 45 | ]; 46 | } 47 | 48 | return [ 49 | { 50 | test: /\.s[ac]ss$/, 51 | loader: ExtractTextPlugin.extract( 52 | 'style-loader', 53 | 'css!autoprefixer-loader?browsers=last 2 version!sass' 54 | ), 55 | }, 56 | ]; 57 | } 58 | 59 | function makeConfig(options) { 60 | if (!options) options = {}; 61 | _.defaults(options, DEFAULTS); 62 | 63 | const isDevelopment = options.isDevelopment; 64 | 65 | return { 66 | devtool: isDevelopment ? 'eval-source-map' : 'source-map', 67 | entry: { 68 | steem: path.join(options.baseDir, 'src/browser.js'), 69 | 'steem-tests': path.join(options.baseDir, 'test/api.test.js'), 70 | }, 71 | output: { 72 | path: path.join(options.baseDir, 'dist'), 73 | filename: '[name].min.js', 74 | }, 75 | plugins: makePlugins(options), 76 | module: { 77 | rules: [ 78 | { 79 | test: /\.js?$/, 80 | use: 'babel-loader', 81 | exclude: /node_modules/, 82 | }, 83 | { 84 | test: /\.json?$/, 85 | type: 'json', 86 | }, 87 | ], 88 | }, 89 | resolve: { 90 | alias: { 91 | '@exodus/bytebuffer': 'bytebuffer', 92 | 'stream': 'stream-browserify', 93 | 'path': 'path-browserify', 94 | } 95 | }, 96 | node: { 97 | fs: 'empty', 98 | crypto: 'empty', 99 | }, 100 | optimization: { 101 | minimize: !isDevelopment, 102 | }, 103 | }; 104 | } 105 | 106 | if (!module.parent) { 107 | console.log(makeConfig({ 108 | isDevelopment: process.env.NODE_ENV !== 'production', 109 | })); 110 | } 111 | 112 | exports = module.exports = makeConfig; 113 | exports.DEFAULTS = DEFAULTS; 114 | --------------------------------------------------------------------------------