├── .gitignore ├── .npmignore ├── .npmrc.template ├── .travis.yml ├── README.md ├── dist ├── index.html ├── run_tests.sh └── test.html ├── package.json ├── scripts ├── is_latest.sh ├── publish-edge.sh ├── publish-tag.sh └── publish-utils.sh ├── src ├── aes.js ├── api_common.js ├── api_object.js ├── common.test.js ├── ecdsa.js ├── ecsignature.js ├── enforce_types.js ├── hash.js ├── index.js ├── key_private.js ├── key_public.js ├── key_utils.js ├── object.test.js ├── promise-async.js └── signature.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | npm-debug.log 4 | coverage 5 | test 6 | lib 7 | dist/*.js 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Exclude all files by default 2 | * 3 | 4 | # Include distribution bundle but exclude test files if they are built into dist 5 | !dist/** 6 | *.test.* 7 | 8 | # Include documentation and version information in bundle 9 | !CONTRIBUTING.md 10 | 11 | # Include any additional source files which should be bundled 12 | !src/**/*.abi.json 13 | -------------------------------------------------------------------------------- /.npmrc.template: -------------------------------------------------------------------------------- 1 | //registry.npmjs.org/:_authToken=${NPM_AUTH_TOKEN} -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | env: 2 | global: 3 | PUBLISH_NPM_LATEST_FROM="master" 4 | sudo: false 5 | language: node_js 6 | node_js: 7 | - "10.0.0" 8 | 9 | before_install: 10 | - npm i -g npm@6.4.1 11 | - yarn global add webpack 12 | script: 13 | - yarn test 14 | - cd dist && ./run_tests.sh && cd .. 15 | deploy: 16 | - provider: script 17 | script: bash ./scripts/publish-edge.sh 18 | on: 19 | branch: develop 20 | - provider: script 21 | skip_cleanup: true 22 | script: bash ./scripts/publish-tag.sh 23 | on: 24 | tags: true 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![NPM](https://img.shields.io/npm/v/eosjs-ecc.svg)](https://www.npmjs.org/package/eosjs-ecc) 2 | [![Build Status](https://travis-ci.org/EOSIO/eosjs-ecc.svg?branch=master)](https://travis-ci.org/EOSIO/eosjs-ecc) 3 | 4 | # Elliptic curve cryptography functions (ECC) 5 | 6 | Private Key, Public Key, Signature, AES, Encryption / Decryption 7 | 8 | # Import 9 | 10 | ```js 11 | import ecc from 'eosjs-ecc' 12 | // or 13 | const ecc = require('eosjs-ecc') 14 | ``` 15 | 16 | # Include 17 | 18 | - Install with: `yarn add eosjs-ecc` 19 | - Html script tag, see [releases](https://github.com/EOSIO/eosjs-ecc/releases) for the correct **version** and its matching script **integrity** hash. 20 | 21 | ```html 22 | 23 | 24 | 25 | 30 | 33 | 34 | 35 | 36 | See console object: eosjs_ecc 37 | 38 | 39 | ``` 40 | 41 | # Common API 42 | 43 | 44 | 45 | ### Table of Contents 46 | 47 | - [wif](#wif) 48 | - [ecc](#ecc) 49 | - [initialize](#initialize) 50 | - [unsafeRandomKey](#unsaferandomkey) 51 | - [randomKey](#randomkey) 52 | - [Parameters](#parameters) 53 | - [Examples](#examples) 54 | - [seedPrivate](#seedprivate) 55 | - [Parameters](#parameters-1) 56 | - [Examples](#examples-1) 57 | - [privateToPublic](#privatetopublic) 58 | - [Parameters](#parameters-2) 59 | - [Examples](#examples-2) 60 | - [isValidPublic](#isvalidpublic) 61 | - [Parameters](#parameters-3) 62 | - [Examples](#examples-3) 63 | - [isValidPrivate](#isvalidprivate) 64 | - [Parameters](#parameters-4) 65 | - [Examples](#examples-4) 66 | - [sign](#sign) 67 | - [Parameters](#parameters-5) 68 | - [Examples](#examples-5) 69 | - [signHash](#signhash) 70 | - [Parameters](#parameters-6) 71 | - [verify](#verify) 72 | - [Parameters](#parameters-7) 73 | - [Examples](#examples-6) 74 | - [recover](#recover) 75 | - [Parameters](#parameters-8) 76 | - [Examples](#examples-7) 77 | - [recoverHash](#recoverhash) 78 | - [Parameters](#parameters-9) 79 | - [sha256](#sha256) 80 | - [Parameters](#parameters-10) 81 | - [Examples](#examples-8) 82 | - [pubkey](#pubkey) 83 | 84 | ## wif 85 | 86 | [Wallet Import Format](https://en.bitcoin.it/wiki/Wallet_import_format) 87 | 88 | Type: [string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) 89 | 90 | ## ecc 91 | 92 | ### initialize 93 | 94 | Initialize by running some self-checking code. This should take a 95 | second to gather additional CPU entropy used during private key 96 | generation. 97 | 98 | Initialization happens once even if called multiple times. 99 | 100 | Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)** 101 | 102 | ### unsafeRandomKey 103 | 104 | Does not pause to gather CPU entropy. 105 | 106 | Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)<PrivateKey>** test key 107 | 108 | ### randomKey 109 | 110 | #### Parameters 111 | 112 | - `cpuEntropyBits` **[number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)** gather additional entropy 113 | from a CPU mining algorithm. This will already happen once by 114 | default. (optional, default `0`) 115 | 116 | #### Examples 117 | 118 | ```javascript 119 | ecc.randomKey().then(privateKey => { 120 | console.log('Private Key:\t', privateKey) // wif 121 | console.log('Public Key:\t', ecc.privateToPublic(privateKey)) // EOSkey... 122 | }) 123 | ``` 124 | 125 | Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[wif](#wif)>** 126 | 127 | ### seedPrivate 128 | 129 | #### Parameters 130 | 131 | - `seed` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** any length string. This is private. The same 132 | seed produces the same private key every time. At least 128 random 133 | bits should be used to produce a good private key. 134 | 135 | #### Examples 136 | 137 | ```javascript 138 | ecc.seedPrivate('secret') === wif 139 | ``` 140 | 141 | Returns **[wif](#wif)** 142 | 143 | ### privateToPublic 144 | 145 | #### Parameters 146 | 147 | - `wif` **[wif](#wif)** 148 | - `pubkey_prefix` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** public key prefix (optional, default `'EOS'`) 149 | 150 | #### Examples 151 | 152 | ```javascript 153 | ecc.privateToPublic(wif) === pubkey 154 | ``` 155 | 156 | Returns **[pubkey](#pubkey)** 157 | 158 | ### isValidPublic 159 | 160 | #### Parameters 161 | 162 | - `pubkey` **[pubkey](#pubkey)** like EOSKey.. 163 | - `pubkey_prefix` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** (optional, default `'EOS'`) 164 | 165 | #### Examples 166 | 167 | ```javascript 168 | ecc.isValidPublic(pubkey) === true 169 | ``` 170 | 171 | Returns **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** valid 172 | 173 | ### isValidPrivate 174 | 175 | #### Parameters 176 | 177 | - `wif` **[wif](#wif)** 178 | 179 | #### Examples 180 | 181 | ```javascript 182 | ecc.isValidPrivate(wif) === true 183 | ``` 184 | 185 | Returns **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** valid 186 | 187 | ### sign 188 | 189 | Create a signature using data or a hash. 190 | 191 | #### Parameters 192 | 193 | - `data` **([string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Buffer](https://nodejs.org/api/buffer.html))** 194 | - `privateKey` **([wif](#wif) | PrivateKey)** 195 | - `encoding` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** data encoding (if string) (optional, default `'utf8'`) 196 | 197 | #### Examples 198 | 199 | ```javascript 200 | ecc.sign('I am alive', wif) 201 | ``` 202 | 203 | Returns **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** string signature 204 | 205 | ### signHash 206 | 207 | #### Parameters 208 | 209 | - `dataSha256` **([String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Buffer](https://nodejs.org/api/buffer.html))** sha256 hash 32 byte buffer or string 210 | - `privateKey` **([wif](#wif) | PrivateKey)** 211 | - `encoding` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** dataSha256 encoding (if string) (optional, default `'hex'`) 212 | 213 | Returns **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** string signature 214 | 215 | ### verify 216 | 217 | Verify signed data. 218 | 219 | #### Parameters 220 | 221 | - `signature` **([string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Buffer](https://nodejs.org/api/buffer.html))** buffer or hex string 222 | - `data` **([string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Buffer](https://nodejs.org/api/buffer.html))** 223 | - `pubkey` **([pubkey](#pubkey) | PublicKey)** 224 | - `encoding` (optional, default `'utf8'`) 225 | - `hashData` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** sha256 hash data before verify (optional, default `true`) 226 | 227 | #### Examples 228 | 229 | ```javascript 230 | ecc.verify(signature, 'I am alive', pubkey) === true 231 | ``` 232 | 233 | Returns **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** 234 | 235 | ### recover 236 | 237 | Recover the public key used to create the signature. 238 | 239 | #### Parameters 240 | 241 | - `signature` **([String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Buffer](https://nodejs.org/api/buffer.html))** (EOSbase58sig.., Hex, Buffer) 242 | - `data` **([String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Buffer](https://nodejs.org/api/buffer.html))** full data 243 | - `encoding` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** data encoding (if data is a string) (optional, default `'utf8'`) 244 | 245 | #### Examples 246 | 247 | ```javascript 248 | ecc.recover(signature, 'I am alive') === pubkey 249 | ``` 250 | 251 | Returns **[pubkey](#pubkey)** 252 | 253 | ### recoverHash 254 | 255 | #### Parameters 256 | 257 | - `signature` **([String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Buffer](https://nodejs.org/api/buffer.html))** (EOSbase58sig.., Hex, Buffer) 258 | - `dataSha256` **([String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Buffer](https://nodejs.org/api/buffer.html))** sha256 hash 32 byte buffer or hex string 259 | - `encoding` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** dataSha256 encoding (if dataSha256 is a string) (optional, default `'hex'`) 260 | 261 | Returns **PublicKey** 262 | 263 | ### sha256 264 | 265 | #### Parameters 266 | 267 | - `data` **([string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Buffer](https://nodejs.org/api/buffer.html))** always binary, you may need Buffer.from(data, 'hex') 268 | - `resultEncoding` (optional, default `'hex'`) 269 | - `encoding` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** result encoding 'hex', 'binary' or 'base64' (optional, default `'hex'`) 270 | 271 | #### Examples 272 | 273 | ```javascript 274 | ecc.sha256('hashme') === '02208b..' 275 | ``` 276 | 277 | ```javascript 278 | ecc.sha256(Buffer.from('02208b', 'hex')) === '29a23..' 279 | ``` 280 | 281 | Returns **([string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Buffer](https://nodejs.org/api/buffer.html))** Buffer when encoding is null, or string 282 | 283 | ## pubkey 284 | 285 | EOSKey.. 286 | 287 | Type: [string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) 288 | 289 | # Usage (Object API) 290 | 291 | ```js 292 | let {PrivateKey, PublicKey, Signature, Aes, key_utils, config} = require('eosjs-ecc') 293 | 294 | // Create a new random private key 295 | let privateWif 296 | PrivateKey.randomKey().then(privateKey => privateWif = privateKey.toWif()) 297 | 298 | // Convert to a public key 299 | pubkey = PrivateKey.fromString(privateWif).toPublic().toString() 300 | ``` 301 | 302 | - [PrivateKey](./src/key_private.js) 303 | - [PublicKey](./src/key_public.js) 304 | - [Signature](./src/signature.js) 305 | - [Aes](./src/aes.js) 306 | - [key_utils](./src/key_utils.js) 307 | - [config](./src/config.js) 308 | 309 | # Browser 310 | 311 | ```bash 312 | git clone https://github.com/EOSIO/eosjs-ecc.git 313 | cd eosjs-ecc 314 | yarn 315 | yarn build_browser 316 | # builds: ./dist/eosjs-ecc.js 317 | # Verify release hash 318 | ``` 319 | 320 | ```html 321 | 322 | ``` 323 | 324 | ```js 325 | var ecc = eosjs_ecc 326 | 327 | ecc.randomKey().then(privateWif => { 328 | var pubkey = ecc.privateToPublic(privateWif) 329 | console.log(pubkey) 330 | }) 331 | ``` 332 | -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | See console object: eosjs_ecc 8 | 9 | 10 | -------------------------------------------------------------------------------- /dist/run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -o errexit 3 | 4 | cd .. 5 | npm install 6 | npm run test 7 | 8 | npm run build 9 | npm run minimize 10 | 11 | echo "Subresource Integrity" 12 | npm run srisum 13 | -------------------------------------------------------------------------------- /dist/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Mocha Tests 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eosjs-ecc", 3 | "version": "4.0.8", 4 | "description": "Elliptic curve cryptography functions", 5 | "keywords": [ 6 | "ECC", 7 | "Private Key", 8 | "Public Key", 9 | "Signature", 10 | "AES", 11 | "Encryption", 12 | "Decryption" 13 | ], 14 | "main": "lib/index.js", 15 | "files": [ 16 | "README.md", 17 | "docs", 18 | "lib" 19 | ], 20 | "scripts": { 21 | "test": "mocha --use_strict src/*.test.js", 22 | "test_lib": "mocha --use_strict lib/*.test.js", 23 | "coverage": "nyc --reporter=lcov --reporter=text npm test", 24 | "coveralls": "yarn coverage && cat ./coverage/lcov.info | ./node_modules/.bin/coveralls", 25 | "build": "yarn build_lib && yarn build_browser", 26 | "build_lib": "rm -f lib/* && babel src --out-dir lib", 27 | "build_browser": "mkdir -p lib && browserify -o lib/eosjs-ecc.js -s eosjs_ecc lib/index.js", 28 | "build_browser_test": "yarn build && browserify -o dist/test.js lib/*.test.js", 29 | "documentation": "node_modules/documentation/bin/documentation.js", 30 | "minimize": "terser lib/eosjs-ecc.js -o lib/eosjs-ecc.min.js --source-map --compress --mangle", 31 | "docs": "yarn documentation -- readme src/api_common.js --section \"Common API\" --shallow", 32 | "srisum": "npx srisum lib/eosjs-ecc.*", 33 | "prepublishOnly": "yarn build && yarn minimize && yarn test_lib && yarn docs" 34 | }, 35 | "repository": { 36 | "type": "git", 37 | "url": "git://github.com/EOSIO/eosjs-ecc.git" 38 | }, 39 | "dependencies": { 40 | "@babel/runtime": "7.6.0", 41 | "bigi": "1.4.2", 42 | "browserify-aes": "1.0.6", 43 | "bs58": "4.0.1", 44 | "bytebuffer": "5.0.1", 45 | "create-hash": "1.1.3", 46 | "create-hmac": "1.1.6", 47 | "ecurve": "1.0.5", 48 | "randombytes": "2.0.5" 49 | }, 50 | "license": "MIT", 51 | "devDependencies": { 52 | "@babel/cli": "^7.6.0", 53 | "@babel/core": "^7.6.0", 54 | "@babel/plugin-transform-runtime": "^7.6.0", 55 | "@babel/preset-env": "^7.6.0", 56 | "browserify": "^16.2.3", 57 | "coveralls": "^3.0.3", 58 | "documentation": "^12.1.4", 59 | "mocha": "^5.2.0", 60 | "nyc": "^14.1.0", 61 | "terser": "^3.17.0" 62 | }, 63 | "nyc": { 64 | "temp-directory": "./coverage/.nyc_output" 65 | }, 66 | "babel": { 67 | "presets": [ 68 | "@babel/preset-env" 69 | ], 70 | "plugins": [ 71 | "@babel/plugin-transform-runtime" 72 | ] 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /scripts/is_latest.sh: -------------------------------------------------------------------------------- 1 | is_latest=false; 2 | 3 | current_commit="$(git rev-parse HEAD)"; 4 | tags="$(git tag --sort=-creatordate)"; 5 | 6 | IFS='\n' read -ra arry <<< "$tags" 7 | 8 | latest_tag="${arry[0]}" 9 | 10 | if [ "$latest_tag" == "" ]; then 11 | latest_tag="v0.0.0"; 12 | else 13 | tag_commit="$(git rev-list -n 1 ${latest_tag})"; 14 | if [ "$tag_commit" == "$current_commit" ]; then 15 | is_latest=true; 16 | fi 17 | fi 18 | 19 | echo "tag_commit: ${tag_commit}"; 20 | echo "current_commit: ${current_commit}"; 21 | echo "is_latest: ${is_latest}"; 22 | 23 | export TRAVIS_IS_LATEST_TAG="$is_latest" -------------------------------------------------------------------------------- /scripts/publish-edge.sh: -------------------------------------------------------------------------------- 1 | echo "Starting publish edge" && \ 2 | 3 | . "${TRAVIS_BUILD_DIR}/scripts/publish-utils.sh" && \ 4 | 5 | echo "Running on branch/tag ${TRAVIS_BRANCH}" && \ 6 | 7 | echo "Setting up git" && \ 8 | setup_git && \ 9 | 10 | echo "Creating new version" && \ 11 | git checkout -- . && git status && \ 12 | 13 | # # get the short commit hash to include in the npm package 14 | current_commit="$(git rev-parse --short HEAD)" && \ 15 | 16 | npm install && \ 17 | 18 | npm version prerelease -preid "${current_commit}" -no-git-tag-version && \ 19 | 20 | git commit -a -m "Updating version [skip ci]" && \ 21 | 22 | echo "Publish to NPM" && \ 23 | 24 | cp .npmrc.template $HOME/.npmrc && \ 25 | 26 | npm publish --tag edge && \ 27 | 28 | echo "finished publish to edge" -------------------------------------------------------------------------------- /scripts/publish-tag.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | echo "travis tag : $TRAVIS_TAG" 3 | 4 | . "${TRAVIS_BUILD_DIR}/scripts/publish-utils.sh"; 5 | 6 | if [[ "$TRAVIS_TAG" == "" ]]; then 7 | echo "No tag specified, skipping..."; 8 | else 9 | echo "Running on branch/tag ${TRAVIS_TAG}": 10 | 11 | echo "Setting up git" 12 | setup_git 13 | 14 | echo "Creating new version" 15 | git checkout -- . 16 | 17 | git status 18 | 19 | npm version -no-git-tag-version $TRAVIS_TAG 20 | 21 | echo "Pushing to git" 22 | git commit -a -m "Publishing version ${TRAVIS_TAG} [skip ci]" 23 | 24 | git push origin HEAD:${1} 25 | 26 | echo "Build and Publish to NPM" 27 | 28 | cp .npmrc.template $HOME/.npmrc 29 | 30 | if [[ "$TRAVIS_TAG" == *"-beta"* ]]; then 31 | echo "Publishing with beta tag to npm" 32 | npm publish --tag beta 33 | else 34 | echo "Publishing with latest tag to npm" 35 | npm publish 36 | fi 37 | fi 38 | 39 | -------------------------------------------------------------------------------- /scripts/publish-utils.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | setup_git() { 4 | # Set the user name and email to match the API token holder 5 | # This will make sure the git commits will have the correct photo 6 | # and the user gets the credit for a checkin 7 | git config --global user.email "devops@block.one" 8 | git config --global user.name "blockone-devops" 9 | git config --global push.default matching 10 | 11 | # Get the credentials from a file 12 | git config credential.helper "store --file=.git/credentials" 13 | 14 | # This associates the API Key with the account 15 | echo "https://${GITHUB_API_KEY}:@github.com" > .git/credentials 16 | } -------------------------------------------------------------------------------- /src/aes.js: -------------------------------------------------------------------------------- 1 | const randomBytes = require('randombytes') 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 | module.exports = { 12 | encrypt, 13 | decrypt 14 | } 15 | 16 | /** 17 | Spec: http://localhost:3002/steem/@dantheman/how-to-encrypt-a-memo-when-transferring-steem 18 | 19 | @throws {Error|TypeError} - "Invalid Key, ..." 20 | 21 | @arg {PrivateKey} private_key - required and used for decryption 22 | @arg {PublicKey} public_key - required and used to calcualte the shared secret 23 | @arg {string} [nonce = uniqueNonce()] - assigned a random unique uint64 24 | 25 | @return {object} 26 | @property {string} nonce - random or unique uint64, provides entropy when re-using the same private/public keys. 27 | @property {Buffer} message - Plain text message 28 | @property {number} checksum - shared secret checksum 29 | */ 30 | function encrypt(private_key, public_key, message, nonce = uniqueNonce()) { 31 | return crypt(private_key, public_key, nonce, message) 32 | } 33 | 34 | /** 35 | Spec: http://localhost:3002/steem/@dantheman/how-to-encrypt-a-memo-when-transferring-steem 36 | 37 | @arg {PrivateKey} private_key - required and used for decryption 38 | @arg {PublicKey} public_key - required and used to calcualte the shared secret 39 | @arg {string} nonce - random or unique uint64, provides entropy when re-using the same private/public keys. 40 | @arg {Buffer} message - Encrypted or plain text message 41 | @arg {number} checksum - shared secret checksum 42 | 43 | @throws {Error|TypeError} - "Invalid Key, ..." 44 | 45 | @return {Buffer} - message 46 | */ 47 | function decrypt(private_key, public_key, nonce, message, checksum) { 48 | return crypt(private_key, public_key, nonce, message, checksum).message 49 | } 50 | 51 | /** 52 | @arg {Buffer} message - Encrypted or plain text message (see checksum) 53 | @arg {number} checksum - shared secret checksum (null to encrypt, non-null to decrypt) 54 | @private 55 | */ 56 | function crypt(private_key, public_key, nonce, message, checksum) { 57 | private_key = PrivateKey(private_key) 58 | if (!private_key) 59 | throw new TypeError('private_key is required') 60 | 61 | public_key = PublicKey(public_key) 62 | if (!public_key) 63 | throw new TypeError('public_key is required') 64 | 65 | nonce = toLongObj(nonce) 66 | if (!nonce) 67 | throw new TypeError('nonce is required') 68 | 69 | if (!Buffer.isBuffer(message)) { 70 | if (typeof message !== 'string') 71 | throw new TypeError('message should be buffer or string') 72 | message = new Buffer(message, 'binary') 73 | } 74 | if (checksum && typeof checksum !== 'number') 75 | throw new TypeError('checksum should be a number') 76 | 77 | const S = private_key.getSharedSecret(public_key); 78 | let ebuf = new ByteBuffer(ByteBuffer.DEFAULT_CAPACITY, ByteBuffer.LITTLE_ENDIAN) 79 | ebuf.writeUint64(nonce) 80 | ebuf.append(S.toString('binary'), 'binary') 81 | ebuf = new Buffer(ebuf.copy(0, ebuf.offset).toBinary(), 'binary') 82 | const encryption_key = hash.sha512(ebuf) 83 | 84 | // D E B U G 85 | // console.log('crypt', { 86 | // priv_to_pub: private_key.toPublic().toString(), 87 | // pub: public_key.toString(), 88 | // nonce: nonce.toString(), 89 | // message: message.length, 90 | // checksum, 91 | // S: S.toString('hex'), 92 | // encryption_key: encryption_key.toString('hex'), 93 | // }) 94 | 95 | const iv = encryption_key.slice(32, 48) 96 | const key = encryption_key.slice(0, 32) 97 | 98 | // check is first 64 bit of sha256 hash treated as uint64_t truncated to 32 bits. 99 | let check = hash.sha256(encryption_key) 100 | check = check.slice(0, 4) 101 | const cbuf = ByteBuffer.fromBinary(check.toString('binary'), ByteBuffer.DEFAULT_CAPACITY, ByteBuffer.LITTLE_ENDIAN) 102 | check = cbuf.readUint32() 103 | 104 | if (checksum) { 105 | if (check !== checksum) 106 | throw new Error('Invalid key') 107 | message = cryptoJsDecrypt(message, key, iv) 108 | } else { 109 | message = cryptoJsEncrypt(message, key, iv) 110 | } 111 | return {nonce, message, checksum: check} 112 | } 113 | 114 | /** This method does not use a checksum, the returned data must be validated some other way. 115 | 116 | @arg {string|Buffer} message - ciphertext binary format 117 | @arg {string|Buffer} key - 256bit 118 | @arg {string|Buffer} iv - 128bit 119 | 120 | @return {Buffer} 121 | */ 122 | function cryptoJsDecrypt(message, key, iv) { 123 | assert(message, "Missing cipher text") 124 | message = toBinaryBuffer(message) 125 | const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv) 126 | // decipher.setAutoPadding(true) 127 | message = Buffer.concat([decipher.update(message), decipher.final()]) 128 | return message 129 | } 130 | 131 | /** This method does not use a checksum, the returned data must be validated some other way. 132 | @arg {string|Buffer} message - plaintext binary format 133 | @arg {string|Buffer} key - 256bit 134 | @arg {string|Buffer} iv - 128bit 135 | 136 | @return {Buffer} 137 | */ 138 | function cryptoJsEncrypt(message, key, iv) { 139 | assert(message, "Missing plain text") 140 | message = toBinaryBuffer(message) 141 | const cipher = crypto.createCipheriv('aes-256-cbc', key, iv) 142 | // cipher.setAutoPadding(true) 143 | message = Buffer.concat([cipher.update(message), cipher.final()]) 144 | return message 145 | } 146 | 147 | /** @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. 148 | */ 149 | function uniqueNonce() { 150 | if(unique_nonce_entropy === null) { 151 | const b = new Uint8Array(randomBytes(2)) 152 | unique_nonce_entropy = parseInt(b[0] << 8 | b[1], 10) 153 | } 154 | let long = Long.fromNumber(Date.now()) 155 | const entropy = ++unique_nonce_entropy % 0xFFFF 156 | // console.log('uniqueNonce date\t', ByteBuffer.allocate(8).writeUint64(long).toHex(0)) 157 | // console.log('uniqueNonce entropy\t', ByteBuffer.allocate(8).writeUint64(Long.fromNumber(entropy)).toHex(0)) 158 | long = long.shiftLeft(16).or(Long.fromNumber(entropy)); 159 | // console.log('uniqueNonce final\t', ByteBuffer.allocate(8).writeUint64(long).toHex(0)) 160 | return long.toString() 161 | } 162 | let unique_nonce_entropy = null 163 | // for(let i=1; i < 10; i++) key.uniqueNonce() 164 | 165 | const toLongObj = o => (o ? Long.isLong(o) ? o : Long.fromString(o) : o) 166 | const toBinaryBuffer = o => (o ? Buffer.isBuffer(o) ? o : new Buffer(o, 'binary') : o) 167 | -------------------------------------------------------------------------------- /src/api_common.js: -------------------------------------------------------------------------------- 1 | const Aes = require("./aes") 2 | const PrivateKey = require("./key_private") 3 | const PublicKey = require("./key_public") 4 | const Signature = require("./signature") 5 | const key_utils = require("./key_utils") 6 | const hash = require("./hash") 7 | 8 | /** 9 | [Wallet Import Format](https://en.bitcoin.it/wiki/Wallet_import_format) 10 | @typedef {string} wif 11 | */ 12 | /** 13 | EOSKey.. 14 | @typedef {string} pubkey 15 | */ 16 | 17 | /** @namespace */ 18 | const ecc = { 19 | /** 20 | Initialize by running some self-checking code. This should take a 21 | second to gather additional CPU entropy used during private key 22 | generation. 23 | 24 | Initialization happens once even if called multiple times. 25 | 26 | @return {Promise} 27 | */ 28 | initialize: PrivateKey.initialize, 29 | 30 | /** 31 | Does not pause to gather CPU entropy. 32 | @return {Promise} test key 33 | */ 34 | unsafeRandomKey: () => ( 35 | PrivateKey.unsafeRandomKey().then(key => key.toString()) 36 | ), 37 | 38 | /** 39 | @arg {number} [cpuEntropyBits = 0] gather additional entropy 40 | from a CPU mining algorithm. This will already happen once by 41 | default. 42 | 43 | @return {Promise} 44 | 45 | @example 46 | ecc.randomKey().then(privateKey => { 47 | console.log('Private Key:\t', privateKey) // wif 48 | console.log('Public Key:\t', ecc.privateToPublic(privateKey)) // EOSkey... 49 | }) 50 | */ 51 | randomKey: (cpuEntropyBits) => ( 52 | PrivateKey.randomKey(cpuEntropyBits).then(key => key.toString()) 53 | ), 54 | 55 | /** 56 | 57 | @arg {string} seed - any length string. This is private. The same 58 | seed produces the same private key every time. At least 128 random 59 | bits should be used to produce a good private key. 60 | @return {wif} 61 | 62 | @example ecc.seedPrivate('secret') === wif 63 | */ 64 | seedPrivate: seed => PrivateKey.fromSeed(seed).toString(), 65 | 66 | /** 67 | @arg {wif} wif 68 | @arg {string} [pubkey_prefix = 'EOS'] - public key prefix 69 | 70 | @return {pubkey} 71 | 72 | @example ecc.privateToPublic(wif) === pubkey 73 | */ 74 | privateToPublic: (wif, pubkey_prefix = 'EOS') => 75 | PrivateKey(wif).toPublic().toString(pubkey_prefix), 76 | 77 | /** 78 | @arg {pubkey} pubkey - like EOSKey.. 79 | @arg {string} [pubkey_prefix = 'EOS'] 80 | 81 | @return {boolean} valid 82 | 83 | @example ecc.isValidPublic(pubkey) === true 84 | */ 85 | isValidPublic: (pubkey, pubkey_prefix = 'EOS') => 86 | PublicKey.isValid(pubkey, pubkey_prefix), 87 | 88 | /** 89 | @arg {wif} wif 90 | @return {boolean} valid 91 | 92 | @example ecc.isValidPrivate(wif) === true 93 | */ 94 | isValidPrivate: (wif) => PrivateKey.isValid(wif), 95 | 96 | /** 97 | Create a signature using data or a hash. 98 | 99 | @arg {string|Buffer} data 100 | @arg {wif|PrivateKey} privateKey 101 | @arg {String} [encoding = 'utf8'] - data encoding (if string) 102 | 103 | @return {string} string signature 104 | 105 | @example ecc.sign('I am alive', wif) 106 | */ 107 | sign: (data, privateKey, encoding = 'utf8') => { 108 | if(encoding === true) { 109 | throw new TypeError('API changed, use signHash(..) instead') 110 | } else { 111 | if(encoding === false) { 112 | console.log('Warning: ecc.sign hashData parameter was removed'); 113 | } 114 | } 115 | return Signature.sign(data, privateKey, encoding).toString() 116 | }, 117 | 118 | /** 119 | @arg {String|Buffer} dataSha256 - sha256 hash 32 byte buffer or string 120 | @arg {wif|PrivateKey} privateKey 121 | @arg {String} [encoding = 'hex'] - dataSha256 encoding (if string) 122 | 123 | @return {string} string signature 124 | */ 125 | signHash: (dataSha256, privateKey, encoding = 'hex') => { 126 | return Signature.signHash(dataSha256, privateKey, encoding).toString() 127 | }, 128 | 129 | /** 130 | Verify signed data. 131 | 132 | @arg {string|Buffer} signature - buffer or hex string 133 | @arg {string|Buffer} data 134 | @arg {pubkey|PublicKey} pubkey 135 | @arg {boolean} [hashData = true] - sha256 hash data before verify 136 | @return {boolean} 137 | 138 | @example ecc.verify(signature, 'I am alive', pubkey) === true 139 | */ 140 | verify: (signature, data, pubkey, encoding = 'utf8') => { 141 | if(encoding === true) { 142 | throw new TypeError('API changed, use verifyHash(..) instead') 143 | } else { 144 | if(encoding === false) { 145 | console.log('Warning: ecc.verify hashData parameter was removed'); 146 | } 147 | } 148 | signature = Signature.from(signature) 149 | return signature.verify(data, pubkey, encoding) 150 | }, 151 | 152 | verifyHash(signature, dataSha256, pubkey, encoding = 'hex') { 153 | signature = Signature.from(signature) 154 | return signature.verifyHash(dataSha256, pubkey, encoding) 155 | }, 156 | 157 | /** 158 | Recover the public key used to create the signature. 159 | 160 | @arg {String|Buffer} signature (EOSbase58sig.., Hex, Buffer) 161 | @arg {String|Buffer} data - full data 162 | @arg {String} [encoding = 'utf8'] - data encoding (if data is a string) 163 | 164 | @return {pubkey} 165 | 166 | @example ecc.recover(signature, 'I am alive') === pubkey 167 | */ 168 | recover: (signature, data, encoding = 'utf8') => { 169 | if(encoding === true) { 170 | throw new TypeError('API changed, use recoverHash(signature, data) instead') 171 | } else { 172 | if(encoding === false) { 173 | console.log('Warning: ecc.recover hashData parameter was removed'); 174 | } 175 | } 176 | signature = Signature.from(signature) 177 | return signature.recover(data, encoding).toString() 178 | }, 179 | 180 | /** 181 | @arg {String|Buffer} signature (EOSbase58sig.., Hex, Buffer) 182 | @arg {String|Buffer} dataSha256 - sha256 hash 32 byte buffer or hex string 183 | @arg {String} [encoding = 'hex'] - dataSha256 encoding (if dataSha256 is a string) 184 | 185 | @return {PublicKey} 186 | */ 187 | recoverHash: (signature, dataSha256, encoding = 'hex') => { 188 | signature = Signature.from(signature) 189 | return signature.recoverHash(dataSha256, encoding).toString() 190 | }, 191 | 192 | /** @arg {string|Buffer} data - always binary, you may need Buffer.from(data, 'hex') 193 | @arg {string} [encoding = 'hex'] - result encoding 'hex', 'binary' or 'base64' 194 | @return {string|Buffer} - Buffer when encoding is null, or string 195 | 196 | @example ecc.sha256('hashme') === '02208b..' 197 | @example ecc.sha256(Buffer.from('02208b', 'hex')) === '29a23..' 198 | */ 199 | sha256: (data, resultEncoding = 'hex') => hash.sha256(data, resultEncoding) 200 | } 201 | 202 | module.exports = ecc 203 | -------------------------------------------------------------------------------- /src/api_object.js: -------------------------------------------------------------------------------- 1 | 2 | const Aes = require("./aes") 3 | const PrivateKey = require("./key_private") 4 | const PublicKey = require("./key_public") 5 | const Signature = require("./signature") 6 | const key_utils = require("./key_utils") 7 | 8 | module.exports = { 9 | Aes, PrivateKey, PublicKey, 10 | Signature, key_utils 11 | } 12 | -------------------------------------------------------------------------------- /src/common.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | const assert = require('assert') 3 | 4 | const ecc = require('.') 5 | 6 | const wif = '5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss' 7 | 8 | describe('Common API', () => { 9 | it('unsafeRandomKey', async function() { 10 | const pvt = await ecc.unsafeRandomKey() 11 | assert.equal(typeof pvt, 'string', 'pvt') 12 | assert(/^5[HJK]/.test(wif)) 13 | // assert(/^PVT_K1_/.test(pvt)) // todo 14 | }) 15 | 16 | it('seedPrivate', () => { 17 | assert.equal(ecc.seedPrivate(''), wif) 18 | // assert.equal(ecc.seedPrivate(''), 'PVT_K1_2jH3nnhxhR3zPUcsKaWWZC9ZmZAnKm3GAnFD1xynGJE1Znuvjd') 19 | }) 20 | 21 | it('privateToPublic', () => { 22 | // const pub = 'PUB_K1_859gxfnXyUriMgUeThh1fWv3oqcpLFyHa3TfFYC4PK2Ht7beeX' 23 | const pub = 'EOS859gxfnXyUriMgUeThh1fWv3oqcpLFyHa3TfFYC4PK2HqhToVM' 24 | assert.equal(ecc.privateToPublic(wif), pub) 25 | }) 26 | 27 | it('isValidPublic', () => { 28 | const keys = [ 29 | [true, 'PUB_K1_859gxfnXyUriMgUeThh1fWv3oqcpLFyHa3TfFYC4PK2Ht7beeX'], 30 | [true, 'EOS859gxfnXyUriMgUeThh1fWv3oqcpLFyHa3TfFYC4PK2HqhToVM'], 31 | [false, 'MMM859gxfnXyUriMgUeThh1fWv3oqcpLFyHa3TfFYC4PK2HqhToVM'], 32 | [false, 'EOS859gxfnXyUriMgUeThh1fWv3oqcpLFyHa3TfFYC4PK2HqhToVm', 'EOS'], 33 | [true, 'PUB859gxfnXyUriMgUeThh1fWv3oqcpLFyHa3TfFYC4PK2HqhToVM', 'PUB'], 34 | [false, 'PUB859gxfnXyUriMgUeThh1fWv3oqcpLFyHa3TfFYC4PK2HqhToVm', 'PUB'], 35 | ] 36 | for(const key of keys) { 37 | const [valid, pubkey, prefix] = key 38 | assert.equal(valid, ecc.isValidPublic(pubkey, prefix), pubkey) 39 | } 40 | }) 41 | 42 | it('isValidPrivate', () => { 43 | const keys = [ 44 | [true, '5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss'], 45 | [false, '5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjsm'], 46 | ] 47 | for(const key of keys) { 48 | assert.equal(key[0], ecc.isValidPrivate(key[1]), key[1]) 49 | } 50 | }) 51 | 52 | it('hashs', () => { 53 | const hashes = [ 54 | // ['sha1', 'da39a3ee5e6b4b0d3255bfef95601890afd80709'], 55 | ['sha256', 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'], 56 | ] 57 | for(const hash of hashes) { 58 | assert.equal(ecc[hash[0]](''), hash[1]) 59 | assert.equal(ecc[hash[0]](Buffer.from('')), hash[1]) 60 | } 61 | }) 62 | 63 | it('signatures', () => { 64 | const pvt = ecc.seedPrivate('') 65 | const pubkey = ecc.privateToPublic(pvt) 66 | 67 | const data = 'hi' 68 | const dataSha256 = ecc.sha256(data) 69 | 70 | const sigs = [ 71 | ecc.sign(data, pvt), 72 | ecc.signHash(dataSha256, pvt) 73 | ] 74 | 75 | for(const sig of sigs) { 76 | assert(ecc.verify(sig, data, pubkey), 'verify data') 77 | assert(ecc.verifyHash(sig, dataSha256, pubkey), 'verify hash') 78 | assert.equal(pubkey, ecc.recover(sig, data), 'recover from data') 79 | assert.equal(pubkey, ecc.recoverHash(sig, dataSha256), 'recover from hash') 80 | } 81 | }) 82 | }) 83 | 84 | describe('Common API (initialized)', () => { 85 | it('initialize', () => ecc.initialize()) 86 | 87 | it('randomKey', () => { 88 | const cpuEntropyBits = 1 89 | ecc.key_utils.addEntropy(1, 2, 3) 90 | const pvt = ecc.unsafeRandomKey().then(pvt => { 91 | assert.equal(typeof pvt, 'string', 'pvt') 92 | assert(/^5[HJK]/.test(wif)) 93 | // assert(/^PVT_K1_/.test(pvt)) 94 | }) 95 | }) 96 | }) 97 | -------------------------------------------------------------------------------- /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(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(32) 23 | var v = new Buffer(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([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([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([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 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/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 | function toCompact(i, compressed) { 11 | if (compressed) i += 4 12 | i += 27 13 | 14 | var buffer = new Buffer(65) 15 | buffer.writeUInt8(i, 0) 16 | 17 | r.toBuffer(32).copy(buffer, 1) 18 | s.toBuffer(32).copy(buffer, 33) 19 | 20 | return buffer 21 | } 22 | 23 | function toDER() { 24 | var rBa = r.toDERInteger() 25 | var sBa = s.toDERInteger() 26 | 27 | var sequence = [] 28 | 29 | // INTEGER 30 | sequence.push(0x02, rBa.length) 31 | sequence = sequence.concat(rBa) 32 | 33 | // INTEGER 34 | sequence.push(0x02, sBa.length) 35 | sequence = sequence.concat(sBa) 36 | 37 | // SEQUENCE 38 | sequence.unshift(0x30, sequence.length) 39 | 40 | return new Buffer(sequence) 41 | } 42 | 43 | function toScriptSignature(hashType) { 44 | var hashTypeBuffer = new Buffer(1) 45 | hashTypeBuffer.writeUInt8(hashType, 0) 46 | 47 | return Buffer.concat([toDER(), hashTypeBuffer]) 48 | } 49 | 50 | return {r, s, toCompact, toDER, toScriptSignature} 51 | } 52 | 53 | // Import operations 54 | ECSignature.parseCompact = function(buffer) { 55 | assert.equal(buffer.length, 65, 'Invalid signature length') 56 | var i = buffer.readUInt8(0) - 27 57 | 58 | // At most 3 bits 59 | assert.equal(i, i & 7, 'Invalid signature parameter') 60 | var compressed = !!(i & 4) 61 | 62 | // Recovery param only 63 | i = i & 3 64 | 65 | var r = BigInteger.fromBuffer(buffer.slice(1, 33)) 66 | var s = BigInteger.fromBuffer(buffer.slice(33)) 67 | 68 | return { 69 | compressed: compressed, 70 | i: i, 71 | signature: ECSignature(r, s) 72 | } 73 | } 74 | 75 | ECSignature.fromDER = function(buffer) { 76 | assert.equal(buffer.readUInt8(0), 0x30, 'Not a DER sequence') 77 | assert.equal(buffer.readUInt8(1), buffer.length - 2, 'Invalid sequence length') 78 | assert.equal(buffer.readUInt8(2), 0x02, 'Expected a DER integer') 79 | 80 | var rLen = buffer.readUInt8(3) 81 | assert(rLen > 0, 'R length is zero') 82 | 83 | var offset = 4 + rLen 84 | assert.equal(buffer.readUInt8(offset), 0x02, 'Expected a DER integer (2)') 85 | 86 | var sLen = buffer.readUInt8(offset + 1) 87 | assert(sLen > 0, 'S length is zero') 88 | 89 | var rB = buffer.slice(4, offset) 90 | var sB = buffer.slice(offset + 2) 91 | offset += 2 + sLen 92 | 93 | if (rLen > 1 && rB.readUInt8(0) === 0x00) { 94 | assert(rB.readUInt8(1) & 0x80, 'R value excessively padded') 95 | } 96 | 97 | if (sLen > 1 && sB.readUInt8(0) === 0x00) { 98 | assert(sB.readUInt8(1) & 0x80, 'S value excessively padded') 99 | } 100 | 101 | assert.equal(offset, buffer.length, 'Invalid DER encoding') 102 | var r = BigInteger.fromDERInteger(rB) 103 | var s = BigInteger.fromDERInteger(sB) 104 | 105 | assert(r.signum() >= 0, 'R value is negative') 106 | assert(s.signum() >= 0, 'S value is negative') 107 | 108 | return ECSignature(r, s) 109 | } 110 | 111 | // FIXME: 0x00, 0x04, 0x80 are SIGHASH_* boundary constants, importing Transaction causes a circular dependency 112 | ECSignature.parseScriptSignature = function(buffer) { 113 | var hashType = buffer.readUInt8(buffer.length - 1) 114 | var hashTypeMod = hashType & ~0x80 115 | 116 | assert(hashTypeMod > 0x00 && hashTypeMod < 0x04, 'Invalid hashType') 117 | 118 | return { 119 | signature: ECSignature.fromDER(buffer.slice(0, -1)), 120 | hashType: hashType 121 | } 122 | } 123 | 124 | module.exports = ECSignature 125 | -------------------------------------------------------------------------------- /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/hash.js: -------------------------------------------------------------------------------- 1 | const createHash = require('create-hash') 2 | const createHmac = require('create-hmac') 3 | 4 | /** @namespace hash */ 5 | 6 | /** @arg {string|Buffer} data 7 | @arg {string} [resultEncoding = null] - 'hex', 'binary' or 'base64' 8 | @return {string|Buffer} - Buffer when resultEncoding is null, or string 9 | */ 10 | function sha1(data, resultEncoding) { 11 | return createHash('sha1').update(data).digest(resultEncoding) 12 | } 13 | 14 | /** @arg {string|Buffer} data 15 | @arg {string} [resultEncoding = null] - 'hex', 'binary' or 'base64' 16 | @return {string|Buffer} - Buffer when resultEncoding is null, or string 17 | */ 18 | function sha256(data, resultEncoding) { 19 | return createHash('sha256').update(data).digest(resultEncoding) 20 | } 21 | 22 | /** @arg {string|Buffer} data 23 | @arg {string} [resultEncoding = null] - 'hex', 'binary' or 'base64' 24 | @return {string|Buffer} - Buffer when resultEncoding is null, or string 25 | */ 26 | function sha512(data, resultEncoding) { 27 | return createHash('sha512').update(data).digest(resultEncoding) 28 | } 29 | 30 | function HmacSHA256(buffer, secret) { 31 | return createHmac('sha256', secret).update(buffer).digest() 32 | } 33 | 34 | function ripemd160(data) { 35 | try{ 36 | return createHash('rmd160').update(data).digest(); 37 | } catch(e){ 38 | return createHash('ripemd160').update(data).digest(); 39 | } 40 | } 41 | 42 | // function hash160(buffer) { 43 | // return ripemd160(sha256(buffer)) 44 | // } 45 | // 46 | // function hash256(buffer) { 47 | // return sha256(sha256(buffer)) 48 | // } 49 | 50 | // 51 | // function HmacSHA512(buffer, secret) { 52 | // return crypto.createHmac('sha512', secret).update(buffer).digest() 53 | // } 54 | 55 | module.exports = { 56 | sha1: sha1, 57 | sha256: sha256, 58 | sha512: sha512, 59 | HmacSHA256: HmacSHA256, 60 | ripemd160: ripemd160 61 | // hash160: hash160, 62 | // hash256: hash256, 63 | // HmacSHA512: HmacSHA512 64 | } 65 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const commonApi = require('./api_common') 2 | const objectApi = require('./api_object') 3 | 4 | const ecc = Object.assign({}, commonApi, objectApi) 5 | 6 | module.exports = ecc 7 | -------------------------------------------------------------------------------- /src/key_private.js: -------------------------------------------------------------------------------- 1 | const ecurve = require('ecurve'); 2 | const Point = ecurve.Point; 3 | const secp256k1 = ecurve.getCurveByName('secp256k1'); 4 | const BigInteger = require('bigi'); 5 | const assert = require('assert'); 6 | 7 | const hash = require('./hash'); 8 | const PublicKey = require('./key_public'); 9 | const keyUtils = require('./key_utils'); 10 | const createHash = require('create-hash') 11 | const promiseAsync = require('./promise-async') 12 | 13 | const G = secp256k1.G 14 | const n = secp256k1.n 15 | 16 | module.exports = PrivateKey; 17 | 18 | /** 19 | @typedef {string} wif - https://en.bitcoin.it/wiki/Wallet_import_format 20 | @typedef {string} pubkey - EOSKey.. 21 | @typedef {ecurve.Point} Point 22 | */ 23 | 24 | /** 25 | @param {BigInteger} d 26 | */ 27 | function PrivateKey(d) { 28 | if(typeof d === 'string') { 29 | return PrivateKey.fromString(d) 30 | } else if(Buffer.isBuffer(d)) { 31 | return PrivateKey.fromBuffer(d) 32 | } else if(typeof d === 'object' && BigInteger.isBigInteger(d.d)) { 33 | return PrivateKey(d.d) 34 | } 35 | 36 | if(!BigInteger.isBigInteger(d)) { 37 | throw new TypeError('Invalid private key') 38 | } 39 | 40 | /** @return {string} private key like PVT_K1_base58privatekey.. */ 41 | function toString() { 42 | // todo, use PVT_K1_ 43 | // return 'PVT_K1_' + keyUtils.checkEncode(toBuffer(), 'K1') 44 | return toWif() 45 | } 46 | 47 | /** 48 | @return {wif} 49 | */ 50 | function toWif() { 51 | var private_key = toBuffer(); 52 | // checksum includes the version 53 | private_key = Buffer.concat([new Buffer([0x80]), private_key]); 54 | return keyUtils.checkEncode(private_key, 'sha256x2') 55 | } 56 | 57 | let public_key; 58 | 59 | /** 60 | @return {Point} 61 | */ 62 | function toPublic() { 63 | if (public_key) { 64 | // cache 65 | // S L O W in the browser 66 | return public_key 67 | } 68 | const Q = secp256k1.G.multiply(d); 69 | return public_key = PublicKey.fromPoint(Q); 70 | } 71 | 72 | function toBuffer() { 73 | return d.toBuffer(32); 74 | } 75 | 76 | /** 77 | ECIES 78 | @arg {string|Object} pubkey wif, PublicKey object 79 | @return {Buffer} 64 byte shared secret 80 | */ 81 | function getSharedSecret(public_key) { 82 | public_key = PublicKey(public_key) 83 | let KB = public_key.toUncompressed().toBuffer() 84 | let KBP = Point.fromAffine( 85 | secp256k1, 86 | BigInteger.fromBuffer( KB.slice( 1,33 )), // x 87 | BigInteger.fromBuffer( KB.slice( 33,65 )) // y 88 | ) 89 | let r = toBuffer() 90 | let P = KBP.multiply(BigInteger.fromBuffer(r)) 91 | let S = P.affineX.toBuffer({size: 32}) 92 | // SHA512 used in ECIES 93 | return hash.sha512(S) 94 | } 95 | 96 | // /** ECIES TODO unit test 97 | // @arg {string|Object} pubkey wif, PublicKey object 98 | // @return {Buffer} 64 byte shared secret 99 | // */ 100 | // function getSharedSecret(public_key) { 101 | // public_key = PublicKey(public_key).toUncompressed() 102 | // var P = public_key.Q.multiply( d ); 103 | // var S = P.affineX.toBuffer({size: 32}); 104 | // // ECIES, adds an extra sha512 105 | // return hash.sha512(S); 106 | // } 107 | 108 | /** 109 | @arg {string} name - child key name. 110 | @return {PrivateKey} 111 | 112 | @example activePrivate = masterPrivate.getChildKey('owner').getChildKey('active') 113 | @example activePrivate.getChildKey('mycontract').getChildKey('myperm') 114 | */ 115 | function getChildKey(name) { 116 | // console.error('WARNING: getChildKey untested against eosd'); // no eosd impl yet 117 | const index = createHash('sha256').update(toBuffer()).update(name).digest() 118 | return PrivateKey(index) 119 | } 120 | 121 | function toHex() { 122 | return toBuffer().toString('hex'); 123 | } 124 | 125 | return { 126 | d, 127 | toWif, 128 | toString, 129 | toPublic, 130 | toBuffer, 131 | getSharedSecret, 132 | getChildKey 133 | } 134 | } 135 | 136 | /** @private */ 137 | function parseKey(privateStr) { 138 | assert.equal(typeof privateStr, 'string', 'privateStr') 139 | const match = privateStr.match(/^PVT_([A-Za-z0-9]+)_([A-Za-z0-9]+)$/) 140 | 141 | if(match === null) { 142 | // legacy WIF - checksum includes the version 143 | const versionKey = keyUtils.checkDecode(privateStr, 'sha256x2') 144 | const version = versionKey.readUInt8(0); 145 | assert.equal(0x80, version, `Expected version ${0x80}, instead got ${version}`) 146 | const privateKey = PrivateKey.fromBuffer(versionKey.slice(1)) 147 | const keyType = 'K1' 148 | const format = 'WIF' 149 | return {privateKey, format, keyType} 150 | } 151 | 152 | assert(match.length === 3, 'Expecting private key like: PVT_K1_base58privateKey..') 153 | const [, keyType, keyString] = match 154 | assert.equal(keyType, 'K1', 'K1 private key expected') 155 | const privateKey = PrivateKey.fromBuffer(keyUtils.checkDecode(keyString, keyType)) 156 | return {privateKey, format: 'PVT', keyType} 157 | } 158 | 159 | PrivateKey.fromHex = function(hex) { 160 | return PrivateKey.fromBuffer(new Buffer(hex, 'hex')); 161 | } 162 | 163 | PrivateKey.fromBuffer = function(buf) { 164 | if (!Buffer.isBuffer(buf)) { 165 | throw new Error("Expecting parameter to be a Buffer type"); 166 | } 167 | if(buf.length === 33 && buf[32] === 1) { 168 | // remove compression flag 169 | buf = buf.slice(0, -1) 170 | } 171 | if (32 !== buf.length) { 172 | throw new Error(`Expecting 32 bytes, instead got ${buf.length}`); 173 | } 174 | return PrivateKey(BigInteger.fromBuffer(buf)); 175 | } 176 | 177 | /** 178 | @arg {string} seed - any length string. This is private, the same seed 179 | produces the same private key every time. 180 | 181 | @return {PrivateKey} 182 | */ 183 | PrivateKey.fromSeed = function(seed) { // generate_private_key 184 | if (!(typeof seed === 'string')) { 185 | throw new Error('seed must be of type string'); 186 | } 187 | return PrivateKey.fromBuffer(hash.sha256(seed)); 188 | } 189 | 190 | /** 191 | @arg {wif} key 192 | @return {boolean} true if key is in the Wallet Import Format 193 | */ 194 | PrivateKey.isWif = function(text) { 195 | try { 196 | assert(parseKey(text).format === 'WIF') 197 | return true 198 | } catch(e) { 199 | return false 200 | } 201 | } 202 | 203 | /** 204 | @arg {wif|Buffer|PrivateKey} key 205 | @return {boolean} true if key is convertable to a private key object. 206 | */ 207 | PrivateKey.isValid = function(key) { 208 | try { 209 | PrivateKey(key) 210 | return true 211 | } catch(e) { 212 | return false 213 | } 214 | } 215 | 216 | /** @deprecated */ 217 | PrivateKey.fromWif = function(str) { 218 | console.log('PrivateKey.fromWif is deprecated, please use PrivateKey.fromString'); 219 | return PrivateKey.fromString(str) 220 | } 221 | 222 | /** 223 | @throws {AssertError|Error} parsing key 224 | @arg {string} privateStr Eosio or Wallet Import Format (wif) -- a secret 225 | */ 226 | PrivateKey.fromString = function(privateStr) { 227 | return parseKey(privateStr).privateKey 228 | } 229 | 230 | /** 231 | Create a new random private key. 232 | 233 | Call initialize() first to run some self-checking code and gather some CPU 234 | entropy. 235 | 236 | @arg {number} [cpuEntropyBits = 0] - additional CPU entropy, this already 237 | happens once so it should not be needed again. 238 | 239 | @return {Promise} - random private key 240 | */ 241 | PrivateKey.randomKey = function(cpuEntropyBits = 0) { 242 | return PrivateKey.initialize().then(() => ( 243 | PrivateKey.fromBuffer(keyUtils.random32ByteBuffer({cpuEntropyBits})) 244 | )) 245 | } 246 | 247 | /** 248 | @return {Promise} for testing, does not require initialize(). 249 | */ 250 | PrivateKey.unsafeRandomKey = function() { 251 | return Promise.resolve( 252 | PrivateKey.fromBuffer(keyUtils.random32ByteBuffer({safe: false})) 253 | ) 254 | } 255 | 256 | 257 | let initialized = false, unitTested = false 258 | 259 | /** 260 | Run self-checking code and gather CPU entropy. 261 | 262 | Initialization happens once even if called multiple times. 263 | 264 | @return {Promise} 265 | */ 266 | function initialize() { 267 | if(initialized) { 268 | return 269 | } 270 | 271 | unitTest() 272 | keyUtils.addEntropy(...keyUtils.cpuEntropy()) 273 | assert(keyUtils.entropyCount() >= 128, 'insufficient entropy') 274 | 275 | initialized = true 276 | } 277 | 278 | PrivateKey.initialize = promiseAsync(initialize) 279 | 280 | /** 281 | Unit test basic private and public key functionality. 282 | 283 | @throws {AssertError} 284 | */ 285 | function unitTest() { 286 | const pvt = PrivateKey(hash.sha256('')) 287 | 288 | const pvtError = 'key comparison test failed on a known private key' 289 | assert.equal(pvt.toWif(), '5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss', pvtError) 290 | assert.equal(pvt.toString(), '5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss', pvtError) 291 | // assert.equal(pvt.toString(), 'PVT_K1_2jH3nnhxhR3zPUcsKaWWZC9ZmZAnKm3GAnFD1xynGJE1Znuvjd', pvtError) 292 | 293 | const pub = pvt.toPublic() 294 | const pubError = 'pubkey string comparison test failed on a known public key' 295 | assert.equal(pub.toString(), 'EOS859gxfnXyUriMgUeThh1fWv3oqcpLFyHa3TfFYC4PK2HqhToVM', pubError) 296 | // assert.equal(pub.toString(), 'PUB_K1_859gxfnXyUriMgUeThh1fWv3oqcpLFyHa3TfFYC4PK2Ht7beeX', pubError) 297 | // assert.equal(pub.toStringLegacy(), 'EOS859gxfnXyUriMgUeThh1fWv3oqcpLFyHa3TfFYC4PK2HqhToVM', pubError) 298 | 299 | doesNotThrow(() => PrivateKey.fromString(pvt.toWif()), 'converting known wif from string') 300 | doesNotThrow(() => PrivateKey.fromString(pvt.toString()), 'converting known pvt from string') 301 | doesNotThrow(() => PublicKey.fromString(pub.toString()), 'converting known public key from string') 302 | // doesNotThrow(() => PublicKey.fromString(pub.toStringLegacy()), 'converting known public key from string') 303 | 304 | unitTested = true 305 | } 306 | 307 | /** @private */ 308 | const doesNotThrow = (cb, msg) => { 309 | try { 310 | cb() 311 | } catch(error) { 312 | error.message = `${msg} ==> ${error.message}` 313 | throw error 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /src/key_public.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const ecurve = require('ecurve'); 3 | const BigInteger = require('bigi'); 4 | const secp256k1 = ecurve.getCurveByName('secp256k1'); 5 | 6 | const hash = require('./hash'); 7 | const keyUtils = require('./key_utils'); 8 | 9 | var G = secp256k1.G 10 | var n = secp256k1.n 11 | 12 | module.exports = PublicKey 13 | 14 | /** 15 | @param {string|Buffer|PublicKey|ecurve.Point} public key 16 | @param {string} [pubkey_prefix = 'EOS'] 17 | */ 18 | function PublicKey(Q, pubkey_prefix = 'EOS') { 19 | if(typeof Q === 'string') { 20 | const publicKey = PublicKey.fromString(Q, pubkey_prefix) 21 | assert(publicKey != null, 'Invalid public key') 22 | return publicKey 23 | } else if(Buffer.isBuffer(Q)) { 24 | return PublicKey.fromBuffer(Q) 25 | } else if(typeof Q === 'object' && Q.Q) { 26 | return PublicKey(Q.Q) 27 | } 28 | 29 | assert.equal(typeof Q, 'object', 'Invalid public key') 30 | assert.equal(typeof Q.compressed, 'boolean', 'Invalid public key') 31 | 32 | function toBuffer(compressed = Q.compressed) { 33 | return Q.getEncoded(compressed); 34 | } 35 | 36 | let pubdata // cache 37 | 38 | // /** 39 | // @todo secp224r1 40 | // @return {string} PUB_K1_base58pubkey.. 41 | // */ 42 | // function toString() { 43 | // if(pubdata) { 44 | // return pubdata 45 | // } 46 | // pubdata = `PUB_K1_` + keyUtils.checkEncode(toBuffer(), 'K1') 47 | // return pubdata; 48 | // } 49 | 50 | /** @todo rename to toStringLegacy 51 | * @arg {string} [pubkey_prefix = 'EOS'] - public key prefix 52 | */ 53 | function toString(pubkey_prefix = 'EOS') { 54 | return pubkey_prefix + keyUtils.checkEncode(toBuffer()) 55 | } 56 | 57 | function toUncompressed() { 58 | var buf = Q.getEncoded(false); 59 | var point = ecurve.Point.decodeFrom(secp256k1, buf); 60 | return PublicKey.fromPoint(point); 61 | } 62 | 63 | /** @deprecated */ 64 | function child( offset ) { 65 | console.error('Deprecated warning: PublicKey.child') 66 | 67 | assert(Buffer.isBuffer(offset), "Buffer required: offset") 68 | assert.equal(offset.length, 32, "offset length") 69 | 70 | offset = Buffer.concat([ toBuffer(), offset ]) 71 | offset = hash.sha256( offset ) 72 | 73 | let c = BigInteger.fromBuffer( offset ) 74 | 75 | if (c.compareTo(n) >= 0) 76 | throw new Error("Child offset went out of bounds, try again") 77 | 78 | 79 | let cG = G.multiply(c) 80 | let Qprime = Q.add(cG) 81 | 82 | if( secp256k1.isInfinity(Qprime) ) 83 | throw new Error("Child offset derived to an invalid key, try again") 84 | 85 | return PublicKey.fromPoint(Qprime) 86 | } 87 | 88 | function toHex() { 89 | return toBuffer().toString('hex'); 90 | } 91 | 92 | return { 93 | Q, 94 | toString, 95 | // toStringLegacy, 96 | toUncompressed, 97 | toBuffer, 98 | child, 99 | toHex 100 | } 101 | } 102 | 103 | /** 104 | @param {string|Buffer|PublicKey|ecurve.Point} pubkey - public key 105 | @param {string} [pubkey_prefix = 'EOS'] 106 | */ 107 | PublicKey.isValid = function(pubkey, pubkey_prefix = 'EOS') { 108 | try { 109 | PublicKey(pubkey, pubkey_prefix) 110 | return true 111 | } catch(e) { 112 | return false 113 | } 114 | } 115 | 116 | PublicKey.fromBinary = function(bin) { 117 | return PublicKey.fromBuffer(new Buffer(bin, 'binary')); 118 | } 119 | 120 | PublicKey.fromBuffer = function(buffer) { 121 | return PublicKey(ecurve.Point.decodeFrom(secp256k1, buffer)); 122 | } 123 | 124 | PublicKey.fromPoint = function(point) { 125 | return PublicKey(point); 126 | } 127 | 128 | /** 129 | @arg {string} public_key - like PUB_K1_base58pubkey.. 130 | @arg {string} [pubkey_prefix = 'EOS'] - public key prefix 131 | @return PublicKey or `null` (invalid) 132 | */ 133 | PublicKey.fromString = function(public_key, pubkey_prefix = 'EOS') { 134 | try { 135 | return PublicKey.fromStringOrThrow(public_key, pubkey_prefix) 136 | } catch (e) { 137 | return null; 138 | } 139 | } 140 | 141 | /** 142 | @arg {string} public_key - like PUB_K1_base58pubkey.. 143 | @arg {string} [pubkey_prefix = 'EOS'] - public key prefix 144 | 145 | @throws {Error} if public key is invalid 146 | 147 | @return PublicKey 148 | */ 149 | PublicKey.fromStringOrThrow = function(public_key, pubkey_prefix = 'EOS') { 150 | assert.equal(typeof public_key, 'string', 'public_key') 151 | const match = public_key.match(/^PUB_([A-Za-z0-9]+)_([A-Za-z0-9]+)$/) 152 | if(match === null) { 153 | // legacy 154 | var prefix_match = new RegExp("^" + pubkey_prefix); 155 | if(prefix_match.test(public_key)) { 156 | public_key = public_key.substring(pubkey_prefix.length) 157 | } 158 | return PublicKey.fromBuffer(keyUtils.checkDecode(public_key)) 159 | } 160 | assert(match.length === 3, 'Expecting public key like: PUB_K1_base58pubkey..') 161 | const [, keyType, keyString] = match 162 | assert.equal(keyType, 'K1', 'K1 private key expected') 163 | return PublicKey.fromBuffer(keyUtils.checkDecode(keyString, keyType)) 164 | } 165 | 166 | PublicKey.fromHex = function(hex) { 167 | return PublicKey.fromBuffer(new Buffer(hex, 'hex')); 168 | } 169 | 170 | PublicKey.fromStringHex = function(hex) { 171 | return PublicKey.fromString(new Buffer(hex, 'hex')); 172 | } 173 | -------------------------------------------------------------------------------- /src/key_utils.js: -------------------------------------------------------------------------------- 1 | const base58 = require('bs58') 2 | const assert = require('assert') 3 | const randomBytes = require('randombytes'); 4 | 5 | const hash = require('./hash'); 6 | 7 | module.exports = { 8 | random32ByteBuffer, 9 | addEntropy, 10 | cpuEntropy, 11 | entropyCount: () => entropyCount, 12 | checkDecode, 13 | checkEncode 14 | } 15 | 16 | let entropyPos = 0, entropyCount = 0 17 | 18 | const externalEntropyArray = randomBytes(101) 19 | 20 | 21 | /** 22 | Additional forms of entropy are used. A week random number generator can run out of entropy. This should ensure even the worst random number implementation will be reasonably safe. 23 | 24 | @arg {number} [cpuEntropyBits = 0] generate entropy on the fly. This is 25 | not required, entropy can be added in advanced via addEntropy or initialize(). 26 | 27 | @arg {boolean} [safe = true] false for testing, otherwise this will be 28 | true to ensure initialize() was called. 29 | 30 | @return a random buffer obtained from the secure random number generator. Additional entropy is used. 31 | */ 32 | function random32ByteBuffer({cpuEntropyBits = 0, safe = true} = {}) { 33 | assert.equal(typeof cpuEntropyBits, 'number', 'cpuEntropyBits') 34 | assert.equal(typeof safe, 'boolean', 'boolean') 35 | 36 | if(safe) { 37 | assert(entropyCount >= 128, 'Call initialize() to add entropy') 38 | } 39 | 40 | // if(entropyCount > 0) { 41 | // console.log(`Additional private key entropy: ${entropyCount} events`) 42 | // } 43 | 44 | const hash_array = [] 45 | hash_array.push(randomBytes(32)) 46 | hash_array.push(Buffer.from(cpuEntropy(cpuEntropyBits))) 47 | hash_array.push(externalEntropyArray) 48 | hash_array.push(browserEntropy()) 49 | return hash.sha256(Buffer.concat(hash_array)) 50 | } 51 | 52 | /** 53 | Adds entropy. This may be called many times while the amount of data saved 54 | is accumulatively reduced to 101 integers. Data is retained in RAM for the 55 | life of this module. 56 | 57 | @example React 58 | componentDidMount() { 59 | this.refs.MyComponent.addEventListener("mousemove", this.onEntropyEvent, {capture: false, passive: true}) 60 | } 61 | componentWillUnmount() { 62 | this.refs.MyComponent.removeEventListener("mousemove", this.onEntropyEvent); 63 | } 64 | onEntropyEvent = (e) => { 65 | if(e.type === 'mousemove') 66 | key_utils.addEntropy(e.pageX, e.pageY, e.screenX, e.screenY) 67 | else 68 | console.log('onEntropyEvent Unknown', e.type, e) 69 | } 70 | 71 | */ 72 | function addEntropy(...ints) { 73 | assert.equal(externalEntropyArray.length, 101, 'externalEntropyArray') 74 | 75 | entropyCount += ints.length 76 | for(const i of ints) { 77 | const pos = entropyPos++ % 101 78 | const i2 = externalEntropyArray[pos] += i 79 | if(i2 > 9007199254740991) 80 | externalEntropyArray[pos] = 0 81 | } 82 | } 83 | 84 | /** 85 | This runs in just under 1 second and ensures a minimum of cpuEntropyBits 86 | bits of entropy are gathered. 87 | 88 | Based on more-entropy. @see https://github.com/keybase/more-entropy/blob/master/src/generator.iced 89 | 90 | @arg {number} [cpuEntropyBits = 128] 91 | @return {array} counts gathered by measuring variations in the CPU speed during floating point operations. 92 | */ 93 | function cpuEntropy(cpuEntropyBits = 128) { 94 | let collected = [] 95 | let lastCount = null 96 | let lowEntropySamples = 0 97 | while(collected.length < cpuEntropyBits) { 98 | const count = floatingPointCount() 99 | if(lastCount != null) { 100 | const delta = count - lastCount 101 | if(Math.abs(delta) < 1) { 102 | lowEntropySamples++ 103 | continue 104 | } 105 | // how many bits of entropy were in this sample 106 | const bits = Math.floor(log2(Math.abs(delta)) + 1) 107 | if(bits < 4) { 108 | if(bits < 2) { 109 | lowEntropySamples++ 110 | } 111 | continue 112 | } 113 | collected.push(delta) 114 | } 115 | lastCount = count 116 | } 117 | if(lowEntropySamples > 10) { 118 | const pct = Number(lowEntropySamples / cpuEntropyBits * 100).toFixed(2) 119 | // Is this algorithm getting inefficient? 120 | console.warn(`WARN: ${pct}% low CPU entropy re-sampled`); 121 | } 122 | return collected 123 | } 124 | 125 | /** 126 | @private 127 | Count while performing floating point operations during a fixed time 128 | (7 ms for example). Using a fixed time makes this algorithm 129 | predictable in runtime. 130 | */ 131 | function floatingPointCount() { 132 | const workMinMs = 7 133 | const d = Date.now() 134 | let i = 0, x = 0 135 | while (Date.now() < d + workMinMs + 1) { 136 | x = Math.sin(Math.sqrt(Math.log(++i + x))) 137 | } 138 | return i 139 | } 140 | 141 | const log2 = x => Math.log(x) / Math.LN2 142 | 143 | /** 144 | @private 145 | Attempt to gather and hash information from the browser's window, history, and supported mime types. For non-browser environments this simply includes secure random data. In any event, the information is re-hashed in a loop for 25 milliseconds seconds. 146 | 147 | @return {Buffer} 32 bytes 148 | */ 149 | function browserEntropy() { 150 | let entropyStr = Array(randomBytes(101)).join() 151 | try { 152 | entropyStr += (new Date()).toString() + " " + window.screen.height + " " + window.screen.width + " " + 153 | window.screen.colorDepth + " " + " " + window.screen.availHeight + " " + window.screen.availWidth + " " + 154 | window.screen.pixelDepth + navigator.language + " " + window.location + " " + window.history.length; 155 | 156 | for (let i = 0, mimeType; i < navigator.mimeTypes.length; i++) { 157 | mimeType = navigator.mimeTypes[i]; 158 | entropyStr += mimeType.description + " " + mimeType.type + " " + mimeType.suffixes + " "; 159 | } 160 | } catch(error) { 161 | //nodejs:ReferenceError: window is not defined 162 | entropyStr += hash.sha256((new Date()).toString()) 163 | } 164 | 165 | const b = new Buffer(entropyStr); 166 | entropyStr += b.toString('binary') + " " + (new Date()).toString(); 167 | 168 | let entropy = entropyStr; 169 | const start_t = Date.now(); 170 | while (Date.now() - start_t < 25) 171 | entropy = hash.sha256(entropy); 172 | 173 | return entropy; 174 | } 175 | 176 | /** 177 | @arg {Buffer} keyBuffer data 178 | @arg {string} keyType = sha256x2, K1, etc 179 | @return {string} checksum encoded base58 string 180 | */ 181 | function checkEncode(keyBuffer, keyType = null) { 182 | assert(Buffer.isBuffer(keyBuffer), 'expecting keyBuffer') 183 | if(keyType === 'sha256x2') { // legacy 184 | const checksum = hash.sha256(hash.sha256(keyBuffer)).slice(0, 4) 185 | return base58.encode(Buffer.concat([keyBuffer, checksum])) 186 | } else { 187 | const check = [keyBuffer] 188 | if(keyType) { 189 | check.push(Buffer.from(keyType)) 190 | } 191 | const checksum = hash.ripemd160(Buffer.concat(check)).slice(0, 4) 192 | return base58.encode(Buffer.concat([keyBuffer, checksum])) 193 | } 194 | } 195 | 196 | /** 197 | @arg {Buffer} keyString data 198 | @arg {string} keyType = sha256x2, K1, etc 199 | @return {string} checksum encoded base58 string 200 | */ 201 | function checkDecode(keyString, keyType = null) { 202 | assert(keyString != null, 'private key expected') 203 | const buffer = new Buffer(base58.decode(keyString)) 204 | const checksum = buffer.slice(-4) 205 | const key = buffer.slice(0, -4) 206 | 207 | let newCheck 208 | if(keyType === 'sha256x2') { // legacy 209 | newCheck = hash.sha256(hash.sha256(key)).slice(0, 4) // WIF (legacy) 210 | } else { 211 | const check = [key] 212 | if(keyType) { 213 | check.push(Buffer.from(keyType)) 214 | } 215 | newCheck = hash.ripemd160(Buffer.concat(check)).slice(0, 4) //PVT 216 | } 217 | 218 | if (checksum.toString('hex') !== newCheck.toString('hex')) { 219 | throw new Error('Invalid checksum, ' + 220 | `${checksum.toString('hex')} != ${newCheck.toString('hex')}` 221 | ) 222 | } 223 | 224 | return key 225 | } 226 | -------------------------------------------------------------------------------- /src/object.test.js: -------------------------------------------------------------------------------- 1 | 2 | /* eslint-env mocha */ 3 | const assert = require('assert') 4 | 5 | const ecc = require('.') 6 | 7 | const {PublicKey, PrivateKey, Signature} = ecc 8 | 9 | describe('Object API', () => { 10 | const pvt = PrivateKey('5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3') 11 | const pub = pvt.toPublic() 12 | 13 | describe('secp256k1 keys', () => { 14 | it('randomKey', function() { 15 | this.timeout(1100) 16 | return PrivateKey.randomKey() 17 | }) 18 | 19 | it('private to public', () => { 20 | assert.equal( 21 | pub.toString(), 22 | // 'PUB_K1_6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5BoDq63', 23 | 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV', 24 | 'pub.toString' 25 | ) 26 | }) 27 | 28 | it('PrivateKey constructors', () => { 29 | assert(pvt.toWif() === PrivateKey(pvt.toWif()).toWif()) 30 | assert(pvt.toWif() === PrivateKey(pvt.toBuffer()).toWif()) 31 | assert(pvt.toWif() === PrivateKey(pvt).toWif()) 32 | 33 | // 01 suffix indicates a compressed public key (normally this is omitted) 34 | const pvtCompressFlag = Buffer.concat([pvt.toBuffer(), Buffer.from('01', 'hex')]) 35 | assert(pvt.toWif() === PrivateKey(pvtCompressFlag).toWif()) 36 | 37 | assert.throws(() => PrivateKey(), /Invalid private key/) 38 | assert.throws(() => PrivateKey.fromHex('ff'), /Expecting 32 bytes/) 39 | assert.throws(() => PrivateKey.fromBuffer('ff'), /Expecting parameter to be a Buffer type/) 40 | assert.doesNotThrow(() => { 41 | PrivateKey('PVT_K1_2jH3nnhxhR3zPUcsKaWWZC9ZmZAnKm3GAnFD1xynGJE1Znuvjd') 42 | }) 43 | }) 44 | 45 | it('Helpers', () => { 46 | assert.equal(PrivateKey.isWif('5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3'), true, 'isWif') 47 | assert.equal(PrivateKey.isWif('PVT_K1_2jH3nnhxhR3zPUcsKaWWZC9ZmZAnKm3GAnFD1xynGJE1Znuvjd'), false, 'isWif') 48 | }) 49 | 50 | it('PublicKey constructors', () => { 51 | assert(pub.toString() === PublicKey(pub.toString()).toString()) 52 | assert(pub.toString() === PublicKey(pub.toBuffer()).toString()) 53 | assert(pub.toString() === PublicKey(pub).toString()) 54 | assert.throws(() => PublicKey(), /Invalid public key/) 55 | }) 56 | }) 57 | 58 | /** @todo secp224r1 */ 59 | // it('PrivateKey secp224r1', () => { 60 | // const pvt = PrivateKey('PVT_K1_iyQmnyPEGvFd8uffnk152WC2WryBjgTrg22fXQryuGL9mU6qW') 61 | // const pub = pvt.toPublic() 62 | // 63 | // assert.equal( 64 | // pub.toString(), 65 | // 'PUB_K1_6EPHFSKVYHBjQgxVGQPrwCxTg7BbZ69H9i4gztN9deKTEXYne4', 66 | // 'toString' 67 | // ) 68 | // }) 69 | 70 | it('Signature', () => { 71 | const sig = Signature.sign('data', pvt) 72 | const sigString = sig.toString() 73 | assert.equal(sig.toString(), sigString, 'cache') 74 | assert.equal(Signature.fromString(sigString).toString(), sigString, 'fromString') 75 | assert(sigString.length > 90, 'signature string is too short') 76 | assert(Signature.from(sigString), 'signature from string') 77 | }) 78 | }) 79 | -------------------------------------------------------------------------------- /src/promise-async.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | Convert a synchronous function into a asynchronous one (via setTimeout) 4 | wrapping it in a promise. This does not expect the function to have a 5 | callback paramter. 6 | 7 | @arg {function} func - non-callback function 8 | 9 | @example promiseAsync(myfunction) 10 | */ 11 | module.exports = func => ( 12 | (...args) => ( 13 | new Promise((resolve, reject) => { 14 | setTimeout(() => { 15 | try { 16 | resolve(func(...args)) 17 | } catch(err) { 18 | reject(err) 19 | } 20 | }) 21 | }) 22 | ) 23 | ) 24 | -------------------------------------------------------------------------------- /src/signature.js: -------------------------------------------------------------------------------- 1 | const ecdsa = require('./ecdsa'); 2 | const hash = require('./hash'); 3 | const curve = require('ecurve').getCurveByName('secp256k1'); 4 | const assert = require('assert'); 5 | const BigInteger = require('bigi'); 6 | const keyUtils = require('./key_utils'); 7 | const PublicKey = require('./key_public'); 8 | const PrivateKey = require('./key_private'); 9 | 10 | module.exports = Signature 11 | 12 | function Signature(r, s, i) { 13 | assert.equal(r != null, true, 'Missing parameter'); 14 | assert.equal(s != null, true, 'Missing parameter'); 15 | assert.equal(i != null, true, 'Missing parameter'); 16 | 17 | /** 18 | Verify signed data. 19 | 20 | @arg {String|Buffer} data - full data 21 | @arg {pubkey|PublicKey} pubkey - EOSKey.. 22 | @arg {String} [encoding = 'utf8'] - data encoding (if data is a string) 23 | 24 | @return {boolean} 25 | */ 26 | function verify(data, pubkey, encoding = 'utf8') { 27 | if(typeof data === 'string') { 28 | data = Buffer.from(data, encoding) 29 | } 30 | assert(Buffer.isBuffer(data), 'data is a required String or Buffer') 31 | data = hash.sha256(data) 32 | return verifyHash(data, pubkey) 33 | } 34 | 35 | /** 36 | Verify a buffer of exactally 32 bytes in size (sha256(text)) 37 | 38 | @arg {String|Buffer} dataSha256 - 32 byte buffer or string 39 | @arg {String|PublicKey} pubkey - EOSKey.. 40 | @arg {String} [encoding = 'hex'] - dataSha256 encoding (if string) 41 | 42 | @return {boolean} 43 | */ 44 | function verifyHash(dataSha256, pubkey, encoding = 'hex') { 45 | if(typeof dataSha256 === 'string') { 46 | dataSha256 = Buffer.from(dataSha256, encoding) 47 | } 48 | if(dataSha256.length !== 32 || !Buffer.isBuffer(dataSha256)) 49 | throw new Error("dataSha256: 32 bytes required") 50 | 51 | const publicKey = PublicKey(pubkey) 52 | assert(publicKey, 'pubkey required') 53 | 54 | return ecdsa.verify( 55 | curve, dataSha256, 56 | { r: r, s: s }, 57 | publicKey.Q 58 | ); 59 | }; 60 | 61 | /** @deprecated 62 | 63 | Verify hex data by converting to a buffer then hashing. 64 | 65 | @return {boolean} 66 | */ 67 | function verifyHex(hex, pubkey) { 68 | console.log('Deprecated: use verify(data, pubkey, "hex")'); 69 | 70 | const buf = Buffer.from(hex, 'hex'); 71 | return verify(buf, pubkey); 72 | }; 73 | 74 | /** 75 | Recover the public key used to create this signature using full data. 76 | 77 | @arg {String|Buffer} data - full data 78 | @arg {String} [encoding = 'utf8'] - data encoding (if string) 79 | 80 | @return {PublicKey} 81 | */ 82 | function recover(data, encoding = 'utf8') { 83 | if(typeof data === 'string') { 84 | data = Buffer.from(data, encoding) 85 | } 86 | assert(Buffer.isBuffer(data), 'data is a required String or Buffer') 87 | data = hash.sha256(data) 88 | 89 | return recoverHash(data) 90 | }; 91 | 92 | /** 93 | @arg {String|Buffer} dataSha256 - sha256 hash 32 byte buffer or hex string 94 | @arg {String} [encoding = 'hex'] - dataSha256 encoding (if string) 95 | 96 | @return {PublicKey} 97 | */ 98 | function recoverHash(dataSha256, encoding = 'hex') { 99 | if(typeof dataSha256 === 'string') { 100 | dataSha256 = Buffer.from(dataSha256, encoding) 101 | } 102 | if(dataSha256.length !== 32 || !Buffer.isBuffer(dataSha256)) { 103 | throw new Error("dataSha256: 32 byte String or buffer requred") 104 | } 105 | 106 | const e = BigInteger.fromBuffer(dataSha256); 107 | let i2 = i 108 | i2 -= 27; 109 | i2 = i2 & 3; 110 | const Q = ecdsa.recoverPubKey(curve, e, {r, s, i}, i2); 111 | return PublicKey.fromPoint(Q); 112 | }; 113 | 114 | function toBuffer() { 115 | var buf; 116 | buf = new Buffer(65); 117 | buf.writeUInt8(i, 0); 118 | r.toBuffer(32).copy(buf, 1); 119 | s.toBuffer(32).copy(buf, 33); 120 | return buf; 121 | }; 122 | 123 | function toHex() { 124 | return toBuffer().toString("hex"); 125 | }; 126 | 127 | let signatureCache 128 | 129 | function toString() { 130 | if(signatureCache) { 131 | return signatureCache 132 | } 133 | signatureCache = 'SIG_K1_' + keyUtils.checkEncode(toBuffer(), 'K1') 134 | return signatureCache 135 | } 136 | 137 | return { 138 | r, s, i, 139 | toBuffer, 140 | verify, 141 | verifyHash, 142 | verifyHex,// deprecated 143 | recover, 144 | recoverHash, 145 | toHex, 146 | toString, 147 | 148 | /** @deprecated use verify (same arguments and return) */ 149 | verifyBuffer: (...args) => { 150 | console.log('Deprecated: use signature.verify instead (same arguments)'); 151 | return verify(...args) 152 | }, 153 | 154 | /** @deprecated use recover (same arguments and return) */ 155 | recoverPublicKey: (...args) => { 156 | console.log('Deprecated: use signature.recover instead (same arguments)'); 157 | return recover(...args) 158 | }, 159 | 160 | /** @deprecated use recoverHash (same arguments and return) */ 161 | recoverPublicKeyFromBuffer: (...args) => { 162 | console.log('Deprecated: use signature.recoverHash instead (same arguments)'); 163 | return recoverHash(...args) 164 | } 165 | } 166 | } 167 | 168 | /** 169 | Hash and sign arbitrary data. 170 | 171 | @arg {string|Buffer} data - full data 172 | @arg {wif|PrivateKey} privateKey 173 | @arg {String} [encoding = 'utf8'] - data encoding (if string) 174 | 175 | @return {Signature} 176 | */ 177 | Signature.sign = function(data, privateKey, encoding = 'utf8') { 178 | if(typeof data === 'string') { 179 | data = Buffer.from(data, encoding) 180 | } 181 | assert(Buffer.isBuffer(data), 'data is a required String or Buffer') 182 | data = hash.sha256(data) 183 | return Signature.signHash(data, privateKey) 184 | } 185 | 186 | /** 187 | Sign a buffer of exactally 32 bytes in size (sha256(text)) 188 | 189 | @arg {string|Buffer} dataSha256 - 32 byte buffer or string 190 | @arg {wif|PrivateKey} privateKey 191 | @arg {String} [encoding = 'hex'] - dataSha256 encoding (if string) 192 | 193 | @return {Signature} 194 | */ 195 | Signature.signHash = function(dataSha256, privateKey, encoding = 'hex') { 196 | if(typeof dataSha256 === 'string') { 197 | dataSha256 = Buffer.from(dataSha256, encoding) 198 | } 199 | if( dataSha256.length !== 32 || ! Buffer.isBuffer(dataSha256) ) 200 | throw new Error("dataSha256: 32 byte buffer requred") 201 | 202 | privateKey = PrivateKey(privateKey) 203 | assert(privateKey, 'privateKey required') 204 | 205 | var der, e, ecsignature, i, lenR, lenS, nonce; 206 | i = null; 207 | nonce = 0; 208 | e = BigInteger.fromBuffer(dataSha256); 209 | while (true) { 210 | ecsignature = ecdsa.sign(curve, dataSha256, privateKey.d, nonce++); 211 | der = ecsignature.toDER(); 212 | lenR = der[3]; 213 | lenS = der[5 + lenR]; 214 | if (lenR === 32 && lenS === 32) { 215 | i = ecdsa.calcPubKeyRecoveryParam(curve, e, ecsignature, privateKey.toPublic().Q); 216 | i += 4; // compressed 217 | i += 27; // compact // 24 or 27 :( forcing odd-y 2nd key candidate) 218 | break; 219 | } 220 | if (nonce % 10 === 0) { 221 | console.log("WARN: " + nonce + " attempts to find canonical signature"); 222 | } 223 | } 224 | return Signature(ecsignature.r, ecsignature.s, i); 225 | }; 226 | 227 | Signature.fromBuffer = function(buf) { 228 | var i, r, s; 229 | assert(Buffer.isBuffer(buf), 'Buffer is required') 230 | assert.equal(buf.length, 65, 'Invalid signature length'); 231 | i = buf.readUInt8(0); 232 | assert.equal(i - 27, i - 27 & 7, 'Invalid signature parameter'); 233 | r = BigInteger.fromBuffer(buf.slice(1, 33)); 234 | s = BigInteger.fromBuffer(buf.slice(33)); 235 | return Signature(r, s, i); 236 | }; 237 | 238 | Signature.fromHex = function(hex) { 239 | return Signature.fromBuffer(Buffer.from(hex, "hex")); 240 | }; 241 | 242 | /** 243 | @arg {string} signature - like SIG_K1_base58signature.. 244 | @return {Signature} or `null` (invalid) 245 | */ 246 | Signature.fromString = function(signature) { 247 | try { 248 | return Signature.fromStringOrThrow(signature) 249 | } catch (e) { 250 | return null; 251 | } 252 | } 253 | 254 | /** 255 | @arg {string} signature - like SIG_K1_base58signature.. 256 | @throws {Error} invalid 257 | @return {Signature} 258 | */ 259 | Signature.fromStringOrThrow = function(signature) { 260 | assert.equal(typeof signature, 'string', 'signature') 261 | const match = signature.match(/^SIG_([A-Za-z0-9]+)_([A-Za-z0-9]+)$/) 262 | assert(match != null && match.length === 3, 'Expecting signature like: SIG_K1_base58signature..') 263 | const [, keyType, keyString] = match 264 | assert.equal(keyType, 'K1', 'K1 signature expected') 265 | return Signature.fromBuffer(keyUtils.checkDecode(keyString, keyType)) 266 | } 267 | 268 | /** 269 | @arg {String|Signature} o - hex string 270 | @return {Signature} 271 | */ 272 | Signature.from = (o) => { 273 | const signature = o ? 274 | (o.r && o.s && o.i) ? o : 275 | typeof o === 'string' && o.length === 130 ? Signature.fromHex(o) : 276 | typeof o === 'string' && o.length !== 130 ? Signature.fromStringOrThrow(o) : 277 | Buffer.isBuffer(o) ? Signature.fromBuffer(o) : 278 | null : o/*null or undefined*/ 279 | 280 | if(!signature) { 281 | throw new TypeError('signature should be a hex string or buffer') 282 | } 283 | return signature 284 | } 285 | --------------------------------------------------------------------------------