├── .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 | [](https://www.npmjs.org/package/eosjs-ecc)
2 | [](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 |
--------------------------------------------------------------------------------