├── .babelrc
├── .eslintignore
├── .eslintrc.json
├── .gitattributes
├── .github
├── FUNDING.yml
└── workflows
│ └── ci.yml
├── .gitignore
├── .npmignore
├── .npmrc
├── .nvmrc
├── .solhint.json
├── ISSUE_TEMPLATE.md
├── LICENSE
├── README
├── README.md
├── config
├── karma.conf.js
└── webpack.config.js
├── contracts
├── .gitignore
├── DonationBag.sol
└── TestContract.sol
├── dist
├── es
│ ├── browserify.index.js
│ ├── calculate-contract-address.js
│ ├── cipher.js
│ ├── create-identity.js
│ ├── decrypt-with-private-key.js
│ ├── encrypt-with-public-key.js
│ ├── hash.js
│ ├── hex.js
│ ├── index.js
│ ├── public-key-by-private-key.js
│ ├── public-key.js
│ ├── recover-public-key.js
│ ├── recover.js
│ ├── sign-transaction.js
│ ├── sign.js
│ ├── tx-data-by-compiled.js
│ ├── util.js
│ └── vrs.js
└── lib
│ ├── browserify.index.js
│ ├── calculate-contract-address.js
│ ├── cipher.js
│ ├── create-identity.js
│ ├── decrypt-with-private-key.js
│ ├── encrypt-with-public-key.js
│ ├── hash.js
│ ├── hex.js
│ ├── index.js
│ ├── public-key-by-private-key.js
│ ├── public-key.js
│ ├── recover-public-key.js
│ ├── recover.js
│ ├── sign-transaction.js
│ ├── sign.js
│ ├── tx-data-by-compiled.js
│ ├── util.js
│ └── vrs.js
├── package.json
├── renovate.json
├── src
├── browserify.index.js
├── calculate-contract-address.js
├── cipher.js
├── create-identity.js
├── decrypt-with-private-key.js
├── encrypt-with-public-key.js
├── hash.js
├── hex.js
├── index.js
├── public-key-by-private-key.js
├── public-key.js
├── recover-public-key.js
├── recover.js
├── sign-transaction.js
├── sign.js
├── tx-data-by-compiled.js
├── util.js
└── vrs.js
├── test
├── .eslintrc
├── bug-template.test.js
├── index.test.js
├── integration.test.js
├── issues.test.js
├── karma.test.js
├── performance.test.js
├── tutorials
│ ├── encrypted-message.test.js
│ └── signed-data.test.js
├── typings.test.js
└── unit.test.js
├── tutorials
├── creating-transactions.md
├── encrypted-message.md
└── signed-data.md
└── typings
└── index.d.ts
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-env"],
3 | "plugins": ["@babel/plugin-transform-runtime"],
4 | "env": {
5 | "es6": {
6 | "presets": [
7 | [
8 | "@babel/preset-env",
9 | {
10 | "modules": false
11 | }
12 | ]
13 | ]
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist/
2 | gen/
3 | test_tmp/
4 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "node": true
6 | },
7 | "extends": "eslint:recommended",
8 | "parserOptions": {
9 | "ecmaVersion": 2018,
10 | "sourceType": "module"
11 | },
12 | "rules": {
13 | "accessor-pairs": "error",
14 | "array-bracket-newline": "off",
15 | "array-bracket-spacing": [
16 | "error",
17 | "never"
18 | ],
19 | "array-callback-return": "off",
20 | "array-element-newline": "off",
21 | "arrow-body-style": "off",
22 | "arrow-parens": "off",
23 | "arrow-spacing": [
24 | "error",
25 | {
26 | "after": true,
27 | "before": true
28 | }
29 | ],
30 | "block-scoped-var": "error",
31 | "block-spacing": "error",
32 | "brace-style": [
33 | "error",
34 | "1tbs"
35 | ],
36 | "callback-return": "error",
37 | "capitalized-comments": "off",
38 | "class-methods-use-this": "off",
39 | "comma-dangle": "off",
40 | "comma-spacing": [
41 | "error",
42 | {
43 | "after": true,
44 | "before": false
45 | }
46 | ],
47 | "comma-style": [
48 | "error",
49 | "last"
50 | ],
51 | "complexity": "off",
52 | "computed-property-spacing": [
53 | "error",
54 | "never"
55 | ],
56 | "consistent-return": "off",
57 | "consistent-this": "off",
58 | "curly": "off",
59 | "default-case": "off",
60 | "dot-location": [
61 | "error",
62 | "property"
63 | ],
64 | "dot-notation": "off",
65 | "eol-last": "off",
66 | "eqeqeq": "off",
67 | "func-call-spacing": "error",
68 | "func-name-matching": "off",
69 | "func-names": "off",
70 | "func-style": "off",
71 | "function-paren-newline": "off",
72 | "generator-star-spacing": "error",
73 | "global-require": "off",
74 | "guard-for-in": "off",
75 | "handle-callback-err": "error",
76 | "id-blacklist": "error",
77 | "id-length": "off",
78 | "id-match": "error",
79 | "implicit-arrow-linebreak": "off",
80 | "indent": [
81 | "error",
82 | 4,
83 | {
84 | "MemberExpression": "off",
85 | "SwitchCase": 1
86 | }
87 | ],
88 | "indent-legacy": "off",
89 | "init-declarations": "off",
90 | "jsx-quotes": "error",
91 | "key-spacing": "error",
92 | "keyword-spacing": "off",
93 | "line-comment-position": "off",
94 | "linebreak-style": [
95 | "error",
96 | "unix"
97 | ],
98 | "lines-around-comment": "off",
99 | "lines-around-directive": "error",
100 | "lines-between-class-members": "off",
101 | "max-classes-per-file": "off",
102 | "max-depth": "error",
103 | "max-len": "off",
104 | "max-lines": "off",
105 | "max-lines-per-function": "off",
106 | "max-nested-callbacks": "error",
107 | "max-params": "off",
108 | "max-statements": "off",
109 | "max-statements-per-line": "error",
110 | "multiline-comment-style": "off",
111 | "multiline-ternary": [
112 | "error",
113 | "always-multiline"
114 | ],
115 | "new-parens": "off",
116 | "newline-after-var": "off",
117 | "newline-before-return": "off",
118 | "newline-per-chained-call": "off",
119 | "no-alert": "error",
120 | "no-array-constructor": "error",
121 | "no-await-in-loop": "off",
122 | "no-bitwise": "off",
123 | "no-buffer-constructor": "off",
124 | "no-caller": "error",
125 | "no-catch-shadow": "error",
126 | "no-case-declarations": "off",
127 | "no-confusing-arrow": "off",
128 | "no-constant-condition": [
129 | "error",
130 | {
131 | "checkLoops": false
132 | }
133 | ],
134 | "no-console": "off",
135 | "no-continue": "off",
136 | "no-div-regex": "error",
137 | "no-duplicate-imports": "off",
138 | "no-else-return": "off",
139 | "no-empty": [
140 | "error",
141 | {
142 | "allowEmptyCatch": true
143 | }
144 | ],
145 | "no-empty-function": "off",
146 | "no-eq-null": "error",
147 | "no-eval": "error",
148 | "no-extend-native": "error",
149 | "no-extra-bind": "error",
150 | "no-extra-label": "error",
151 | "no-extra-parens": "off",
152 | "no-floating-decimal": "error",
153 | "no-implicit-globals": "error",
154 | "no-implied-eval": "error",
155 | "no-inline-comments": "off",
156 | "no-invalid-this": "off",
157 | "no-iterator": "error",
158 | "no-label-var": "error",
159 | "no-labels": "error",
160 | "no-lone-blocks": "error",
161 | "no-lonely-if": "off",
162 | "no-loop-func": "off",
163 | "no-magic-numbers": "off",
164 | "no-mixed-operators": "off",
165 | "no-mixed-requires": "error",
166 | "no-multi-assign": "error",
167 | "no-multi-spaces": "error",
168 | "no-multi-str": "error",
169 | "no-multiple-empty-lines": "off",
170 | "no-native-reassign": "error",
171 | "no-negated-condition": "off",
172 | "no-negated-in-lhs": "error",
173 | "no-nested-ternary": "error",
174 | "no-new": "error",
175 | "no-new-func": "error",
176 | "no-new-object": "error",
177 | "no-new-require": "error",
178 | "no-new-wrappers": "error",
179 | "no-octal-escape": "error",
180 | "no-param-reassign": "off",
181 | "no-path-concat": "error",
182 | "no-plusplus": "off",
183 | "no-process-env": "off",
184 | "no-process-exit": "off",
185 | "no-proto": "off",
186 | "no-prototype-builtins": "off",
187 | "no-restricted-globals": "error",
188 | "no-restricted-imports": "error",
189 | "no-restricted-modules": "error",
190 | "no-restricted-properties": "error",
191 | "no-restricted-syntax": "error",
192 | "no-return-assign": "off",
193 | "no-return-await": "off",
194 | "no-script-url": "error",
195 | "no-self-compare": "error",
196 | "no-sequences": "error",
197 | "no-shadow": "off",
198 | "no-shadow-restricted-names": "error",
199 | "no-spaced-func": "error",
200 | "no-sync": "off",
201 | "no-tabs": "error",
202 | "no-template-curly-in-string": "error",
203 | "no-ternary": "off",
204 | "no-throw-literal": "error",
205 | "no-trailing-spaces": [
206 | "error",
207 | {
208 | "ignoreComments": true,
209 | "skipBlankLines": true
210 | }
211 | ],
212 | "no-undef-init": "error",
213 | "no-undefined": "off",
214 | "no-underscore-dangle": "off",
215 | "no-unmodified-loop-condition": "error",
216 | "no-unneeded-ternary": "off",
217 | "no-unused-expressions": "off",
218 | "no-use-before-define": "off",
219 | "no-useless-call": "error",
220 | "no-useless-computed-key": "error",
221 | "no-useless-concat": "off",
222 | "no-useless-constructor": "error",
223 | "no-useless-return": "off",
224 | "no-var": "error",
225 | "no-void": "error",
226 | "no-warning-comments": "off",
227 | "no-whitespace-before-property": "error",
228 | "no-with": "error",
229 | "nonblock-statement-body-position": [
230 | "error",
231 | "any"
232 | ],
233 | "object-curly-newline": "off",
234 | "object-curly-spacing": [
235 | "error",
236 | "always"
237 | ],
238 | "object-property-newline": "error",
239 | "object-shorthand": "off",
240 | "one-var": "off",
241 | "one-var-declaration-per-line": [
242 | "error",
243 | "initializations"
244 | ],
245 | "operator-assignment": "off",
246 | "operator-linebreak": [
247 | "error",
248 | "after"
249 | ],
250 | "padded-blocks": "off",
251 | "padding-line-between-statements": "error",
252 | "prefer-arrow-callback": "off",
253 | "prefer-const": "error",
254 | "prefer-destructuring": "off",
255 | "prefer-numeric-literals": "error",
256 | "prefer-object-spread": "off",
257 | "prefer-promise-reject-errors": "error",
258 | "prefer-reflect": "off",
259 | "prefer-rest-params": "off",
260 | "prefer-spread": "off",
261 | "prefer-template": "off",
262 | "quote-props": "off",
263 | "quotes": [
264 | "error",
265 | "single"
266 | ],
267 | "radix": "off",
268 | "require-atomic-updates": "off",
269 | "require-await": "off",
270 | "require-jsdoc": "off",
271 | "rest-spread-spacing": [
272 | "error",
273 | "never"
274 | ],
275 | "semi": "error",
276 | "semi-spacing": [
277 | "error",
278 | {
279 | "after": true,
280 | "before": false
281 | }
282 | ],
283 | "semi-style": [
284 | "error",
285 | "last"
286 | ],
287 | "sort-imports": "off",
288 | "sort-keys": "off",
289 | "sort-vars": "off",
290 | "space-before-blocks": "error",
291 | "space-before-function-paren": "off",
292 | "space-in-parens": [
293 | "error",
294 | "never"
295 | ],
296 | "space-infix-ops": "error",
297 | "space-unary-ops": "error",
298 | "spaced-comment": "off",
299 | "strict": "error",
300 | "switch-colon-spacing": "error",
301 | "symbol-description": "error",
302 | "template-curly-spacing": [
303 | "error",
304 | "never"
305 | ],
306 | "template-tag-spacing": "error",
307 | "unicode-bom": [
308 | "error",
309 | "never"
310 | ],
311 | "valid-jsdoc": "off",
312 | "vars-on-top": "error",
313 | "wrap-iife": "error",
314 | "wrap-regex": "off",
315 | "yield-star-spacing": "error",
316 | "yoda": "off"
317 | }
318 | }
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sol linguist-language=Solidity
2 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: pubkey
4 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | # Controls when the action will run.
3 | on:
4 | # Triggers the workflow on push or pull request events but only for the master branch
5 | push:
6 | branches: [ master ]
7 | pull_request:
8 | branches: [ master ]
9 |
10 | # Allows you to run this workflow manually from the Actions tab
11 | workflow_dispatch:
12 |
13 | jobs:
14 | build:
15 | runs-on: ubuntu-latest
16 | steps:
17 | - uses: actions/checkout@v4
18 | - name: Use Node.js 12.11.0
19 | uses: actions/setup-node@v4
20 | with:
21 | node-version-file: '.nvmrc'
22 | - run: npm install
23 | - run: npm run lint
24 | - run: npm run build
25 | - run: npm run test:node
26 | - name: Run headless test
27 | uses: GabrielBB/xvfb-action@v1
28 | with:
29 | run: npm run test:browser
30 | - run: npm run test:typings
31 | - run: npm run test:deps
32 | - run: npm run test:size
33 | - run: npm run build:size
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | npm-debug.log
3 | core
4 | log.txt
5 | package-lock.json
6 | gen/
7 | test_tmp/
8 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .git/
2 | .babelrc
3 | .eslintignore
4 | .eslintrc.json
5 | .solhint.json
6 | .travis.yml
7 | package-lock.json
8 | gen/
9 | scripts/
10 | test_tmp/
11 | perf.txt
12 | ISSUE_TEMPLATE.md
13 | config/
14 | test/
15 | .gitattributes
16 | renovate.json
17 | tutorials/
18 | contracts/
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | unsafe-perm = true
2 | package-lock=false
3 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 22.16.0
2 |
--------------------------------------------------------------------------------
/.solhint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "solhint:default",
3 | "rules": {
4 | "func-order": "off",
5 | "not-rely-on-time": "off"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Daniel M
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
13 |
14 |
15 | # eth-crypto
16 |
17 | Cryptographic javascript-functions for ethereum and tutorials on how to use them together with web3js and solidity.
18 |
19 | ## Tutorials
20 |
21 | - **[Creating Keys and use them for ethereum-transactions](./tutorials/creating-transactions.md)**
22 |
23 | In this tutorial we will create an ethereum-identity and use it so send transactions to the blockchain.
24 |
25 | - **[Sign and validate data with solidity](./tutorials/signed-data.md)**
26 |
27 | In this tutorial we will sign data in javascript and validate the signature inside of a smart-contract.
28 |
29 | - **[Sending encrypted and signed data to other identites](./tutorials/encrypted-message.md)**
30 |
31 | In this tutorial we will use the ethereum-identites and asymmetric cryptography to send an encrypted and signed message from Alice to Bob.
32 |
33 | ## Functions
34 |
35 | ### Install
36 |
37 | ```bash
38 | npm install eth-crypto --save
39 | ```
40 |
41 | ```javascript
42 | // es6
43 | import EthCrypto from 'eth-crypto';
44 |
45 | // node
46 | const EthCrypto = require('eth-crypto');
47 | ```
48 |
49 | ## API
50 |
51 | - [createIdentity()](https://github.com/pubkey/eth-crypto#createidentity)
52 | - [publicKeyByPrivateKey()](https://github.com/pubkey/eth-crypto#publickeybyprivatekey)
53 | - [publicKey.toAddress()](https://github.com/pubkey/eth-crypto#publickeytoaddress)
54 | - [publicKey.compress()](https://github.com/pubkey/eth-crypto#publickeycompress)
55 | - [publicKey.decompress()](https://github.com/pubkey/eth-crypto#publickeydecompress)
56 | - [sign()](https://github.com/pubkey/eth-crypto#sign)
57 | - [recover()](https://github.com/pubkey/eth-crypto#recover)
58 | - [recoverPublicKey()](https://github.com/pubkey/eth-crypto#recoverpublickey)
59 | - [encryptWithPublicKey()](https://github.com/pubkey/eth-crypto#encryptwithpublickey)
60 | - [decryptWithPrivateKey()](https://github.com/pubkey/eth-crypto#decryptwithprivatekey)
61 | - [cipher.stringify()](https://github.com/pubkey/eth-crypto#cipherstringify)
62 | - [cipher.parse()](https://github.com/pubkey/eth-crypto#cipherparse)
63 | - [signTransaction()](https://github.com/pubkey/eth-crypto#signtransaction)
64 | - [txDataByCompiled()](https://github.com/pubkey/eth-crypto#txdatabycompiled)
65 | - [calculateContractAddress()](https://github.com/pubkey/eth-crypto#calculatecontractaddress)
66 | - [hex.compress() hex.decompress()](https://github.com/pubkey/eth-crypto#hex-compressdecompress)
67 |
68 |
69 | # [READ THE FULL DOCUMENTATION ON GITHUB](https://github.com/pubkey/eth-crypto)
70 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 | # eth-crypto
9 |
10 | Cryptographic javascript-functions for ethereum and tutorials on how to use them together with web3js and solidity.
11 |
12 | ## Tutorials
13 |
14 | - **[Creating keys and use them for ethereum transactions](./tutorials/creating-transactions.md)**
15 |
16 | In this tutorial we will create an ethereum-identity and use it to send transactions to the blockchain.
17 |
18 | - **[Sign and validate data with solidity](./tutorials/signed-data.md)**
19 |
20 | In this tutorial we will sign data in javascript and validate the signature inside of a smart-contract.
21 |
22 | - **[Sending encrypted and signed data to other identities](./tutorials/encrypted-message.md)**
23 |
24 | In this tutorial we will use the ethereum-identities and asymmetric cryptography to send an encrypted and signed message from Alice to Bob.
25 |
26 | ## Sponsored by
27 |
28 |
29 |
30 |
35 |
36 |
37 | The JavaScript Database
38 |
39 |
40 |
41 | ## Using eth-crypto
42 |
43 | ### Install
44 |
45 | ```bash
46 | npm install eth-crypto --save
47 | ```
48 |
49 | ```javascript
50 | // es6
51 | import EthCrypto from 'eth-crypto';
52 |
53 | // node
54 | const EthCrypto = require('eth-crypto');
55 | ```
56 |
57 | ## API
58 |
59 | - [createIdentity()](#createidentity)
60 | - [publicKeyByPrivateKey()](#publickeybyprivatekey)
61 | - [publicKey.toAddress()](#publickeytoaddress)
62 | - [publicKey.compress()](#publickeycompress)
63 | - [publicKey.decompress()](#publickeydecompress)
64 | - [sign()](#sign)
65 | - [recover()](#recover)
66 | - [recoverPublicKey()](#recoverpublickey)
67 | - [encryptWithPublicKey()](#encryptwithpublickey)
68 | - [decryptWithPrivateKey()](#decryptwithprivatekey)
69 | - [cipher.stringify()](#cipherstringify)
70 | - [cipher.parse()](#cipherparse)
71 | - [signTransaction()](#signtransaction)
72 | - [txDataByCompiled()](#txdatabycompiled)
73 | - [calculateContractAddress()](#calculatecontractaddress)
74 | - [hex.compress() hex.decompress()](#hex-compressdecompress)
75 | ### createIdentity()
76 |
77 | Creates a new ethereum-identity with privateKey, publicKey and address as hex-string.
78 |
79 | ```javascript
80 | const identity = EthCrypto.createIdentity();
81 | /* > {
82 | address: '0x3f243FdacE01Cfd9719f7359c94BA11361f32471',
83 | privateKey: '0x107be946709e41b7895eea9f2dacf998a0a9124acbb786f0fd1a826101581a07',
84 | publicKey: 'bf1cc3154424dc22191941d9f4f50b063a2b663a2337e5548abea633c1d06ece...'
85 | } */
86 | ```
87 |
88 | You can also create an identity by providing your own entropy-buffer. Use this with caution, a bad entropy can result in an unsecure private key.
89 |
90 | ```javascript
91 | const entropy = Buffer.from('f2dacf...', 'utf-8'); // must contain at least 128 chars
92 | const identity = EthCrypto.createIdentity(entropy);
93 | /* > {
94 | address: '0x59c8d4d645B0a3b230DE368d815ebDE372d37Ea8',
95 | privateKey: '0x18cea40e44624867ddfd775b2898cdb2da29b4be92ee072b9eb02d43b6f2473a',
96 | publicKey: '991ce4643653ef452327ee3d1a56af19c84599d340ffd427e784...'
97 | } */
98 | ```
99 |
100 | ### publicKeyByPrivateKey()
101 |
102 | Derives the publicKey from a privateKey and returns it as hex-string.
103 |
104 | ```javascript
105 | const publicKey = EthCrypto.publicKeyByPrivateKey(
106 | '0x107be946709e41b7895eea9f2dacf998a0a9124acbb786f0fd1a826101581a07'
107 | );
108 | // > 'bf1cc3154424dc22191941d9f4f50b063a2b663a2337e5548abea633c1d06ece...'
109 | ```
110 |
111 | ### publicKey.toAddress()
112 |
113 | Derives the ethereum-address from the publicKey.
114 |
115 | ```javascript
116 | const address = EthCrypto.publicKey.toAddress(
117 | 'bf1cc3154424dc22191941d9f4f50b063a2b663a2337e5548abea633c1d06ece...'
118 | );
119 | // > '0x3f243FdacE01Cfd9719f7359c94BA11361f32471'
120 | ```
121 |
122 | ### publicKey.compress()
123 |
124 | Compresses an uncompressed publicKey.
125 |
126 | ```javascript
127 | const address = EthCrypto.publicKey.compress(
128 | '04a34d6aef3eb42335fb3cacb59...'
129 | );
130 | // > '03a34d6aef3eb42335fb3cacb59478c0b44c0bbeb8bb4ca427dbc7044157a5d24b' // compressed keys start with '02' or '03'
131 | ```
132 |
133 | ### publicKey.decompress()
134 |
135 | Decompresses a compressed publicKey.
136 |
137 | ```javascript
138 | const address = EthCrypto.publicKey.decompress(
139 | '03a34d6aef3eb42335fb3c...'
140 | );
141 | // > 'a34d6aef3eb42335fb3cacb5947' // non-compressed keys start with '04' or no prefix
142 | ```
143 |
144 | ### sign()
145 |
146 | Signs the hash with the privateKey. Returns the signature as hex-string.
147 |
148 | ```javascript
149 | const message = 'foobar';
150 | const messageHash = EthCrypto.hash.keccak256(message);
151 | const signature = EthCrypto.sign(
152 | '0x107be946709e41b7895eea9f2dacf998a0a9124acbb786f0fd1a826101581a07', // privateKey
153 | messageHash // hash of message
154 | );
155 | // > '0xc04b809d8f33c46ff80c44ba58e866ff0d5..'
156 | ```
157 |
158 | ### recover()
159 |
160 | Recovers the signers address from the signature.
161 |
162 | ```javascript
163 | const signer = EthCrypto.recover(
164 | '0xc04b809d8f33c46ff80c44ba58e866ff0d5..',
165 | EthCrypto.hash.keccak256('foobar') // signed message hash
166 | );
167 | // > '0x3f243FdacE01Cfd9719f7359c94BA11361f32471'
168 | ```
169 |
170 | ### recoverPublicKey()
171 |
172 | Recovers the signers `publicKey` from the signature.
173 | ```javascript
174 | const signer = EthCrypto.recoverPublicKey(
175 | '0xc04b809d8f33c46ff80c44ba58e866ff0d5..', // signature
176 | EthCrypto.hash.keccak256('foobar') // message hash
177 | );
178 | // > 'bf1cc3154424dc22191941d9f4f50b063a2b663a2337e5548abea633c1d06ece..'
179 | ```
180 |
181 |
182 | ### encryptWithPublicKey()
183 |
184 | Encrypts the message with the publicKey so that only the corresponding privateKey can decrypt it. Returns (async) the encrypted data as object with hex-strings.
185 |
186 | ```javascript
187 | const encrypted = await EthCrypto.encryptWithPublicKey(
188 | 'bf1cc3154424dc22191941d9f4f50b063a2b663a2337e5548abea633c1d06ece...', // publicKey
189 | 'foobar' // message
190 | );
191 | /* > {
192 | iv: '02aeac54cb45283b427bd1a5028552c1',
193 | ephemPublicKey: '044acf39ed83c304f19f41ea66615d7a6c0068d5fc48ee181f2fb1091...',
194 | ciphertext: '5fbbcc1a44ee19f7499dbc39cfc4ce96',
195 | mac: '96490b293763f49a371d3a2040a2d2cb57f246ee88958009fe3c7ef2a38264a1'
196 | } */
197 | ```
198 |
199 | ### decryptWithPrivateKey()
200 |
201 | Decrypts the encrypted data with the privateKey. Returns (async) the message as string.
202 |
203 | ```javascript
204 | const message = await EthCrypto.decryptWithPrivateKey(
205 | '0x107be946709e41b7895eea9f2dacf998a0a9124acbb786f0fd1a826101581a07', // privateKey
206 | {
207 | iv: '02aeac54cb45283b427bd1a5028552c1',
208 | ephemPublicKey: '044acf39ed83c304f19f41ea66615d7a6c0068d5fc48ee181f2fb1091...',
209 | ciphertext: '5fbbcc1a44ee19f7499dbc39cfc4ce96',
210 | mac: '96490b293763f49a371d3a2040a2d2cb57f246ee88958009fe3c7ef2a38264a1'
211 | } // encrypted-data
212 | );
213 | // 'foobar'
214 | ```
215 |
216 | ### cipher.stringify()
217 |
218 | Transforms the object with the encrypted data into a smaller string-representation.
219 |
220 | ```javascript
221 | const str = EthCrypto.cipher.stringify({
222 | iv: '02aeac54cb45283b427bd1a5028552c1',
223 | ephemPublicKey: '044acf39ed83c304f19f41ea66615d7a6c0068d5fc48ee181f2fb1091...',
224 | ciphertext: '5fbbcc1a44ee19f7499dbc39cfc4ce96',
225 | mac: '96490b293763f49a371d3a2040a2d2cb57f246ee88958009fe3c7ef2a38264a1'
226 | });
227 | // > '59ab06532fc965b0107977f43e69e5a4038db32099dab281c8f5aece2852...'
228 | ```
229 |
230 | ### cipher.parse()
231 |
232 | Parses the string-representation back into the encrypted object.
233 |
234 | ```javascript
235 | const str = EthCrypto.cipher.parse('59ab06532fc965b0107977f43e69e5a4038db32099dab281c8f5aece2852...');
236 | /* > {
237 | iv: '02aeac54cb45283b427bd1a5028552c1',
238 | ephemPublicKey: '044acf39ed83c304f19f41ea66615d7a6c0068d5fc48ee181f2fb1091...',
239 | ciphertext: '5fbbcc1a44ee19f7499dbc39cfc4ce96',
240 | mac: '96490b293763f49a371d3a2040a2d2cb57f246ee88958009fe3c7ef2a38264a1'
241 | } */
242 | ```
243 |
244 | ### signTransaction()
245 |
246 | Signs a raw transaction with the privateKey. Returns a serialized tx which can be submitted to the node.
247 |
248 | ```javascript
249 | const identity = EthCrypto.createIdentity();
250 | const rawTx = {
251 | from: identity.address,
252 | to: '0x86Fa049857E0209aa7D9e616F7eb3b3B78ECfdb0',
253 | value: new BN('1000000000000000000'),
254 | gasPrice: 5000000000,
255 | nonce: 0,
256 | gasLimit: 21000
257 | };
258 | const signedTx = EthCrypto.signTransaction(
259 | rawTx,
260 | identity.privateKey
261 | );
262 | console.log(signedTx);
263 | // > '071d3a2040a2d2cb...'
264 |
265 | // you can now send the tx to the node
266 | const receipt = await web3.eth.sendSignedTransaction(signedTx);
267 | ```
268 |
269 | ### txDataByCompiled()
270 |
271 | Creates the data-string which must be submitted with an transaction to create a contract-instance.
272 |
273 | ```javascript
274 | const SolidityCli = require('solidity-cli');
275 |
276 | // create compiled solidity-code
277 | const compiled = await SolidityCli.compileCode(
278 | 'contract ExampleContract {...'
279 | )[':ExampleContract'];
280 |
281 | const createCode = EthCrypto.txDataByCompiled(
282 | compiled.interface, // abi
283 | compiled.bytecode, // bytecode
284 | [identity.address] // constructor-arguments
285 | );
286 |
287 | // now you can submit this to the blockchain
288 | const serializedTx = EthCrypto.signTransaction(
289 | {
290 | from: identity.address,
291 | nonce: 0,
292 | gasLimit: 5000000,
293 | gasPrice: 5000000000,
294 | data: createCode
295 | },
296 | identity.privateKey
297 | );
298 | const receipt = await web3.eth.sendSignedTransaction(serializedTx);
299 | ```
300 |
301 | ### calculateContractAddress()
302 | Calculates the address for the contract from the senders address and the nonce, without deploying it to the blockchain.
303 |
304 | ```javascript
305 | // pre-calculate address
306 | const calculatedAddress = EthCrypto.calculateContractAddress(
307 | account.address, // address of the sender
308 | 3 // nonce with which the contract will be deployed
309 | );
310 |
311 | const rawTx = {
312 | from: account.address,
313 | gasPrice: parseInt(gasPrice),
314 | nonce: 3,
315 | data: compiled.code
316 | };
317 | const receipt = await state.web3.eth.sendTransaction(rawTx);
318 |
319 | console.log(receipt.contractAddress === calculatedAddress);
320 | // > true
321 | ```
322 |
323 | ### hex compress/decompress
324 |
325 | "Compress" or "decompress" a hex-string to make it smaller. You can either compress to utf16 which reduces the size to about 1/4, or to base64 which reduces the size to about 4/5. This is not a real compression, it just make your string smaller when you have to store it in utf-16 anyways.
326 |
327 | ```javascript
328 | const hexString = '0x107be946709e41b7895eea9f2dacf998a0a9124acbb786f0fd1a826101581a07'; // 66 chars
329 |
330 | const utf16 = EthCrypto.hex.compress(hexString); // compress to utf16
331 | // > 'ၻ炞䆷襞ⶬ輦ꂩቊ쮷蛰ﴚ艡Řᨇ' // 16 chars
332 |
333 | const base64 = EthCrypto.hex.compress(hexString, true); // compress to base64
334 | // > 'EHvpRnCeQbeJXuqfLaz5mKCpEkrLt4bw/RqCYQFYGgc=' // 44 chars
335 |
336 | EthCrypto.hex.decompress(utf16); // decompress from utf16
337 | // > '0x107be946709e41b7895eea9f2d...'
338 |
339 | EthCrypto.hex.decompress(base64, true); // decompress from base64
340 | // > '0x107be946709e41b7895eea9f2d...'
341 |
342 | ```
343 |
--------------------------------------------------------------------------------
/config/karma.conf.js:
--------------------------------------------------------------------------------
1 | const configuration = {
2 | basePath: '',
3 | frameworks: [
4 | 'mocha',
5 | 'browserify',
6 | 'detectBrowsers'
7 | ],
8 | files: [
9 | '../test_tmp/karma.test.js'
10 | ],
11 | port: 9876,
12 | colors: true,
13 | autoWatch: false,
14 |
15 | /**
16 | * see
17 | * @link https://github.com/litixsoft/karma-detect-browsers
18 | */
19 | detectBrowsers: {
20 | enabled: true,
21 | usePhantomJS: false,
22 | postDetection: function(availableBrowser) {
23 | // return ['Firefox']; // comment in to test specific browser
24 | const browsers = availableBrowser
25 | .filter(b => !['PhantomJS', 'FirefoxAurora', 'FirefoxNightly'].includes(b))
26 | .map(b => {
27 | if (b === 'Chrome' || b === 'Chromium') return 'Chrome_travis_ci';
28 | else return b;
29 | });
30 | return browsers;
31 | }
32 | },
33 |
34 | // Karma plugins loaded
35 | plugins: [
36 | 'karma-mocha',
37 | 'karma-browserify',
38 | 'karma-chrome-launcher',
39 | 'karma-edge-launcher',
40 | 'karma-firefox-launcher',
41 | 'karma-ie-launcher',
42 | 'karma-opera-launcher',
43 | 'karma-detect-browsers'
44 | ],
45 |
46 | // Source files that you wanna generate coverage for.
47 | // Do not include tests or libraries (these files will be instrumented by Istanbul)
48 | preprocessors: {
49 | '../test_tmp/*.test.js': ['browserify']
50 | },
51 |
52 | client: {
53 | mocha: {
54 | bail: true,
55 | timeout: 12000
56 | }
57 | },
58 | browsers: ['Chrome_travis_ci'],
59 | browserDisconnectTimeout: 12000,
60 | processKillTimeout: 12000,
61 | customLaunchers: {
62 | Chrome_travis_ci: {
63 | base: 'ChromeHeadless',
64 | flags: ['--no-sandbox']
65 | }
66 | },
67 | singleRun: true
68 | };
69 |
70 | if (process.env.TRAVIS) {
71 | configuration.browsers = ['Chrome_travis_ci'];
72 | /**
73 | * overwrite reporters-default
74 | * So no big list will be shown at log
75 | */
76 | configuration.reporters = [];
77 | }
78 |
79 | module.exports = function(config) {
80 | config.set(configuration);
81 | };
82 |
--------------------------------------------------------------------------------
/config/webpack.config.js:
--------------------------------------------------------------------------------
1 |
2 | const path = require('path');
3 | const TerserPlugin = require('terser-webpack-plugin');
4 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
5 |
6 |
7 | //console.log(process.env.NODE_ENV);
8 | //process.exit();
9 |
10 |
11 | const plugins = [];
12 | if (process.env.NODE_ENV === 'disc')
13 | plugins.push(new BundleAnalyzerPlugin());
14 |
15 | module.exports = {
16 | mode: 'production',
17 | entry: './dist/es/browserify.index.js',
18 | optimization: {
19 | minimizer: [
20 | new TerserPlugin({
21 | parallel: true,
22 | })
23 | ]
24 | },
25 | plugins,
26 | output: {
27 | path: path.resolve(__dirname, '../test_tmp'),
28 | filename: 'webpack.bundle.js'
29 | },
30 | resolve: {
31 | fallback: {
32 | 'crypto': require.resolve('crypto-browserify'),
33 | 'stream': require.resolve('stream-browserify')
34 | }
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/contracts/.gitignore:
--------------------------------------------------------------------------------
1 | *.js
2 | *.ts
3 |
--------------------------------------------------------------------------------
/contracts/DonationBag.sol:
--------------------------------------------------------------------------------
1 | pragma solidity 0.4.23;
2 |
3 |
4 | contract DonationBag {
5 |
6 | // donation-signatures must be created by the owner
7 | address public owner;
8 |
9 | // each donation contains this amount of wei
10 | uint public amountPerDonation = 1000000000000000000; // one ether
11 |
12 | // one address can receive only one donation
13 | // the ones already received one, are stored here
14 | mapping (address => bool) public alreadyRecieved;
15 |
16 | // constructor
17 | function DonationBag(address _owner) public {
18 | owner = _owner;
19 | }
20 |
21 | /**
22 | * default function
23 | * Whenever ether is send to the contract without
24 | * transaction data, the default function is called.
25 | * If you do not have a default-function and send ether to this contract,
26 | * the transaction will be reverted with 'VM Exception while processing transaction: revert'
27 | */
28 | function() public payable {
29 | // got money
30 | }
31 |
32 | /**
33 | * to ensure the signatures for this contract cannot be
34 | * replayed somewhere else, we add this prefix to the signed hash
35 | */
36 | string public signPrefix = "Signed for DonationBag:";
37 |
38 | /**
39 | * generates a prefixed hash of the address
40 | * We hash the following together:
41 | * - signPrefix
42 | * - address of this contract
43 | * - the recievers-address
44 | */
45 | function prefixedHash(
46 | address receiver
47 | ) public constant returns(bytes32) {
48 | bytes32 hash = keccak256(
49 | signPrefix,
50 | address(this),
51 | receiver
52 | );
53 | return hash;
54 | }
55 |
56 | /**
57 | * validates if the signature is valid
58 | * by checking if the correct message was signed
59 | */
60 | function isSignatureValid(
61 | address receiver,
62 | uint8 v,
63 | bytes32 r,
64 | bytes32 s
65 | ) public constant returns (bool correct) {
66 | bytes32 mustBeSigned = prefixedHash(receiver);
67 | address signer = ecrecover(
68 | mustBeSigned,
69 | v, r, s
70 | );
71 |
72 | return (signer == owner);
73 | }
74 |
75 | /**
76 | * checks if the signature and message is valid
77 | * if yes we send some wei to the submitter
78 | */
79 | function recieveDonation(
80 | uint8 v,
81 | bytes32 r,
82 | bytes32 s
83 | ) public {
84 |
85 | // already received donation -> revert
86 | if (alreadyRecieved[msg.sender] == true) revert();
87 |
88 | // signature not valid -> revert
89 | if (isSignatureValid(
90 | msg.sender,
91 | v, r, s
92 | ) == false) {
93 | revert();
94 | }
95 |
96 | // all valid -> send wei
97 | alreadyRecieved[msg.sender] = true;
98 | msg.sender.transfer(amountPerDonation);
99 | }
100 |
101 | /**
102 | * returns the current contract-balance
103 | */
104 | function getBalance() public constant returns (uint256 balance) {
105 | return this.balance;
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/contracts/TestContract.sol:
--------------------------------------------------------------------------------
1 | pragma solidity 0.4.23;
2 |
3 | /**
4 | * this is a test-contract
5 | * to run tests against it
6 | * so we can be sure the code works together with ethereum tools
7 | */
8 |
9 | contract TestContract {
10 |
11 | uint public onePublicValue = 1337;
12 |
13 | /**
14 | * hashes the given values
15 | * should be equal to own hash()-function in js
16 | */
17 | function hashNumber(
18 | uint256 someNumber
19 | ) public constant returns(bytes32) {
20 | return keccak256(
21 | someNumber
22 | );
23 | }
24 |
25 | function hashString(
26 | string someString
27 | ) public constant returns(bytes32) {
28 | return keccak256(
29 | someString
30 | );
31 | }
32 |
33 | function hashMulti(
34 | string someString,
35 | uint256 someNumber,
36 | bool someBool
37 | ) public constant returns(bytes32) {
38 | return keccak256(
39 | someString,
40 | someNumber,
41 | someBool
42 | );
43 | }
44 |
45 | /**
46 | * see https://ethereum.stackexchange.com/a/21037/1375
47 | */
48 | function signHashLikeWeb3Sign(
49 | bytes32 _hash
50 | ) public constant returns (bytes32) {
51 | bytes memory prefix = "\x19Ethereum Signed Message:\n32";
52 | bytes32 prefixedHash = keccak256(prefix, _hash);
53 | return prefixedHash;
54 | }
55 |
56 | /**
57 | * checks if the signature is valid
58 | * should be valid for signature created from the sign()-function in js
59 | */
60 | function recoverSignature(
61 | bytes32 messageHash,
62 | uint8 v,
63 | bytes32 r,
64 | bytes32 s
65 | ) public constant returns (address) {
66 | address signer = ecrecover(
67 | messageHash,
68 | v, r, s
69 | );
70 | return signer;
71 | }
72 |
73 | /**
74 | * recovers the signer from the message instead of the messageHash
75 | */
76 | function recoverSignatureFromMessage(
77 | string _message,
78 | uint8 v,
79 | bytes32 r,
80 | bytes32 s
81 | ) public constant returns (address) {
82 | bytes32 hash = hashString(_message);
83 | address signer = ecrecover(
84 | hash,
85 | v, r, s
86 | );
87 | return signer;
88 | }
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/dist/es/browserify.index.js:
--------------------------------------------------------------------------------
1 | var EthCrypto = require('./index.js');
2 | window['EthCrypto'] = EthCrypto;
--------------------------------------------------------------------------------
/dist/es/calculate-contract-address.js:
--------------------------------------------------------------------------------
1 | import { generateAddress, toChecksumAddress, toBuffer } from 'ethereumjs-util';
2 | import { addLeading0x } from './util';
3 | export function calculateContractAddress(creatorAddress, nonce) {
4 | var addressBuffer = generateAddress(toBuffer(addLeading0x(creatorAddress)), toBuffer(nonce));
5 | var address = addressBuffer.toString('hex');
6 | return toChecksumAddress(addLeading0x(address));
7 | }
--------------------------------------------------------------------------------
/dist/es/cipher.js:
--------------------------------------------------------------------------------
1 | import { compress, decompress } from './public-key';
2 | export function stringify(cipher) {
3 | if (typeof cipher === 'string') return cipher;
4 |
5 | // use compressed key because it's smaller
6 | var compressedKey = compress(cipher.ephemPublicKey);
7 | var ret = Buffer.concat([Buffer.from(cipher.iv, 'hex'),
8 | // 16bit
9 | Buffer.from(compressedKey, 'hex'),
10 | // 33bit
11 | Buffer.from(cipher.mac, 'hex'),
12 | // 32bit
13 | Buffer.from(cipher.ciphertext, 'hex') // var bit
14 | ]);
15 | return ret.toString('hex');
16 | }
17 | export function parse(str) {
18 | if (typeof str !== 'string') return str;
19 | var buf = Buffer.from(str, 'hex');
20 | var ret = {
21 | iv: buf.toString('hex', 0, 16),
22 | ephemPublicKey: buf.toString('hex', 16, 49),
23 | mac: buf.toString('hex', 49, 81),
24 | ciphertext: buf.toString('hex', 81, buf.length)
25 | };
26 |
27 | // decompress publicKey
28 | ret.ephemPublicKey = '04' + decompress(ret.ephemPublicKey);
29 | return ret;
30 | }
--------------------------------------------------------------------------------
/dist/es/create-identity.js:
--------------------------------------------------------------------------------
1 | import { utils as ethersUtils, Wallet } from 'ethers';
2 | import { stripHexPrefix } from 'ethereumjs-util';
3 | var MIN_ENTROPY_SIZE = 128;
4 | var keccak256 = ethersUtils.keccak256;
5 |
6 | /**
7 | * create a privateKey from the given entropy or a new one
8 | * @param {Buffer} entropy
9 | * @return {string}
10 | */
11 | export function createPrivateKey(entropy) {
12 | if (entropy) {
13 | if (!Buffer.isBuffer(entropy)) throw new Error('EthCrypto.createPrivateKey(): given entropy is no Buffer');
14 | if (Buffer.byteLength(entropy, 'utf8') < MIN_ENTROPY_SIZE) throw new Error('EthCrypto.createPrivateKey(): Entropy-size must be at least ' + MIN_ENTROPY_SIZE);
15 | var outerHex = keccak256(entropy);
16 | return outerHex;
17 | } else {
18 | var innerHex = keccak256(ethersUtils.concat([ethersUtils.randomBytes(32), ethersUtils.randomBytes(32)]));
19 | var middleHex = ethersUtils.concat([ethersUtils.concat([ethersUtils.randomBytes(32), innerHex]), ethersUtils.randomBytes(32)]);
20 | var _outerHex = keccak256(middleHex);
21 | return _outerHex;
22 | }
23 | }
24 |
25 | /**
26 | * creates a new object with
27 | * private-, public-Key and address
28 | * @param {Buffer?} entropy if provided, will use that as single random-source
29 | */
30 | export function createIdentity(entropy) {
31 | var privateKey = createPrivateKey(entropy);
32 | var wallet = new Wallet(privateKey);
33 | var identity = {
34 | privateKey: privateKey,
35 | // remove trailing '0x04'
36 | publicKey: stripHexPrefix(wallet.publicKey).slice(2),
37 | address: wallet.address
38 | };
39 | return identity;
40 | }
--------------------------------------------------------------------------------
/dist/es/decrypt-with-private-key.js:
--------------------------------------------------------------------------------
1 | import { decrypt } from 'eccrypto';
2 | import { parse } from './cipher';
3 | import { removeLeading0x } from './util';
4 | export function decryptWithPrivateKey(privateKey, encrypted) {
5 | encrypted = parse(encrypted);
6 |
7 | // remove trailing '0x' from privateKey
8 | var twoStripped = removeLeading0x(privateKey);
9 | var encryptedBuffer = {
10 | iv: Buffer.from(encrypted.iv, 'hex'),
11 | ephemPublicKey: Buffer.from(encrypted.ephemPublicKey, 'hex'),
12 | ciphertext: Buffer.from(encrypted.ciphertext, 'hex'),
13 | mac: Buffer.from(encrypted.mac, 'hex')
14 | };
15 | return decrypt(Buffer.from(twoStripped, 'hex'), encryptedBuffer).then(function (decryptedBuffer) {
16 | return decryptedBuffer.toString();
17 | });
18 | }
--------------------------------------------------------------------------------
/dist/es/encrypt-with-public-key.js:
--------------------------------------------------------------------------------
1 | import { encrypt } from 'eccrypto';
2 | import { decompress } from './public-key';
3 | export function encryptWithPublicKey(publicKey, message, opts) {
4 | // ensure its an uncompressed publicKey
5 | publicKey = decompress(publicKey);
6 |
7 | // re-add the compression-flag
8 | var pubString = '04' + publicKey;
9 | return encrypt(Buffer.from(pubString, 'hex'), Buffer.from(message), opts ? opts : {}).then(function (encryptedBuffers) {
10 | var encrypted = {
11 | iv: encryptedBuffers.iv.toString('hex'),
12 | ephemPublicKey: encryptedBuffers.ephemPublicKey.toString('hex'),
13 | ciphertext: encryptedBuffers.ciphertext.toString('hex'),
14 | mac: encryptedBuffers.mac.toString('hex')
15 | };
16 | return encrypted;
17 | });
18 | }
--------------------------------------------------------------------------------
/dist/es/hash.js:
--------------------------------------------------------------------------------
1 | import { utils as ethersUtils } from 'ethers';
2 | export function keccak256(params) {
3 | var types = [];
4 | var values = [];
5 | if (!Array.isArray(params)) {
6 | types.push('string');
7 | values.push(params);
8 | } else {
9 | params.forEach(function (p) {
10 | types.push(p.type);
11 | values.push(p.value);
12 | });
13 | }
14 | return ethersUtils.solidityKeccak256(types, values);
15 | }
16 | export var SIGN_PREFIX = '\x19Ethereum Signed Message:\n32';
--------------------------------------------------------------------------------
/dist/es/hex.js:
--------------------------------------------------------------------------------
1 | /**
2 | * compress/decompress hex-strings to utf16 or base64
3 | * thx @juvian
4 | * @link https://stackoverflow.com/a/40471908/3443137
5 | */
6 |
7 | import { removeLeading0x, addLeading0x } from './util';
8 | export function compress(hex) {
9 | var base64 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
10 | hex = removeLeading0x(hex);
11 |
12 | // if base64:true, we use our own function because it results in a smaller output
13 | if (base64 === true) return Buffer.from(hex, 'hex').toString('base64');
14 | var string = '';
15 | while (hex.length % 4 != 0) {
16 | // we need it to be multiple of 4
17 | hex = '0' + hex;
18 | }
19 | for (var i = 0; i < hex.length; i += 4) {
20 | // get char from ascii code which goes from 0 to 65536
21 | string += String.fromCharCode(parseInt(hex.substring(i, i + 4), 16));
22 | }
23 | return string;
24 | }
25 | export function decompress(compressedString) {
26 | var base64 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
27 | // if base64:true, we use our own function because it results in a smaller output
28 | if (base64 === true) {
29 | var ret = Buffer.from(compressedString, 'base64').toString('hex');
30 | return addLeading0x(ret);
31 | }
32 | var hex = '';
33 | for (var i = 0; i < compressedString.length; i++) {
34 | // get character ascii code and convert to hexa string, adding necessary 0s
35 | hex += ((i == 0 ? '' : '000') + compressedString.charCodeAt(i).toString(16)).slice(-4);
36 | }
37 | hex = hex.toLowerCase();
38 | return addLeading0x(hex);
39 | }
--------------------------------------------------------------------------------
/dist/es/index.js:
--------------------------------------------------------------------------------
1 | import { createIdentity } from './create-identity';
2 | import * as publicKey from './public-key';
3 | import { decryptWithPrivateKey } from './decrypt-with-private-key';
4 | import { encryptWithPublicKey } from './encrypt-with-public-key';
5 | import * as cipher from './cipher';
6 | import { publicKeyByPrivateKey } from './public-key-by-private-key';
7 | import { recover } from './recover';
8 | import { recoverPublicKey } from './recover-public-key';
9 | import { sign } from './sign';
10 | import { signTransaction } from './sign-transaction';
11 | import { txDataByCompiled } from './tx-data-by-compiled';
12 | import { calculateContractAddress } from './calculate-contract-address';
13 | import * as hash from './hash';
14 | import * as hex from './hex';
15 | import * as vrs from './vrs';
16 | import * as util from './util';
17 | export { createIdentity, publicKey, decryptWithPrivateKey, encryptWithPublicKey, cipher, publicKeyByPrivateKey, recover, recoverPublicKey, sign, signTransaction, txDataByCompiled, calculateContractAddress, hash, hex, vrs, util };
18 | export default {
19 | createIdentity: createIdentity,
20 | publicKey: publicKey,
21 | decryptWithPrivateKey: decryptWithPrivateKey,
22 | encryptWithPublicKey: encryptWithPublicKey,
23 | cipher: cipher,
24 | publicKeyByPrivateKey: publicKeyByPrivateKey,
25 | recover: recover,
26 | recoverPublicKey: recoverPublicKey,
27 | sign: sign,
28 | signTransaction: signTransaction,
29 | txDataByCompiled: txDataByCompiled,
30 | calculateContractAddress: calculateContractAddress,
31 | hash: hash,
32 | hex: hex,
33 | vrs: vrs,
34 | util: util
35 | };
--------------------------------------------------------------------------------
/dist/es/public-key-by-private-key.js:
--------------------------------------------------------------------------------
1 | import { privateToPublic, toBuffer } from 'ethereumjs-util';
2 | import { addLeading0x } from './util';
3 |
4 | /**
5 | * Generate publicKey from the privateKey.
6 | * This creates the uncompressed publicKey,
7 | * where 04 has stripped from left
8 | * @returns {string}
9 | */
10 | export function publicKeyByPrivateKey(privateKey) {
11 | privateKey = addLeading0x(privateKey);
12 | var publicKeyBuffer = privateToPublic(toBuffer(privateKey));
13 | return publicKeyBuffer.toString('hex');
14 | }
--------------------------------------------------------------------------------
/dist/es/public-key.js:
--------------------------------------------------------------------------------
1 | import { publicKeyConvert } from 'secp256k1';
2 | import { pubToAddress, toChecksumAddress, toBuffer } from 'ethereumjs-util';
3 | import { hexToUnit8Array, uint8ArrayToHex, addLeading0x } from './util';
4 | export function compress(startsWith04) {
5 | // add trailing 04 if not done before
6 | var testBuffer = Buffer.from(startsWith04, 'hex');
7 | if (testBuffer.length === 64) startsWith04 = '04' + startsWith04;
8 | return uint8ArrayToHex(publicKeyConvert(hexToUnit8Array(startsWith04), true));
9 | }
10 | export function decompress(startsWith02Or03) {
11 | // if already decompressed an not has trailing 04
12 | var testBuffer = Buffer.from(startsWith02Or03, 'hex');
13 | if (testBuffer.length === 64) startsWith02Or03 = '04' + startsWith02Or03;
14 | var decompressed = uint8ArrayToHex(publicKeyConvert(hexToUnit8Array(startsWith02Or03), false));
15 |
16 | // remove trailing 04
17 | decompressed = decompressed.substring(2);
18 | return decompressed;
19 | }
20 |
21 | /**
22 | * generates the ethereum-address of the publicKey
23 | * We create the checksum-address which is case-sensitive
24 | * @returns {string} address
25 | */
26 | export function toAddress(publicKey) {
27 | // normalize key
28 | publicKey = decompress(publicKey);
29 | var addressBuffer = pubToAddress(toBuffer(addLeading0x(publicKey)));
30 | var checkSumAdress = toChecksumAddress(addLeading0x(addressBuffer.toString('hex')));
31 | return checkSumAdress;
32 | }
--------------------------------------------------------------------------------
/dist/es/recover-public-key.js:
--------------------------------------------------------------------------------
1 | import { ecdsaRecover } from 'secp256k1';
2 | import { removeLeading0x, hexToUnit8Array, uint8ArrayToHex } from './util';
3 |
4 | /**
5 | * returns the publicKey for the privateKey with which the messageHash was signed
6 | * @param {string} signature
7 | * @param {string} hash
8 | * @return {string} publicKey
9 | */
10 | export function recoverPublicKey(signature, hash) {
11 | signature = removeLeading0x(signature);
12 |
13 | // split into v-value and sig
14 | var sigOnly = signature.substring(0, signature.length - 2); // all but last 2 chars
15 | var vValue = signature.slice(-2); // last 2 chars
16 |
17 | var recoveryNumber = vValue === '1c' ? 1 : 0;
18 | var pubKey = uint8ArrayToHex(ecdsaRecover(hexToUnit8Array(sigOnly), recoveryNumber, hexToUnit8Array(removeLeading0x(hash)), false));
19 |
20 | // remove trailing '04'
21 | pubKey = pubKey.slice(2);
22 | return pubKey;
23 | }
--------------------------------------------------------------------------------
/dist/es/recover.js:
--------------------------------------------------------------------------------
1 | import { recoverPublicKey } from './recover-public-key';
2 | import { toAddress as addressByPublicKey } from './public-key';
3 |
4 | /**
5 | * returns the address with which the messageHash was signed
6 | * @param {string} sigString
7 | * @param {string} hash
8 | * @return {string} address
9 | */
10 | export function recover(sigString, hash) {
11 | var pubkey = recoverPublicKey(sigString, hash);
12 | var address = addressByPublicKey(pubkey);
13 | return address;
14 | }
--------------------------------------------------------------------------------
/dist/es/sign-transaction.js:
--------------------------------------------------------------------------------
1 | import { Transaction } from '@ethereumjs/tx';
2 | import { publicKeyByPrivateKey } from './public-key-by-private-key';
3 | import { toAddress as addressByPublicKey } from './public-key';
4 | export function signTransaction(rawTx, privateKey) {
5 | var txOptions = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
6 | // check if privateKey->address matches rawTx.from
7 | var publicKey = publicKeyByPrivateKey(privateKey);
8 | var address = addressByPublicKey(publicKey);
9 | if (address != rawTx.from) throw new Error('EthCrypto.signTransaction(): rawTx.from does not match the address of the privateKey');
10 | var privateKeyBuffer = Buffer.from(privateKey.replace(/^.{2}/g, ''), 'hex');
11 | var tx = Transaction.fromTxData(rawTx, txOptions);
12 | var signedTx = tx.sign(privateKeyBuffer);
13 | var serializedTx = signedTx.serialize().toString('hex');
14 | return serializedTx;
15 | }
--------------------------------------------------------------------------------
/dist/es/sign.js:
--------------------------------------------------------------------------------
1 | import { ecdsaSign as secp256k1_sign } from 'secp256k1';
2 | import { addLeading0x, removeLeading0x } from './util';
3 |
4 | /**
5 | * signs the given message
6 | * we do not use sign from eth-lib because the pure secp256k1-version is 90% faster
7 | * @param {string} privateKey
8 | * @param {string} hash
9 | * @return {string} hexString
10 | */
11 | export function sign(privateKey, hash) {
12 | hash = addLeading0x(hash);
13 | if (hash.length !== 66) throw new Error('EthCrypto.sign(): Can only sign hashes, given: ' + hash);
14 | var sigObj = secp256k1_sign(new Uint8Array(Buffer.from(removeLeading0x(hash), 'hex')), new Uint8Array(Buffer.from(removeLeading0x(privateKey), 'hex')));
15 | var recoveryId = sigObj.recid === 1 ? '1c' : '1b';
16 | var newSignature = '0x' + Buffer.from(sigObj.signature).toString('hex') + recoveryId;
17 | return newSignature;
18 | }
--------------------------------------------------------------------------------
/dist/es/tx-data-by-compiled.js:
--------------------------------------------------------------------------------
1 | import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
2 | import { ContractFactory } from 'ethers';
3 | export function txDataByCompiled(abi, bytecode, args) {
4 | // solc returns a string which is often passed instead of the json
5 | if (typeof abi === 'string') abi = JSON.parse(abi);
6 |
7 | // Construct a Contract Factory
8 | var factory = new ContractFactory(abi, '0x' + bytecode);
9 | var deployTransaction = factory.getDeployTransaction.apply(factory, _toConsumableArray(args));
10 | return deployTransaction.data;
11 | }
--------------------------------------------------------------------------------
/dist/es/util.js:
--------------------------------------------------------------------------------
1 | export function removeLeading0x(str) {
2 | if (str.startsWith('0x')) return str.substring(2);else return str;
3 | }
4 | export function addLeading0x(str) {
5 | if (!str.startsWith('0x')) return '0x' + str;else return str;
6 | }
7 | export function uint8ArrayToHex(arr) {
8 | return Buffer.from(arr).toString('hex');
9 | }
10 | export function hexToUnit8Array(str) {
11 | return new Uint8Array(Buffer.from(str, 'hex'));
12 | }
--------------------------------------------------------------------------------
/dist/es/vrs.js:
--------------------------------------------------------------------------------
1 | import { utils as ethersUtils } from 'ethers';
2 | /**
3 | * split signature-hex into parts
4 | * @param {string} hexString
5 | * @return {{v: string, r: string, s: string}}
6 | */
7 | export function fromString(hexString) {
8 | var arr = ethersUtils.splitSignature(hexString);
9 | return {
10 | // convert "v" to hex
11 | v: "0x".concat(arr.v.toString(16)),
12 | r: arr.r,
13 | s: arr.s
14 | };
15 | }
16 |
17 | /**
18 | * merge signature-parts to one string
19 | * @param {{v: string, r: string, s: string}} sig
20 | * @return {string} hexString
21 | */
22 | export function toString(sig) {
23 | return ethersUtils.joinSignature(sig);
24 | }
--------------------------------------------------------------------------------
/dist/lib/browserify.index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var EthCrypto = require('./index.js');
4 | window['EthCrypto'] = EthCrypto;
--------------------------------------------------------------------------------
/dist/lib/calculate-contract-address.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.calculateContractAddress = calculateContractAddress;
7 | var _ethereumjsUtil = require("ethereumjs-util");
8 | var _util = require("./util");
9 | function calculateContractAddress(creatorAddress, nonce) {
10 | var addressBuffer = (0, _ethereumjsUtil.generateAddress)((0, _ethereumjsUtil.toBuffer)((0, _util.addLeading0x)(creatorAddress)), (0, _ethereumjsUtil.toBuffer)(nonce));
11 | var address = addressBuffer.toString('hex');
12 | return (0, _ethereumjsUtil.toChecksumAddress)((0, _util.addLeading0x)(address));
13 | }
--------------------------------------------------------------------------------
/dist/lib/cipher.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.parse = parse;
7 | exports.stringify = stringify;
8 | var _publicKey = require("./public-key");
9 | function stringify(cipher) {
10 | if (typeof cipher === 'string') return cipher;
11 |
12 | // use compressed key because it's smaller
13 | var compressedKey = (0, _publicKey.compress)(cipher.ephemPublicKey);
14 | var ret = Buffer.concat([Buffer.from(cipher.iv, 'hex'),
15 | // 16bit
16 | Buffer.from(compressedKey, 'hex'),
17 | // 33bit
18 | Buffer.from(cipher.mac, 'hex'),
19 | // 32bit
20 | Buffer.from(cipher.ciphertext, 'hex') // var bit
21 | ]);
22 | return ret.toString('hex');
23 | }
24 | function parse(str) {
25 | if (typeof str !== 'string') return str;
26 | var buf = Buffer.from(str, 'hex');
27 | var ret = {
28 | iv: buf.toString('hex', 0, 16),
29 | ephemPublicKey: buf.toString('hex', 16, 49),
30 | mac: buf.toString('hex', 49, 81),
31 | ciphertext: buf.toString('hex', 81, buf.length)
32 | };
33 |
34 | // decompress publicKey
35 | ret.ephemPublicKey = '04' + (0, _publicKey.decompress)(ret.ephemPublicKey);
36 | return ret;
37 | }
--------------------------------------------------------------------------------
/dist/lib/create-identity.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.createIdentity = createIdentity;
7 | exports.createPrivateKey = createPrivateKey;
8 | var _ethers = require("ethers");
9 | var _ethereumjsUtil = require("ethereumjs-util");
10 | var MIN_ENTROPY_SIZE = 128;
11 | var keccak256 = _ethers.utils.keccak256;
12 |
13 | /**
14 | * create a privateKey from the given entropy or a new one
15 | * @param {Buffer} entropy
16 | * @return {string}
17 | */
18 | function createPrivateKey(entropy) {
19 | if (entropy) {
20 | if (!Buffer.isBuffer(entropy)) throw new Error('EthCrypto.createPrivateKey(): given entropy is no Buffer');
21 | if (Buffer.byteLength(entropy, 'utf8') < MIN_ENTROPY_SIZE) throw new Error('EthCrypto.createPrivateKey(): Entropy-size must be at least ' + MIN_ENTROPY_SIZE);
22 | var outerHex = keccak256(entropy);
23 | return outerHex;
24 | } else {
25 | var innerHex = keccak256(_ethers.utils.concat([_ethers.utils.randomBytes(32), _ethers.utils.randomBytes(32)]));
26 | var middleHex = _ethers.utils.concat([_ethers.utils.concat([_ethers.utils.randomBytes(32), innerHex]), _ethers.utils.randomBytes(32)]);
27 | var _outerHex = keccak256(middleHex);
28 | return _outerHex;
29 | }
30 | }
31 |
32 | /**
33 | * creates a new object with
34 | * private-, public-Key and address
35 | * @param {Buffer?} entropy if provided, will use that as single random-source
36 | */
37 | function createIdentity(entropy) {
38 | var privateKey = createPrivateKey(entropy);
39 | var wallet = new _ethers.Wallet(privateKey);
40 | var identity = {
41 | privateKey: privateKey,
42 | // remove trailing '0x04'
43 | publicKey: (0, _ethereumjsUtil.stripHexPrefix)(wallet.publicKey).slice(2),
44 | address: wallet.address
45 | };
46 | return identity;
47 | }
--------------------------------------------------------------------------------
/dist/lib/decrypt-with-private-key.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.decryptWithPrivateKey = decryptWithPrivateKey;
7 | var _eccrypto = require("eccrypto");
8 | var _cipher = require("./cipher");
9 | var _util = require("./util");
10 | function decryptWithPrivateKey(privateKey, encrypted) {
11 | encrypted = (0, _cipher.parse)(encrypted);
12 |
13 | // remove trailing '0x' from privateKey
14 | var twoStripped = (0, _util.removeLeading0x)(privateKey);
15 | var encryptedBuffer = {
16 | iv: Buffer.from(encrypted.iv, 'hex'),
17 | ephemPublicKey: Buffer.from(encrypted.ephemPublicKey, 'hex'),
18 | ciphertext: Buffer.from(encrypted.ciphertext, 'hex'),
19 | mac: Buffer.from(encrypted.mac, 'hex')
20 | };
21 | return (0, _eccrypto.decrypt)(Buffer.from(twoStripped, 'hex'), encryptedBuffer).then(function (decryptedBuffer) {
22 | return decryptedBuffer.toString();
23 | });
24 | }
--------------------------------------------------------------------------------
/dist/lib/encrypt-with-public-key.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.encryptWithPublicKey = encryptWithPublicKey;
7 | var _eccrypto = require("eccrypto");
8 | var _publicKey = require("./public-key");
9 | function encryptWithPublicKey(publicKey, message, opts) {
10 | // ensure its an uncompressed publicKey
11 | publicKey = (0, _publicKey.decompress)(publicKey);
12 |
13 | // re-add the compression-flag
14 | var pubString = '04' + publicKey;
15 | return (0, _eccrypto.encrypt)(Buffer.from(pubString, 'hex'), Buffer.from(message), opts ? opts : {}).then(function (encryptedBuffers) {
16 | var encrypted = {
17 | iv: encryptedBuffers.iv.toString('hex'),
18 | ephemPublicKey: encryptedBuffers.ephemPublicKey.toString('hex'),
19 | ciphertext: encryptedBuffers.ciphertext.toString('hex'),
20 | mac: encryptedBuffers.mac.toString('hex')
21 | };
22 | return encrypted;
23 | });
24 | }
--------------------------------------------------------------------------------
/dist/lib/hash.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.SIGN_PREFIX = void 0;
7 | exports.keccak256 = keccak256;
8 | var _ethers = require("ethers");
9 | function keccak256(params) {
10 | var types = [];
11 | var values = [];
12 | if (!Array.isArray(params)) {
13 | types.push('string');
14 | values.push(params);
15 | } else {
16 | params.forEach(function (p) {
17 | types.push(p.type);
18 | values.push(p.value);
19 | });
20 | }
21 | return _ethers.utils.solidityKeccak256(types, values);
22 | }
23 | var SIGN_PREFIX = exports.SIGN_PREFIX = '\x19Ethereum Signed Message:\n32';
--------------------------------------------------------------------------------
/dist/lib/hex.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.compress = compress;
7 | exports.decompress = decompress;
8 | var _util = require("./util");
9 | /**
10 | * compress/decompress hex-strings to utf16 or base64
11 | * thx @juvian
12 | * @link https://stackoverflow.com/a/40471908/3443137
13 | */
14 |
15 | function compress(hex) {
16 | var base64 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
17 | hex = (0, _util.removeLeading0x)(hex);
18 |
19 | // if base64:true, we use our own function because it results in a smaller output
20 | if (base64 === true) return Buffer.from(hex, 'hex').toString('base64');
21 | var string = '';
22 | while (hex.length % 4 != 0) {
23 | // we need it to be multiple of 4
24 | hex = '0' + hex;
25 | }
26 | for (var i = 0; i < hex.length; i += 4) {
27 | // get char from ascii code which goes from 0 to 65536
28 | string += String.fromCharCode(parseInt(hex.substring(i, i + 4), 16));
29 | }
30 | return string;
31 | }
32 | function decompress(compressedString) {
33 | var base64 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
34 | // if base64:true, we use our own function because it results in a smaller output
35 | if (base64 === true) {
36 | var ret = Buffer.from(compressedString, 'base64').toString('hex');
37 | return (0, _util.addLeading0x)(ret);
38 | }
39 | var hex = '';
40 | for (var i = 0; i < compressedString.length; i++) {
41 | // get character ascii code and convert to hexa string, adding necessary 0s
42 | hex += ((i == 0 ? '' : '000') + compressedString.charCodeAt(i).toString(16)).slice(-4);
43 | }
44 | hex = hex.toLowerCase();
45 | return (0, _util.addLeading0x)(hex);
46 | }
--------------------------------------------------------------------------------
/dist/lib/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _typeof = require("@babel/runtime/helpers/typeof");
4 | Object.defineProperty(exports, "__esModule", {
5 | value: true
6 | });
7 | Object.defineProperty(exports, "calculateContractAddress", {
8 | enumerable: true,
9 | get: function get() {
10 | return _calculateContractAddress.calculateContractAddress;
11 | }
12 | });
13 | exports.cipher = void 0;
14 | Object.defineProperty(exports, "createIdentity", {
15 | enumerable: true,
16 | get: function get() {
17 | return _createIdentity.createIdentity;
18 | }
19 | });
20 | Object.defineProperty(exports, "decryptWithPrivateKey", {
21 | enumerable: true,
22 | get: function get() {
23 | return _decryptWithPrivateKey.decryptWithPrivateKey;
24 | }
25 | });
26 | exports["default"] = void 0;
27 | Object.defineProperty(exports, "encryptWithPublicKey", {
28 | enumerable: true,
29 | get: function get() {
30 | return _encryptWithPublicKey.encryptWithPublicKey;
31 | }
32 | });
33 | exports.publicKey = exports.hex = exports.hash = void 0;
34 | Object.defineProperty(exports, "publicKeyByPrivateKey", {
35 | enumerable: true,
36 | get: function get() {
37 | return _publicKeyByPrivateKey.publicKeyByPrivateKey;
38 | }
39 | });
40 | Object.defineProperty(exports, "recover", {
41 | enumerable: true,
42 | get: function get() {
43 | return _recover.recover;
44 | }
45 | });
46 | Object.defineProperty(exports, "recoverPublicKey", {
47 | enumerable: true,
48 | get: function get() {
49 | return _recoverPublicKey.recoverPublicKey;
50 | }
51 | });
52 | Object.defineProperty(exports, "sign", {
53 | enumerable: true,
54 | get: function get() {
55 | return _sign.sign;
56 | }
57 | });
58 | Object.defineProperty(exports, "signTransaction", {
59 | enumerable: true,
60 | get: function get() {
61 | return _signTransaction.signTransaction;
62 | }
63 | });
64 | Object.defineProperty(exports, "txDataByCompiled", {
65 | enumerable: true,
66 | get: function get() {
67 | return _txDataByCompiled.txDataByCompiled;
68 | }
69 | });
70 | exports.vrs = exports.util = void 0;
71 | var _createIdentity = require("./create-identity");
72 | var publicKey = _interopRequireWildcard(require("./public-key"));
73 | exports.publicKey = publicKey;
74 | var _decryptWithPrivateKey = require("./decrypt-with-private-key");
75 | var _encryptWithPublicKey = require("./encrypt-with-public-key");
76 | var cipher = _interopRequireWildcard(require("./cipher"));
77 | exports.cipher = cipher;
78 | var _publicKeyByPrivateKey = require("./public-key-by-private-key");
79 | var _recover = require("./recover");
80 | var _recoverPublicKey = require("./recover-public-key");
81 | var _sign = require("./sign");
82 | var _signTransaction = require("./sign-transaction");
83 | var _txDataByCompiled = require("./tx-data-by-compiled");
84 | var _calculateContractAddress = require("./calculate-contract-address");
85 | var hash = _interopRequireWildcard(require("./hash"));
86 | exports.hash = hash;
87 | var hex = _interopRequireWildcard(require("./hex"));
88 | exports.hex = hex;
89 | var vrs = _interopRequireWildcard(require("./vrs"));
90 | exports.vrs = vrs;
91 | var util = _interopRequireWildcard(require("./util"));
92 | exports.util = util;
93 | function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
94 | function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { "default": e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n["default"] = e, t && t.set(e, n), n; }
95 | var _default = exports["default"] = {
96 | createIdentity: _createIdentity.createIdentity,
97 | publicKey: publicKey,
98 | decryptWithPrivateKey: _decryptWithPrivateKey.decryptWithPrivateKey,
99 | encryptWithPublicKey: _encryptWithPublicKey.encryptWithPublicKey,
100 | cipher: cipher,
101 | publicKeyByPrivateKey: _publicKeyByPrivateKey.publicKeyByPrivateKey,
102 | recover: _recover.recover,
103 | recoverPublicKey: _recoverPublicKey.recoverPublicKey,
104 | sign: _sign.sign,
105 | signTransaction: _signTransaction.signTransaction,
106 | txDataByCompiled: _txDataByCompiled.txDataByCompiled,
107 | calculateContractAddress: _calculateContractAddress.calculateContractAddress,
108 | hash: hash,
109 | hex: hex,
110 | vrs: vrs,
111 | util: util
112 | };
--------------------------------------------------------------------------------
/dist/lib/public-key-by-private-key.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.publicKeyByPrivateKey = publicKeyByPrivateKey;
7 | var _ethereumjsUtil = require("ethereumjs-util");
8 | var _util = require("./util");
9 | /**
10 | * Generate publicKey from the privateKey.
11 | * This creates the uncompressed publicKey,
12 | * where 04 has stripped from left
13 | * @returns {string}
14 | */
15 | function publicKeyByPrivateKey(privateKey) {
16 | privateKey = (0, _util.addLeading0x)(privateKey);
17 | var publicKeyBuffer = (0, _ethereumjsUtil.privateToPublic)((0, _ethereumjsUtil.toBuffer)(privateKey));
18 | return publicKeyBuffer.toString('hex');
19 | }
--------------------------------------------------------------------------------
/dist/lib/public-key.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.compress = compress;
7 | exports.decompress = decompress;
8 | exports.toAddress = toAddress;
9 | var _secp256k = require("secp256k1");
10 | var _ethereumjsUtil = require("ethereumjs-util");
11 | var _util = require("./util");
12 | function compress(startsWith04) {
13 | // add trailing 04 if not done before
14 | var testBuffer = Buffer.from(startsWith04, 'hex');
15 | if (testBuffer.length === 64) startsWith04 = '04' + startsWith04;
16 | return (0, _util.uint8ArrayToHex)((0, _secp256k.publicKeyConvert)((0, _util.hexToUnit8Array)(startsWith04), true));
17 | }
18 | function decompress(startsWith02Or03) {
19 | // if already decompressed an not has trailing 04
20 | var testBuffer = Buffer.from(startsWith02Or03, 'hex');
21 | if (testBuffer.length === 64) startsWith02Or03 = '04' + startsWith02Or03;
22 | var decompressed = (0, _util.uint8ArrayToHex)((0, _secp256k.publicKeyConvert)((0, _util.hexToUnit8Array)(startsWith02Or03), false));
23 |
24 | // remove trailing 04
25 | decompressed = decompressed.substring(2);
26 | return decompressed;
27 | }
28 |
29 | /**
30 | * generates the ethereum-address of the publicKey
31 | * We create the checksum-address which is case-sensitive
32 | * @returns {string} address
33 | */
34 | function toAddress(publicKey) {
35 | // normalize key
36 | publicKey = decompress(publicKey);
37 | var addressBuffer = (0, _ethereumjsUtil.pubToAddress)((0, _ethereumjsUtil.toBuffer)((0, _util.addLeading0x)(publicKey)));
38 | var checkSumAdress = (0, _ethereumjsUtil.toChecksumAddress)((0, _util.addLeading0x)(addressBuffer.toString('hex')));
39 | return checkSumAdress;
40 | }
--------------------------------------------------------------------------------
/dist/lib/recover-public-key.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.recoverPublicKey = recoverPublicKey;
7 | var _secp256k = require("secp256k1");
8 | var _util = require("./util");
9 | /**
10 | * returns the publicKey for the privateKey with which the messageHash was signed
11 | * @param {string} signature
12 | * @param {string} hash
13 | * @return {string} publicKey
14 | */
15 | function recoverPublicKey(signature, hash) {
16 | signature = (0, _util.removeLeading0x)(signature);
17 |
18 | // split into v-value and sig
19 | var sigOnly = signature.substring(0, signature.length - 2); // all but last 2 chars
20 | var vValue = signature.slice(-2); // last 2 chars
21 |
22 | var recoveryNumber = vValue === '1c' ? 1 : 0;
23 | var pubKey = (0, _util.uint8ArrayToHex)((0, _secp256k.ecdsaRecover)((0, _util.hexToUnit8Array)(sigOnly), recoveryNumber, (0, _util.hexToUnit8Array)((0, _util.removeLeading0x)(hash)), false));
24 |
25 | // remove trailing '04'
26 | pubKey = pubKey.slice(2);
27 | return pubKey;
28 | }
--------------------------------------------------------------------------------
/dist/lib/recover.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.recover = recover;
7 | var _recoverPublicKey = require("./recover-public-key");
8 | var _publicKey = require("./public-key");
9 | /**
10 | * returns the address with which the messageHash was signed
11 | * @param {string} sigString
12 | * @param {string} hash
13 | * @return {string} address
14 | */
15 | function recover(sigString, hash) {
16 | var pubkey = (0, _recoverPublicKey.recoverPublicKey)(sigString, hash);
17 | var address = (0, _publicKey.toAddress)(pubkey);
18 | return address;
19 | }
--------------------------------------------------------------------------------
/dist/lib/sign-transaction.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.signTransaction = signTransaction;
7 | var _tx = require("@ethereumjs/tx");
8 | var _publicKeyByPrivateKey = require("./public-key-by-private-key");
9 | var _publicKey = require("./public-key");
10 | function signTransaction(rawTx, privateKey) {
11 | var txOptions = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
12 | // check if privateKey->address matches rawTx.from
13 | var publicKey = (0, _publicKeyByPrivateKey.publicKeyByPrivateKey)(privateKey);
14 | var address = (0, _publicKey.toAddress)(publicKey);
15 | if (address != rawTx.from) throw new Error('EthCrypto.signTransaction(): rawTx.from does not match the address of the privateKey');
16 | var privateKeyBuffer = Buffer.from(privateKey.replace(/^.{2}/g, ''), 'hex');
17 | var tx = _tx.Transaction.fromTxData(rawTx, txOptions);
18 | var signedTx = tx.sign(privateKeyBuffer);
19 | var serializedTx = signedTx.serialize().toString('hex');
20 | return serializedTx;
21 | }
--------------------------------------------------------------------------------
/dist/lib/sign.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.sign = sign;
7 | var _secp256k = require("secp256k1");
8 | var _util = require("./util");
9 | /**
10 | * signs the given message
11 | * we do not use sign from eth-lib because the pure secp256k1-version is 90% faster
12 | * @param {string} privateKey
13 | * @param {string} hash
14 | * @return {string} hexString
15 | */
16 | function sign(privateKey, hash) {
17 | hash = (0, _util.addLeading0x)(hash);
18 | if (hash.length !== 66) throw new Error('EthCrypto.sign(): Can only sign hashes, given: ' + hash);
19 | var sigObj = (0, _secp256k.ecdsaSign)(new Uint8Array(Buffer.from((0, _util.removeLeading0x)(hash), 'hex')), new Uint8Array(Buffer.from((0, _util.removeLeading0x)(privateKey), 'hex')));
20 | var recoveryId = sigObj.recid === 1 ? '1c' : '1b';
21 | var newSignature = '0x' + Buffer.from(sigObj.signature).toString('hex') + recoveryId;
22 | return newSignature;
23 | }
--------------------------------------------------------------------------------
/dist/lib/tx-data-by-compiled.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4 | Object.defineProperty(exports, "__esModule", {
5 | value: true
6 | });
7 | exports.txDataByCompiled = txDataByCompiled;
8 | var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
9 | var _ethers = require("ethers");
10 | function txDataByCompiled(abi, bytecode, args) {
11 | // solc returns a string which is often passed instead of the json
12 | if (typeof abi === 'string') abi = JSON.parse(abi);
13 |
14 | // Construct a Contract Factory
15 | var factory = new _ethers.ContractFactory(abi, '0x' + bytecode);
16 | var deployTransaction = factory.getDeployTransaction.apply(factory, (0, _toConsumableArray2["default"])(args));
17 | return deployTransaction.data;
18 | }
--------------------------------------------------------------------------------
/dist/lib/util.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.addLeading0x = addLeading0x;
7 | exports.hexToUnit8Array = hexToUnit8Array;
8 | exports.removeLeading0x = removeLeading0x;
9 | exports.uint8ArrayToHex = uint8ArrayToHex;
10 | function removeLeading0x(str) {
11 | if (str.startsWith('0x')) return str.substring(2);else return str;
12 | }
13 | function addLeading0x(str) {
14 | if (!str.startsWith('0x')) return '0x' + str;else return str;
15 | }
16 | function uint8ArrayToHex(arr) {
17 | return Buffer.from(arr).toString('hex');
18 | }
19 | function hexToUnit8Array(str) {
20 | return new Uint8Array(Buffer.from(str, 'hex'));
21 | }
--------------------------------------------------------------------------------
/dist/lib/vrs.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.fromString = fromString;
7 | exports.toString = toString;
8 | var _ethers = require("ethers");
9 | /**
10 | * split signature-hex into parts
11 | * @param {string} hexString
12 | * @return {{v: string, r: string, s: string}}
13 | */
14 | function fromString(hexString) {
15 | var arr = _ethers.utils.splitSignature(hexString);
16 | return {
17 | // convert "v" to hex
18 | v: "0x".concat(arr.v.toString(16)),
19 | r: arr.r,
20 | s: arr.s
21 | };
22 | }
23 |
24 | /**
25 | * merge signature-parts to one string
26 | * @param {{v: string, r: string, s: string}} sig
27 | * @return {string} hexString
28 | */
29 | function toString(sig) {
30 | return _ethers.utils.joinSignature(sig);
31 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "eth-crypto",
3 | "version": "2.8.0",
4 | "description": "Cryptographic functions for ethereum and how to use them with web3 and solidity",
5 | "keywords": [
6 | "ethereum",
7 | "eth",
8 | "web3",
9 | "solidity",
10 | "encryption",
11 | "secp256k1",
12 | "dapp",
13 | "blockchain",
14 | "ecies",
15 | "smart-contract",
16 | "identity",
17 | "signature"
18 | ],
19 | "main": "./dist/lib/index.js",
20 | "jsnext:main": "./dist/es/index.js",
21 | "module": "./dist/es/index.js",
22 | "types": "./typings/index.d.ts",
23 | "scripts": {
24 | "test": "npm run test:node && npm run test:browser",
25 | "test:node": "npm run build && mocha ./test/index.test.js -b --timeout 6000 --exit",
26 | "test:browser": "npm run build && karma start ./config/karma.conf.js --single-run",
27 | "test:size": "npm run build && rimraf test_tmp/browserify.js && browserify --no-builtins dist/lib/browserify.index.js > test_tmp/browserify.js && uglifyjs --compress --mangle --output test_tmp/browserify.min.js -- test_tmp/browserify.js && echo \"Build-Size (minified+gzip):\" && gzip-size --raw test_tmp/browserify.min.js",
28 | "test:typings": "npm run build && mocha ./test/typings.test.js -b --timeout 12000 --exit",
29 | "test:deps": "dependency-check ./package.json --no-dev --unused --ignore-module @types/bn.js",
30 | "lint": "eslint --ignore-path .eslintignore src test config && solhint \"contracts/**/*.sol\"",
31 | "clear": "rimraf -rf ./dist && rimraf -rf ./gen && rimraf -rf ./test_tmp",
32 | "build:sol": "solidity-cli -i './contracts/*.sol' -o ./gen",
33 | "build:es6": "rimraf -rf dist/es && cross-env NODE_ENV=es6 babel src --out-dir dist/es",
34 | "build:es5": "babel src --out-dir dist/lib",
35 | "build:test": "babel test --out-dir test_tmp",
36 | "build": "npm run clear && concurrently \"npm run build:es6\" \"npm run build:es5\" \"npm run build:test\" \"npm run build:sol\"",
37 | "build:webpack": "npm run build && cross-env NODE_ENV=build webpack --config ./config/webpack.config.js",
38 | "build:size": "npm run build:webpack && echo \"Build-Size (minified+gzip):\" && gzip-size --raw ./test_tmp/webpack.bundle.js",
39 | "disc": "npm run build && cross-env NODE_ENV=disc webpack --config ./config/webpack.config.js"
40 | },
41 | "repository": {
42 | "type": "git",
43 | "url": "git+https://github.com/pubkey/eth-crypto.git"
44 | },
45 | "author": "pubkey",
46 | "funding": "https://github.com/sponsors/pubkey",
47 | "license": "MIT",
48 | "bugs": {
49 | "url": "https://github.com/pubkey/eth-crypto/issues"
50 | },
51 | "homepage": "https://github.com/pubkey/eth-crypto#readme",
52 | "devDependencies": {
53 | "@babel/cli": "7.27.2",
54 | "@babel/core": "7.27.4",
55 | "@babel/plugin-transform-runtime": "7.27.4",
56 | "@babel/preset-env": "7.27.2",
57 | "assert": "2.1.0",
58 | "async-test-util": "2.5.0",
59 | "babel-loader": "10.0.0",
60 | "bn.js": "5.2.2",
61 | "browserify": "17.0.1",
62 | "concurrently": "9.1.2",
63 | "convert-hrtime": "5.0.0",
64 | "cross-env": "7.0.3",
65 | "dependency-check": "4.1.0",
66 | "disc": "1.3.3",
67 | "eslint": "8.57.1",
68 | "ganache-cli": "6.12.2",
69 | "gzip-size-cli": "5.1.0",
70 | "is-node": "1.1.1",
71 | "js-sha3": "0.9.3",
72 | "karma": "6.4.4",
73 | "karma-babel-preprocessor": "8.0.2",
74 | "karma-browserify": "8.1.0",
75 | "karma-chrome-launcher": "3.2.0",
76 | "karma-coverage": "2.2.1",
77 | "karma-detect-browsers": "2.3.3",
78 | "karma-edge-launcher": "0.4.2",
79 | "karma-firefox-launcher": "2.1.3",
80 | "karma-ie-launcher": "1.0.0",
81 | "karma-mocha": "2.0.1",
82 | "karma-opera-launcher": "1.0.0",
83 | "karma-safari-launcher": "1.0.0",
84 | "mocha": "11.5.0",
85 | "rimraf": "4.4.1",
86 | "solhint": "5.0.5",
87 | "solidity-cli": "1.0.3",
88 | "terser-webpack-plugin": "5.3.14",
89 | "ts-node": "10.9.2",
90 | "typescript": "5.5.4",
91 | "uglify-es": "3.3.9",
92 | "web3": "1.10.4",
93 | "webpack": "5.75.0",
94 | "webpack-bundle-analyzer": "4.10.2",
95 | "webpack-cli": "5.1.4",
96 | "stream-browserify": "3.0.0",
97 | "crypto-browserify": "3.12.1"
98 | },
99 | "dependencies": {
100 | "@babel/runtime": "7.27.4",
101 | "@ethereumjs/tx": "3.5.2",
102 | "@types/bn.js": "5.1.6",
103 | "eccrypto": "1.1.6",
104 | "ethereumjs-util": "7.1.5",
105 | "ethers": "5.8.0",
106 | "secp256k1": "5.0.1"
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "config:base"
4 | ],
5 | "ignoreDeps": [
6 | "webpack"
7 | ],
8 | "statusCheckVerify": true,
9 | "automerge": true,
10 | "rebaseStalePrs": true,
11 | "prHourlyLimit": 4,
12 | "dependencyDashboard": false
13 | }
14 |
--------------------------------------------------------------------------------
/src/browserify.index.js:
--------------------------------------------------------------------------------
1 | const EthCrypto = require('./index.js');
2 |
3 | window['EthCrypto'] = EthCrypto;
--------------------------------------------------------------------------------
/src/calculate-contract-address.js:
--------------------------------------------------------------------------------
1 | import {
2 | generateAddress,
3 | toChecksumAddress,
4 | toBuffer
5 | } from 'ethereumjs-util';
6 | import {
7 | addLeading0x
8 | } from './util';
9 |
10 |
11 | export function calculateContractAddress(
12 | creatorAddress,
13 | nonce
14 | ) {
15 | const addressBuffer = generateAddress(
16 | toBuffer(addLeading0x(creatorAddress)),
17 | toBuffer(nonce)
18 | );
19 | const address = addressBuffer.toString('hex');
20 | return toChecksumAddress(addLeading0x(address));
21 | }
22 |
--------------------------------------------------------------------------------
/src/cipher.js:
--------------------------------------------------------------------------------
1 | import {
2 | compress,
3 | decompress
4 | } from './public-key';
5 |
6 | export function stringify(cipher) {
7 | if (typeof cipher === 'string') return cipher;
8 |
9 | // use compressed key because it's smaller
10 | const compressedKey = compress(cipher.ephemPublicKey);
11 |
12 | const ret = Buffer.concat([
13 | Buffer.from(cipher.iv, 'hex'), // 16bit
14 | Buffer.from(compressedKey, 'hex'), // 33bit
15 | Buffer.from(cipher.mac, 'hex'), // 32bit
16 | Buffer.from(cipher.ciphertext, 'hex') // var bit
17 | ]);
18 |
19 | return ret.toString('hex');
20 |
21 |
22 | }
23 |
24 | export function parse(str) {
25 | if (typeof str !== 'string')
26 | return str;
27 |
28 | const buf = Buffer.from(str, 'hex');
29 |
30 | const ret = {
31 | iv: buf.toString('hex', 0, 16),
32 | ephemPublicKey: buf.toString('hex', 16, 49),
33 | mac: buf.toString('hex', 49, 81),
34 | ciphertext: buf.toString('hex', 81, buf.length)
35 | };
36 |
37 | // decompress publicKey
38 | ret.ephemPublicKey = '04' + decompress(ret.ephemPublicKey);
39 |
40 | return ret;
41 | }
42 |
--------------------------------------------------------------------------------
/src/create-identity.js:
--------------------------------------------------------------------------------
1 | import { utils as ethersUtils, Wallet } from 'ethers';
2 | import { stripHexPrefix } from 'ethereumjs-util';
3 |
4 | const MIN_ENTROPY_SIZE = 128;
5 | const { keccak256 } = ethersUtils;
6 |
7 | /**
8 | * create a privateKey from the given entropy or a new one
9 | * @param {Buffer} entropy
10 | * @return {string}
11 | */
12 | export function createPrivateKey(entropy) {
13 | if (entropy) {
14 | if (!Buffer.isBuffer(entropy))
15 | throw new Error('EthCrypto.createPrivateKey(): given entropy is no Buffer');
16 | if (Buffer.byteLength(entropy, 'utf8') < MIN_ENTROPY_SIZE)
17 | throw new Error('EthCrypto.createPrivateKey(): Entropy-size must be at least ' + MIN_ENTROPY_SIZE);
18 |
19 | const outerHex = keccak256(entropy);
20 | return outerHex;
21 | } else {
22 | const innerHex = keccak256(ethersUtils.concat([ethersUtils.randomBytes(32), ethersUtils.randomBytes(32)]));
23 | const middleHex = ethersUtils.concat([ethersUtils.concat([ethersUtils.randomBytes(32), innerHex]), ethersUtils.randomBytes(32)]);
24 | const outerHex = keccak256(middleHex);
25 | return outerHex;
26 | }
27 | }
28 |
29 | /**
30 | * creates a new object with
31 | * private-, public-Key and address
32 | * @param {Buffer?} entropy if provided, will use that as single random-source
33 | */
34 | export function createIdentity(entropy) {
35 | const privateKey = createPrivateKey(entropy);
36 | const wallet = new Wallet(privateKey);
37 | const identity = {
38 | privateKey: privateKey,
39 | // remove trailing '0x04'
40 | publicKey: stripHexPrefix(wallet.publicKey).slice(2),
41 | address: wallet.address,
42 | };
43 | return identity;
44 | }
45 |
--------------------------------------------------------------------------------
/src/decrypt-with-private-key.js:
--------------------------------------------------------------------------------
1 | import {
2 | decrypt
3 | } from 'eccrypto';
4 | import {
5 | parse
6 | } from './cipher';
7 | import {
8 | removeLeading0x
9 | } from './util';
10 |
11 | export function decryptWithPrivateKey(privateKey, encrypted) {
12 |
13 | encrypted = parse(encrypted);
14 |
15 | // remove trailing '0x' from privateKey
16 | const twoStripped = removeLeading0x(privateKey);
17 |
18 | const encryptedBuffer = {
19 | iv: Buffer.from(encrypted.iv, 'hex'),
20 | ephemPublicKey: Buffer.from(encrypted.ephemPublicKey, 'hex'),
21 | ciphertext: Buffer.from(encrypted.ciphertext, 'hex'),
22 | mac: Buffer.from(encrypted.mac, 'hex')
23 | };
24 |
25 |
26 | return decrypt(
27 | Buffer.from(twoStripped, 'hex'),
28 | encryptedBuffer
29 | ).then(decryptedBuffer => decryptedBuffer.toString());
30 | }
31 |
--------------------------------------------------------------------------------
/src/encrypt-with-public-key.js:
--------------------------------------------------------------------------------
1 | import {
2 | encrypt
3 | } from 'eccrypto';
4 | import {
5 | decompress
6 | } from './public-key';
7 |
8 | export function encryptWithPublicKey(publicKey, message, opts) {
9 |
10 | // ensure its an uncompressed publicKey
11 | publicKey = decompress(publicKey);
12 |
13 | // re-add the compression-flag
14 | const pubString = '04' + publicKey;
15 |
16 |
17 | return encrypt(
18 | Buffer.from(pubString, 'hex'),
19 | Buffer.from(message),
20 | opts ? opts : {}
21 | ).then(encryptedBuffers => {
22 | const encrypted = {
23 | iv: encryptedBuffers.iv.toString('hex'),
24 | ephemPublicKey: encryptedBuffers.ephemPublicKey.toString('hex'),
25 | ciphertext: encryptedBuffers.ciphertext.toString('hex'),
26 | mac: encryptedBuffers.mac.toString('hex')
27 | };
28 | return encrypted;
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/src/hash.js:
--------------------------------------------------------------------------------
1 | import {
2 | utils as ethersUtils
3 | } from 'ethers';
4 |
5 |
6 | export function keccak256(params) {
7 | const types = [];
8 | const values = [];
9 | if (!Array.isArray(params)) {
10 | types.push('string');
11 | values.push(params);
12 | }else {
13 | params.forEach(p => {
14 | types.push(p.type);
15 | values.push(p.value);
16 | });
17 | }
18 | return ethersUtils.solidityKeccak256(types, values);
19 | }
20 |
21 | export const SIGN_PREFIX = '\x19Ethereum Signed Message:\n32';
22 |
--------------------------------------------------------------------------------
/src/hex.js:
--------------------------------------------------------------------------------
1 | /**
2 | * compress/decompress hex-strings to utf16 or base64
3 | * thx @juvian
4 | * @link https://stackoverflow.com/a/40471908/3443137
5 | */
6 |
7 | import {
8 | removeLeading0x,
9 | addLeading0x
10 | } from './util';
11 |
12 | export function compress(hex, base64 = false) {
13 | hex = removeLeading0x(hex);
14 |
15 | // if base64:true, we use our own function because it results in a smaller output
16 | if (base64 === true)
17 | return Buffer.from(hex, 'hex').toString('base64');
18 |
19 | let string = '';
20 | while (hex.length % 4 != 0) { // we need it to be multiple of 4
21 | hex = '0' + hex;
22 | }
23 | for (let i = 0; i < hex.length; i += 4) {
24 | // get char from ascii code which goes from 0 to 65536
25 | string += String.fromCharCode(parseInt(hex.substring(i, i + 4), 16));
26 | }
27 | return string;
28 | }
29 |
30 | export function decompress(compressedString, base64 = false) {
31 |
32 | // if base64:true, we use our own function because it results in a smaller output
33 | if (base64 === true) {
34 | const ret = Buffer.from(compressedString, 'base64').toString('hex');
35 | return addLeading0x(ret);
36 | }
37 |
38 | let hex = '';
39 | for (let i = 0; i < compressedString.length; i++) {
40 | // get character ascii code and convert to hexa string, adding necessary 0s
41 | hex += ((i == 0 ? '' : '000') + compressedString.charCodeAt(i).toString(16)).slice(-4);
42 | }
43 | hex = hex.toLowerCase();
44 | return addLeading0x(hex);
45 | }
46 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 |
2 | import { createIdentity } from './create-identity';
3 | import * as publicKey from './public-key';
4 | import { decryptWithPrivateKey } from './decrypt-with-private-key';
5 | import { encryptWithPublicKey } from './encrypt-with-public-key';
6 | import * as cipher from './cipher';
7 | import { publicKeyByPrivateKey } from './public-key-by-private-key';
8 | import { recover } from './recover';
9 | import { recoverPublicKey } from './recover-public-key';
10 | import { sign } from './sign';
11 | import { signTransaction } from './sign-transaction';
12 | import { txDataByCompiled } from './tx-data-by-compiled';
13 | import { calculateContractAddress } from './calculate-contract-address';
14 | import * as hash from './hash';
15 | import * as hex from './hex';
16 | import * as vrs from './vrs';
17 | import * as util from './util';
18 |
19 | export {
20 | createIdentity,
21 | publicKey,
22 | decryptWithPrivateKey,
23 | encryptWithPublicKey,
24 | cipher,
25 | publicKeyByPrivateKey,
26 | recover,
27 | recoverPublicKey,
28 | sign,
29 | signTransaction,
30 | txDataByCompiled,
31 | calculateContractAddress,
32 | hash,
33 | hex,
34 | vrs,
35 | util
36 | };
37 |
38 | export default {
39 | createIdentity,
40 | publicKey,
41 | decryptWithPrivateKey,
42 | encryptWithPublicKey,
43 | cipher,
44 | publicKeyByPrivateKey,
45 | recover,
46 | recoverPublicKey,
47 | sign,
48 | signTransaction,
49 | txDataByCompiled,
50 | calculateContractAddress,
51 | hash,
52 | hex,
53 | vrs,
54 | util
55 | };
56 |
--------------------------------------------------------------------------------
/src/public-key-by-private-key.js:
--------------------------------------------------------------------------------
1 | import {
2 | privateToPublic,
3 | toBuffer
4 | } from 'ethereumjs-util';
5 | import {
6 | addLeading0x
7 | } from './util';
8 |
9 | /**
10 | * Generate publicKey from the privateKey.
11 | * This creates the uncompressed publicKey,
12 | * where 04 has stripped from left
13 | * @returns {string}
14 | */
15 | export function publicKeyByPrivateKey(privateKey) {
16 | privateKey = addLeading0x(privateKey);
17 | const publicKeyBuffer = privateToPublic(toBuffer(privateKey));
18 | return publicKeyBuffer.toString('hex');
19 | }
20 |
--------------------------------------------------------------------------------
/src/public-key.js:
--------------------------------------------------------------------------------
1 | import {
2 | publicKeyConvert
3 | } from 'secp256k1';
4 | import {
5 | pubToAddress,
6 | toChecksumAddress,
7 | toBuffer
8 | } from 'ethereumjs-util';
9 | import {
10 | hexToUnit8Array,
11 | uint8ArrayToHex,
12 | addLeading0x
13 | } from './util';
14 |
15 | export function compress(startsWith04) {
16 |
17 | // add trailing 04 if not done before
18 | const testBuffer = Buffer.from(startsWith04, 'hex');
19 | if (testBuffer.length === 64) startsWith04 = '04' + startsWith04;
20 |
21 |
22 | return uint8ArrayToHex(publicKeyConvert(
23 | hexToUnit8Array(startsWith04),
24 | true
25 | ));
26 | }
27 |
28 | export function decompress(startsWith02Or03) {
29 |
30 | // if already decompressed an not has trailing 04
31 | const testBuffer = Buffer.from(startsWith02Or03, 'hex');
32 | if (testBuffer.length === 64) startsWith02Or03 = '04' + startsWith02Or03;
33 |
34 | let decompressed = uint8ArrayToHex(publicKeyConvert(
35 | hexToUnit8Array(startsWith02Or03),
36 | false
37 | ));
38 |
39 | // remove trailing 04
40 | decompressed = decompressed.substring(2);
41 | return decompressed;
42 | }
43 |
44 | /**
45 | * generates the ethereum-address of the publicKey
46 | * We create the checksum-address which is case-sensitive
47 | * @returns {string} address
48 | */
49 | export function toAddress(publicKey) {
50 |
51 | // normalize key
52 | publicKey = decompress(publicKey);
53 |
54 | const addressBuffer = pubToAddress(toBuffer(addLeading0x(publicKey)));
55 | const checkSumAdress = toChecksumAddress(addLeading0x(addressBuffer.toString('hex')));
56 | return checkSumAdress;
57 | }
58 |
--------------------------------------------------------------------------------
/src/recover-public-key.js:
--------------------------------------------------------------------------------
1 | import {
2 | ecdsaRecover
3 | } from 'secp256k1';
4 | import {
5 | removeLeading0x,
6 | hexToUnit8Array,
7 | uint8ArrayToHex
8 | } from './util';
9 |
10 |
11 | /**
12 | * returns the publicKey for the privateKey with which the messageHash was signed
13 | * @param {string} signature
14 | * @param {string} hash
15 | * @return {string} publicKey
16 | */
17 | export function recoverPublicKey(signature, hash) {
18 | signature = removeLeading0x(signature);
19 |
20 | // split into v-value and sig
21 | const sigOnly = signature.substring(0, signature.length - 2); // all but last 2 chars
22 | const vValue = signature.slice(-2); // last 2 chars
23 |
24 | const recoveryNumber = vValue === '1c' ? 1 : 0;
25 |
26 | let pubKey = uint8ArrayToHex(ecdsaRecover(
27 | hexToUnit8Array(sigOnly),
28 | recoveryNumber,
29 | hexToUnit8Array(removeLeading0x(hash)),
30 | false
31 | ));
32 |
33 | // remove trailing '04'
34 | pubKey = pubKey.slice(2);
35 |
36 | return pubKey;
37 | }
38 |
--------------------------------------------------------------------------------
/src/recover.js:
--------------------------------------------------------------------------------
1 | import { recoverPublicKey } from './recover-public-key';
2 | import {
3 | toAddress as addressByPublicKey
4 | } from './public-key';
5 |
6 | /**
7 | * returns the address with which the messageHash was signed
8 | * @param {string} sigString
9 | * @param {string} hash
10 | * @return {string} address
11 | */
12 | export function recover(sigString, hash) {
13 | const pubkey = recoverPublicKey(sigString, hash);
14 | const address = addressByPublicKey(pubkey);
15 | return address;
16 | }
17 |
--------------------------------------------------------------------------------
/src/sign-transaction.js:
--------------------------------------------------------------------------------
1 |
2 | import { Transaction } from '@ethereumjs/tx';
3 | import { publicKeyByPrivateKey } from './public-key-by-private-key';
4 | import {
5 | toAddress as addressByPublicKey
6 | } from './public-key';
7 |
8 | export function signTransaction(
9 | rawTx,
10 | privateKey,
11 | txOptions = {}
12 | ) {
13 |
14 | // check if privateKey->address matches rawTx.from
15 | const publicKey = publicKeyByPrivateKey(privateKey);
16 | const address = addressByPublicKey(publicKey);
17 | if (address != rawTx.from)
18 | throw new Error('EthCrypto.signTransaction(): rawTx.from does not match the address of the privateKey');
19 |
20 | const privateKeyBuffer = Buffer.from(privateKey.replace(/^.{2}/g, ''), 'hex');
21 |
22 | const tx = Transaction.fromTxData(rawTx, txOptions);
23 | const signedTx = tx.sign(privateKeyBuffer);
24 | const serializedTx = signedTx.serialize().toString('hex');
25 | return serializedTx;
26 | }
27 |
--------------------------------------------------------------------------------
/src/sign.js:
--------------------------------------------------------------------------------
1 | import {
2 | ecdsaSign as secp256k1_sign
3 | } from 'secp256k1';
4 | import {
5 | addLeading0x,
6 | removeLeading0x
7 | } from './util';
8 |
9 | /**
10 | * signs the given message
11 | * we do not use sign from eth-lib because the pure secp256k1-version is 90% faster
12 | * @param {string} privateKey
13 | * @param {string} hash
14 | * @return {string} hexString
15 | */
16 | export function sign(privateKey, hash) {
17 | hash = addLeading0x(hash);
18 | if (hash.length !== 66)
19 | throw new Error('EthCrypto.sign(): Can only sign hashes, given: ' + hash);
20 |
21 | const sigObj = secp256k1_sign(
22 | new Uint8Array(Buffer.from(removeLeading0x(hash), 'hex')),
23 | new Uint8Array(Buffer.from(removeLeading0x(privateKey), 'hex'))
24 | );
25 |
26 | const recoveryId = sigObj.recid === 1 ? '1c' : '1b';
27 |
28 | const newSignature = '0x' + Buffer.from(sigObj.signature).toString('hex') + recoveryId;
29 | return newSignature;
30 | }
31 |
--------------------------------------------------------------------------------
/src/tx-data-by-compiled.js:
--------------------------------------------------------------------------------
1 | import { ContractFactory } from 'ethers';
2 |
3 | export function txDataByCompiled(
4 | abi,
5 | bytecode,
6 | args
7 | ) {
8 | // solc returns a string which is often passed instead of the json
9 | if (typeof abi === 'string') abi = JSON.parse(abi);
10 |
11 | // Construct a Contract Factory
12 | const factory = new ContractFactory(abi, '0x' + bytecode);
13 |
14 | const deployTransaction = factory.getDeployTransaction(...args);
15 |
16 | return deployTransaction.data;
17 | }
18 |
--------------------------------------------------------------------------------
/src/util.js:
--------------------------------------------------------------------------------
1 | export function removeLeading0x(str) {
2 | if (str.startsWith('0x'))
3 | return str.substring(2);
4 | else return str;
5 | }
6 |
7 | export function addLeading0x(str) {
8 | if (!str.startsWith('0x'))
9 | return '0x' + str;
10 | else return str;
11 | }
12 |
13 | export function uint8ArrayToHex(arr) {
14 | return Buffer.from(arr).toString('hex');
15 | }
16 |
17 | export function hexToUnit8Array(str) {
18 | return new Uint8Array(Buffer.from(str, 'hex'));
19 | }
20 |
--------------------------------------------------------------------------------
/src/vrs.js:
--------------------------------------------------------------------------------
1 | import {
2 | utils as ethersUtils
3 | } from 'ethers';
4 | /**
5 | * split signature-hex into parts
6 | * @param {string} hexString
7 | * @return {{v: string, r: string, s: string}}
8 | */
9 | export function fromString(hexString) {
10 | const arr = ethersUtils.splitSignature(hexString);
11 | return {
12 | // convert "v" to hex
13 | v: `0x${arr.v.toString(16)}`,
14 | r: arr.r,
15 | s: arr.s,
16 | };
17 | }
18 |
19 | /**
20 | * merge signature-parts to one string
21 | * @param {{v: string, r: string, s: string}} sig
22 | * @return {string} hexString
23 | */
24 | export function toString(sig) {
25 | return ethersUtils.joinSignature(sig);
26 | }
27 |
--------------------------------------------------------------------------------
/test/.eslintrc:
--------------------------------------------------------------------------------
1 | env:
2 | node: true
3 | mocha: true
--------------------------------------------------------------------------------
/test/bug-template.test.js:
--------------------------------------------------------------------------------
1 | const AsyncTestUtil = require('async-test-util');
2 | const assert = require('assert');
3 | const EthCrypto = require('../dist/lib/index');
4 |
5 | /**
6 | * If you have found a bug, edit this test to reproduce it
7 | * You can run it with: 'npm run test:node'
8 | * If you have successfully reproduced it, make a pull request with this file
9 | */
10 | describe('bug-template.test.js', () => {
11 | it('should reproduce the bug', () => {
12 | const testData = {
13 | address: '0x3f243FdacE01Cfd9719f7359c94BA11361f32471',
14 | privateKey: '0x107be946709e41b7895eea9f2dacf998a0a9124acbb786f0fd1a826101581a07',
15 | publicKey: 'bf1cc3154424dc22191941d9f4f50b063a2b663a2337e5548abea633c1d06eceacf2b81dd326d278cd992d5e03b0df140f2df389ac9a1c2415a220a4a9e8c046'
16 | };
17 |
18 | // do things with eth-crypto
19 | const message = AsyncTestUtil.randomString(12);
20 | const messageHash = EthCrypto.hash.keccak256(message);
21 | const signature = EthCrypto.sign(testData.privateKey, messageHash);
22 |
23 | // assert things that should be ok
24 | assert.equal(typeof signature, 'string');
25 | assert.ok(signature.length > 10);
26 | });
27 | });
--------------------------------------------------------------------------------
/test/index.test.js:
--------------------------------------------------------------------------------
1 |
2 | require('./unit.test');
3 | require('./integration.test');
4 |
5 | require('./bug-template.test');
6 | require('./issues.test');
7 |
8 | // tutorials
9 | require('./tutorials/signed-data.test');
10 | require('./tutorials/encrypted-message.test');
11 |
12 | require('./performance.test');
13 |
--------------------------------------------------------------------------------
/test/integration.test.js:
--------------------------------------------------------------------------------
1 | const ganache = require('ganache-cli');
2 | const Web3 = require('web3');
3 | const path = require('path');
4 | const AsyncTestUtil = require('async-test-util');
5 | const SolidityCli = require('solidity-cli');
6 | const assert = require('assert');
7 | const BN = require('bn.js');
8 | const EthCrypto = require('../dist/lib/index');
9 | // const web3 = EthCrypto.util.web3;
10 |
11 | describe('integration.test.js', () => {
12 | /**
13 | * we have to set ''http://'' because:
14 | * @link https://github.com/ethereum/web3.js/issues/2786#issuecomment-490161182
15 | */
16 | const WEB3_DEFAULT_PROVIDER = 'http://';
17 | const WEB3_CONFIRMATION_BLOCKS = 1;
18 |
19 | const state = {
20 | web3: null,
21 | accounts: []
22 | };
23 | describe('init', () => {
24 | it('compiled contract', async function () {
25 | this.timeout(30 * 1000);
26 | const contractPath = path.join(__dirname, '../contracts/TestContract.sol');
27 | const compiled = await SolidityCli.compileFile(contractPath);
28 | state.compiled = compiled[':TestContract'];
29 | });
30 | it('create web3', () => {
31 | /**
32 | * we have to set ''http://'' because:
33 | * @link https://github.com/ethereum/web3.js/issues/2786#issuecomment-490161182
34 | */
35 | state.web3 = new Web3(WEB3_DEFAULT_PROVIDER);
36 | state.web3.transactionConfirmationBlocks = WEB3_CONFIRMATION_BLOCKS;
37 | });
38 | it('create testnet', async () => {
39 | // create accounts
40 | const ganacheAccounts = new Array(20)
41 | .fill(0)
42 | .map(() => EthCrypto.createIdentity())
43 | .map(identity => {
44 | state.accounts.push(identity);
45 | const twoStripped = identity.privateKey.replace(/^.{2}/g, '');
46 | return {
47 | secretKey: Buffer.from(twoStripped, 'hex'),
48 | balance: state.web3.utils.toWei('100', 'ether')
49 | };
50 | });
51 | state.web3.setProvider(ganache.provider({
52 | accounts: ganacheAccounts
53 | }));
54 | });
55 | it('deploy test-contract', async () => {
56 | const account = state.accounts.pop();
57 | const gasPrice = await state.web3.eth.getGasPrice();
58 |
59 | const rawTx = {
60 | from: account.address,
61 | gasPrice: parseInt(gasPrice),
62 | data: state.compiled.bytecode
63 | };
64 | const estimateGas = await state.web3.eth.estimateGas(rawTx);
65 | rawTx.gasLimit = estimateGas * 5;
66 |
67 | const receipt = await state.web3.eth.sendTransaction(rawTx);
68 | state.contractAddress = receipt.contractAddress;
69 | assert.ok(state.contractAddress);
70 | });
71 | it('create contract-instance', async () => {
72 | state.contract = new state.web3.eth.Contract(
73 | JSON.parse(state.compiled.interface),
74 | state.contractAddress
75 | );
76 | assert.ok(state.contract);
77 | });
78 | it('should get the public value out of the contract', async () => {
79 | const value = await state.contract.methods.onePublicValue().call();
80 | assert.equal(value, 1337);
81 | });
82 | });
83 | describe('privateKey', () => {
84 | it('should be possible to use the keys with ganache', async () => {
85 | const web3 = new Web3(WEB3_DEFAULT_PROVIDER);
86 | web3.transactionConfirmationBlocks = WEB3_CONFIRMATION_BLOCKS;
87 | const ganacheAccounts = new Array(10)
88 | .fill(0)
89 | .map(() => EthCrypto.createIdentity())
90 | .map(identity => ({
91 | secretKey: Buffer.from(identity.privateKey.replace(/^.{2}/g, ''), 'hex'),
92 | balance: web3.utils.toWei('100', 'ether')
93 | }));
94 | web3.setProvider(ganache.provider({
95 | accounts: ganacheAccounts
96 | }));
97 | });
98 | it('should be possible to sign transaction with the key', async () => {
99 | const identity = EthCrypto.createIdentity();
100 |
101 | const web3 = new Web3(WEB3_DEFAULT_PROVIDER);
102 | web3.transactionConfirmationBlocks = WEB3_CONFIRMATION_BLOCKS;
103 | web3.setProvider(ganache.provider({
104 | accounts: [{
105 | secretKey: Buffer.from(identity.privateKey.replace(/^.{2}/g, ''), 'hex'),
106 | balance: web3.utils.toWei('100', 'ether')
107 | }]
108 | }));
109 | const gasPrice = await web3.eth.getGasPrice();
110 | const rawTx = {
111 | from: identity.address,
112 | to: '0x63dcee1fd1d814858acd4172bb20e1aa0c947c0a',
113 | value: new BN(web3.utils.toWei('1', 'ether')),
114 | nonce: 0,
115 | gasLimit: 60000,
116 | gasPrice: parseInt(gasPrice)
117 | };
118 | const serializedTx = EthCrypto.signTransaction(
119 | rawTx,
120 | identity.privateKey
121 | );
122 | const receipt = await web3.eth.sendSignedTransaction(serializedTx);
123 | assert.equal(receipt.blockNumber, 1);
124 | assert.equal(receipt.status, 1);
125 | });
126 | });
127 | describe('hash', () => {
128 | describe('.keccak256()', () => {
129 | it('number: should create the same hash as solidity', async () => {
130 | const nr = 1337;
131 | const solHash = await state.contract
132 | .methods.hashNumber(nr)
133 | .call();
134 |
135 | const jsHash = EthCrypto.hash.keccak256([{
136 | type: 'uint256',
137 | value: nr
138 | }]);
139 | assert.equal(solHash, jsHash);
140 | });
141 | it('string: should create the same hash as solidity', async () => {
142 | const str = 'foobar';
143 | const jsHash = EthCrypto.hash.keccak256([{
144 | type: 'string',
145 | value: str
146 | }]);
147 | const solHash = await state.contract
148 | .methods.hashString(str)
149 | .call();
150 | assert.equal(jsHash, solHash);
151 | });
152 | it('multi: shoud create same hash as solidity', async () => {
153 | const str = 'foobar';
154 | const bool = false;
155 | const uint = 23453;
156 | const jsHash = EthCrypto.hash
157 | .keccak256([{
158 | type: 'string',
159 | value: str
160 | }, {
161 | type: 'uint256',
162 | value: uint
163 | }, {
164 | type: 'bool',
165 | value: bool
166 | }]);
167 | const solHash = await state.contract
168 | .methods.hashMulti(
169 | str,
170 | uint,
171 | bool
172 | )
173 | .call();
174 | assert.equal(jsHash, solHash);
175 | });
176 | });
177 | describe('.prefixedHash()', () => {
178 | return; // TODO
179 | /* it('should create the same hash as web3.accounts.sign()', async () => {
180 | const ident = EthCrypto.createIdentity();
181 | const str = 'foobar';
182 | const hash = EthCrypto.hash.keccak256([{
183 | type: 'string',
184 | value: str
185 | }]);
186 | console.log('hash: ' + hash);
187 | const account = web3.eth.accounts.privateKeyToAccount(ident.privateKey);
188 | const sig = account.sign({
189 | type: 'bytes32',
190 | value: hash
191 | });
192 | const jsHash = EthCrypto.hash.prefixedHash(hash);
193 | assert.equal(jsHash, sig.messageHash);
194 | });
195 | it('should be possible to create the same prefixed hash in solidity', async () => {
196 | const str = 'foobar';
197 | const hash = EthCrypto.hash.keccak256([{
198 | type: 'string',
199 | value: str
200 | }]);
201 |
202 | console.log('hash: ' + hash);
203 |
204 | const jsHash = EthCrypto.hash.prefixedHash(hash);
205 | console.log('prefixedHash: ' + jsHash);
206 |
207 | const hash2 = EthCrypto.hash.keccak256([{
208 | type: 'string',
209 | value: '\x19Ethereum Signed Message:\n32'
210 | }, {
211 | type: 'bytes32',
212 | value: hash
213 | }]);
214 | console.log('keccak256: ' + hash2);
215 |
216 | const solHash = await state.contract
217 | .methods
218 | .signHashLikeWeb3Sign(hash)
219 | .call();
220 | console.log('= solHash: ' + solHash);
221 | assert.equal(jsHash, solHash);
222 | });*/
223 | });
224 | });
225 | describe('sign', () => {
226 | it('should validate the signature on solidity', async () => {
227 | const ident = EthCrypto.createIdentity();
228 | const message = AsyncTestUtil.randomString(12);
229 | const messageHash = EthCrypto.hash.keccak256([{
230 | type: 'string',
231 | value: message
232 | }]);
233 |
234 | const signature = await EthCrypto.sign(
235 | ident.privateKey,
236 | messageHash
237 | );
238 | const jsSigner = EthCrypto.recover(signature, messageHash);
239 | assert.equal(jsSigner, ident.address);
240 | const vrs = EthCrypto.vrs.fromString(signature);
241 | const solSigner = await state.contract
242 | .methods.recoverSignature(
243 | messageHash,
244 | vrs.v,
245 | vrs.r,
246 | vrs.s
247 | )
248 | .call();
249 | assert.equal(solSigner, ident.address);
250 | });
251 | it('should validate with the message instead of the hash', async () => {
252 | const ident = EthCrypto.createIdentity();
253 | const message = 'foobar';
254 | const messageHash = EthCrypto.hash.keccak256([{
255 | type: 'string',
256 | value: message
257 | }]);
258 | const signature = await EthCrypto.sign(
259 | ident.privateKey,
260 | messageHash
261 | );
262 | const vrs = EthCrypto.vrs.fromString(signature);
263 | const solSigner = await state.contract
264 | .methods.recoverSignatureFromMessage(
265 | message,
266 | vrs.v,
267 | vrs.r,
268 | vrs.s
269 | )
270 | .call();
271 | assert.equal(solSigner, ident.address);
272 | });
273 | });
274 | describe('.calculateContractAddress()', () => {
275 | it('should calculate the correct address', async () => {
276 | const account = state.accounts.pop();
277 | const gasPrice = await state.web3.eth.getGasPrice();
278 |
279 | const calculatedAddress = EthCrypto.calculateContractAddress(
280 | account.address,
281 | 0
282 | );
283 |
284 | const rawTx = {
285 | from: account.address,
286 | gasPrice: parseInt(gasPrice),
287 | data: state.compiled.bytecode
288 | };
289 | const estimateGas = await state.web3.eth.estimateGas(rawTx);
290 | rawTx.gasLimit = estimateGas * 5;
291 |
292 | const receipt = await state.web3.eth.sendTransaction(rawTx);
293 | assert.equal(receipt.contractAddress, calculatedAddress);
294 | });
295 | it('should also work with higher nonce', async () => {
296 | const account = state.accounts.pop();
297 | const account2 = state.accounts.pop();
298 | const gasPrice = await state.web3.eth.getGasPrice();
299 |
300 | // send 3 transactions
301 | await Promise.all(
302 | new Array(3)
303 | .fill(0)
304 | .map(async () => {
305 | const rawTx = {
306 | from: account.address,
307 | to: account2.address,
308 | gasPrice: parseInt(gasPrice),
309 | value: 1
310 | };
311 | const estimateGas = await state.web3.eth.estimateGas(rawTx);
312 | rawTx.gasLimit = estimateGas * 2;
313 | await state.web3.eth.sendTransaction(rawTx);
314 | })
315 | );
316 |
317 | const calculatedAddress = EthCrypto.calculateContractAddress(
318 | account.address,
319 | 3
320 | );
321 |
322 | const rawTx = {
323 | from: account.address,
324 | gasPrice: parseInt(gasPrice),
325 | nonce: 3,
326 | data: state.compiled.bytecode
327 | };
328 | const estimateGas = await state.web3.eth.estimateGas(rawTx);
329 | rawTx.gasLimit = estimateGas * 5;
330 |
331 | const receipt = await state.web3.eth.sendTransaction(rawTx);
332 | assert.equal(receipt.contractAddress, calculatedAddress);
333 | });
334 | });
335 | });
336 |
--------------------------------------------------------------------------------
/test/issues.test.js:
--------------------------------------------------------------------------------
1 | // const AsyncTestUtil = require('async-test-util');
2 | const assert = require('assert');
3 | const EthCrypto = require('../dist/lib/index');
4 | const crypto = require('crypto');
5 |
6 | describe('issues.test.js', () => {
7 | it('#3 Error in recover', async () => {
8 | const payload = {
9 | data: 'something',
10 | val: 5,
11 | other: 'something else'
12 | };
13 | const msgHash = EthCrypto.hash.keccak256(JSON.stringify(payload));
14 | const ident = EthCrypto.createIdentity();
15 |
16 | const sig = EthCrypto.sign(
17 | ident.privateKey, // privateKey
18 | msgHash // hash of message
19 | );
20 | assert.ok(sig);
21 | assert.ok(sig.startsWith('0x'));
22 |
23 | const recAddress = EthCrypto.recover(
24 | sig,
25 | EthCrypto.hash.keccak256(JSON.stringify(payload)) // signed message hash
26 | );
27 | assert.equal(recAddress, ident.address);
28 |
29 | const recKey = EthCrypto.recoverPublicKey(
30 | sig,
31 | EthCrypto.hash.keccak256(JSON.stringify(payload)) // signed message hash
32 | );
33 | assert.equal(recKey, ident.publicKey);
34 | });
35 | it('#3 issuecommet-387616789', () => {
36 | const message = 'foobar';
37 | const messageHash = EthCrypto.hash.keccak256(message);
38 | const signature = EthCrypto.sign(
39 | '0x107be946709e41b7895eea9f2dacf998a0a9124acbb786f0fd1a826101581a07', // privateKey
40 | messageHash // hash of message
41 | );
42 | const signer = EthCrypto.recover(
43 | signature,
44 | messageHash
45 | );
46 | assert.ok(signer);
47 | });
48 | it('#47 cannot encrypt/decrypt with more then 16 chars message', async () => {
49 | const ident = EthCrypto.createIdentity();
50 |
51 | const message = crypto.randomBytes(6).toString('hex');
52 | const challenge = await EthCrypto.encryptWithPublicKey(
53 | ident.publicKey,
54 | Buffer.from(message)
55 | );
56 | const answer = await EthCrypto.decryptWithPrivateKey(
57 | ident.privateKey,
58 | challenge
59 | );
60 | assert.deepEqual(message, answer);
61 | });
62 | });
63 |
--------------------------------------------------------------------------------
/test/karma.test.js:
--------------------------------------------------------------------------------
1 | require('./unit.test');
2 | require('./issues.test');
3 | require('./performance.test');
4 |
--------------------------------------------------------------------------------
/test/performance.test.js:
--------------------------------------------------------------------------------
1 | const AsyncTestUtil = require('async-test-util');
2 | const EthCrypto = require('../dist/lib/index');
3 |
4 | const benchmark = {
5 | sign: {},
6 | recoverPublicKey: {},
7 | encryptWithPublicKey: {},
8 | decryptWithPrivateKey: {}
9 | };
10 |
11 | const nowTime = () => {
12 | return AsyncTestUtil.performanceNow();
13 | };
14 |
15 | const elapsedTime = before => {
16 | return AsyncTestUtil.performanceNow() - before;
17 | };
18 |
19 | describe('performance.test.js', () => {
20 | describe('.sign()', () => {
21 | it('sameKey', async () => {
22 | // prepare
23 | const identity = EthCrypto.createIdentity();
24 | const runs = 200;
25 | const hashes = new Array(runs)
26 | .fill(0)
27 | .map(() => AsyncTestUtil.randomString(12))
28 | .map(s => EthCrypto.hash.keccak256(s).replace(/^.{2}/g, ''));
29 |
30 | // run
31 | const startTime = nowTime();
32 | for (let i = 0; i < runs; i++) {
33 | const hash = hashes.pop();
34 | EthCrypto.sign(
35 | identity.privateKey,
36 | hash
37 | );
38 | }
39 |
40 | const elapsed = elapsedTime(startTime);
41 | benchmark.sign.sameKey = elapsed;
42 | });
43 | it('otherKey', async () => {
44 | // prepare
45 | const runs = 300;
46 | const hashes = new Array(runs)
47 | .fill(0)
48 | .map(() => AsyncTestUtil.randomString(12))
49 | .map(s => EthCrypto.hash.keccak256(s).replace(/^.{2}/g, ''));
50 | const keys = new Array(runs)
51 | .fill(0)
52 | .map(() => EthCrypto.createIdentity().privateKey);
53 |
54 | // run
55 | const startTime = nowTime();
56 | for (let i = 0; i < runs; i++) {
57 | const hash = hashes.pop();
58 | const key = keys.pop();
59 | EthCrypto.sign(
60 | key,
61 | hash
62 | );
63 | }
64 |
65 | const elapsed = elapsedTime(startTime);
66 | benchmark.sign.otherKey = elapsed;
67 | });
68 | });
69 | describe('.recoverPublicKey()', () => {
70 | it('run', async () => {
71 | // prepare
72 | const identity = EthCrypto.createIdentity();
73 | const runs = 200;
74 | const hashes = new Array(runs)
75 | .fill(0)
76 | .map(() => AsyncTestUtil.randomString(12))
77 | .map(s => EthCrypto.hash.keccak256(s).replace(/^.{2}/g, ''));
78 |
79 | const signatures = hashes.map(hash => EthCrypto.sign(
80 | identity.privateKey,
81 | hash
82 | ));
83 |
84 | // run
85 | const startTime = nowTime();
86 | for (let i = 0; i < runs; i++) {
87 | const sig = signatures.pop();
88 | const hash = hashes.pop();
89 | EthCrypto.recoverPublicKey(
90 | sig,
91 | hash
92 | );
93 | }
94 |
95 | const elapsed = elapsedTime(startTime);
96 | benchmark.recoverPublicKey.sameKey = elapsed;
97 | });
98 | });
99 | describe('.encryptWithPublicKey()', () => {
100 | it('sameKey', async () => {
101 | // prepare
102 | const identity = EthCrypto.createIdentity();
103 |
104 | const runs = 200;
105 | const hashes = new Array(runs)
106 | .fill(0)
107 | .map(() => AsyncTestUtil.randomString(12))
108 | .map(s => EthCrypto.hash.keccak256(s));
109 |
110 | // run
111 | const startTime = nowTime();
112 |
113 | await Promise.all(
114 | new Array(runs)
115 | .fill(0)
116 | .map(async () => {
117 | const hash = hashes.pop();
118 | await EthCrypto.encryptWithPublicKey(
119 | identity.publicKey,
120 | hash
121 | );
122 | })
123 | );
124 |
125 | const elapsed = elapsedTime(startTime);
126 | benchmark.encryptWithPublicKey.sameKey = elapsed;
127 | });
128 | it('otherKey', async () => {
129 | // prepare
130 | const runs = 200;
131 | const hashes = new Array(runs)
132 | .fill(0)
133 | .map(() => AsyncTestUtil.randomString(12))
134 | .map(s => EthCrypto.hash.keccak256(s));
135 | const keys = new Array(runs)
136 | .fill(0)
137 | .map(() => EthCrypto.createIdentity().publicKey);
138 |
139 | // run
140 | const startTime = nowTime();
141 | await Promise.all(
142 | new Array(runs)
143 | .fill(0)
144 | .map(async () => {
145 | const hash = hashes.pop();
146 | const publicKey = keys.pop();
147 | await EthCrypto.encryptWithPublicKey(
148 | publicKey,
149 | hash
150 | );
151 | })
152 | );
153 |
154 | const elapsed = elapsedTime(startTime);
155 | benchmark.encryptWithPublicKey.otherKey = elapsed;
156 | });
157 | });
158 | describe('.decryptWithPrivateKey()', () => {
159 | it('sameKey', async () => {
160 | // prepare
161 | const identity = EthCrypto.createIdentity();
162 |
163 | const runs = 200;
164 | const hashes = await Promise.all(
165 | new Array(runs)
166 | .fill(0)
167 | .map(() => AsyncTestUtil.randomString(12))
168 | .map(s => EthCrypto.hash.keccak256(s))
169 | .map(async (h) => EthCrypto.encryptWithPublicKey(
170 | identity.publicKey,
171 | h
172 | ))
173 | );
174 |
175 | // run
176 | const startTime = nowTime();
177 | await Promise.all(
178 | new Array(runs)
179 | .fill(0)
180 | .map(async () => {
181 | const encrypted = hashes.pop();
182 | await EthCrypto.decryptWithPrivateKey(
183 | identity.privateKey,
184 | encrypted
185 | );
186 |
187 | })
188 | );
189 |
190 | const elapsed = elapsedTime(startTime);
191 | benchmark.decryptWithPrivateKey.sameKey = elapsed;
192 | });
193 | });
194 | describe('show', () => {
195 | it('show result', () => {
196 | console.log('benchmark result:');
197 | console.log(JSON.stringify(benchmark, null, 2));
198 | });
199 | });
200 | });
201 |
--------------------------------------------------------------------------------
/test/tutorials/encrypted-message.test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * at this tests, we run the code which is used in the tutorials
3 | * to ensure they work as expected
4 | */
5 | const assert = require('assert');
6 | const EthCrypto = require('../../dist/lib/index');
7 |
8 | describe('encrypted-message.md', () => {
9 | it('run', async () => {
10 | const alice = EthCrypto.createIdentity();
11 | const bob = EthCrypto.createIdentity();
12 |
13 | const secretMessage = 'My name is Satoshi Buterin';
14 | const signature = EthCrypto.sign(
15 | alice.privateKey,
16 | EthCrypto.hash.keccak256(secretMessage)
17 | );
18 |
19 | const payload = {
20 | message: secretMessage,
21 | signature
22 | };
23 | const encrypted = await EthCrypto.encryptWithPublicKey(
24 | bob.publicKey,
25 | JSON.stringify(payload)
26 | );
27 | // console.log('encrypted:');
28 | // console.dir(encrypted);
29 |
30 | const encryptedString = EthCrypto.cipher.stringify(encrypted);
31 |
32 | // decrypt
33 | const encryptedObject = EthCrypto.cipher.parse(encryptedString);
34 | const decrypted = await EthCrypto.decryptWithPrivateKey(
35 | bob.privateKey,
36 | encryptedObject
37 | );
38 | const decryptedPayload = JSON.parse(decrypted);
39 |
40 | // check signature
41 | const senderAddress = EthCrypto.recover(
42 | decryptedPayload.signature,
43 | EthCrypto.hash.keccak256(decryptedPayload.message)
44 | );
45 |
46 | assert.equal(senderAddress, alice.address);
47 | assert.equal(decryptedPayload.message, secretMessage);
48 |
49 | // answer
50 | const answerMessage = 'Roger dad';
51 | const answerSignature = EthCrypto.sign(
52 | bob.privateKey,
53 | EthCrypto.hash.keccak256(answerMessage)
54 | );
55 | const answerPayload = {
56 | message: answerMessage,
57 | signature: answerSignature
58 | };
59 |
60 | const alicePublicKey = EthCrypto.recoverPublicKey(
61 | decryptedPayload.signature,
62 | EthCrypto.hash.keccak256(payload.message)
63 | );
64 |
65 | assert.equal(alicePublicKey, alice.publicKey);
66 |
67 | const encryptedAnswer = await EthCrypto.encryptWithPublicKey(
68 | alicePublicKey,
69 | JSON.stringify(answerPayload)
70 | );
71 | assert.ok(encryptedAnswer);
72 | });
73 | });
74 |
--------------------------------------------------------------------------------
/test/tutorials/signed-data.test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * at this tests, we run the code which is used in the tutorials
3 | * to ensure they work as expected
4 | */
5 |
6 | const ganache = require('ganache-cli');
7 | const Web3 = require('web3');
8 | const assert = require('assert');
9 | const BN = require('bn.js');
10 | const EthCrypto = require('../../dist/lib/index');
11 |
12 | describe('signed-data.md', () => {
13 | it('all', async function() {
14 | this.timeout(12000);
15 | const creatorIdentity = EthCrypto.createIdentity();
16 | const recieverIdentity = EthCrypto.createIdentity();
17 |
18 | const web3 = new Web3('http://');
19 | web3.transactionConfirmationBlocks = 1;
20 |
21 | const ganacheProvider = ganache.provider({
22 | accounts: [
23 | // we preset the balance of our identity to 10 ether
24 | {
25 | secretKey: creatorIdentity.privateKey,
26 | balance: web3.utils.toWei('10', 'ether')
27 | },
28 | // we also give some wei to the recieverIdentity
29 | // so it can send transaction to the chain
30 | {
31 | secretKey: recieverIdentity.privateKey,
32 | balance: web3.utils.toWei('1', 'ether')
33 | }
34 | ]
35 | });
36 | web3.setProvider(ganacheProvider);
37 |
38 | const path = require('path');
39 | const SolidityCli = require('solidity-cli');
40 | const contractPath = path.join(__dirname, '../../contracts/DonationBag.sol');
41 | const compiled = await SolidityCli.compileFile(contractPath);
42 | const compiledDonationBag = compiled[':DonationBag'];
43 |
44 | const createCode = EthCrypto.txDataByCompiled(
45 | JSON.parse(compiledDonationBag.interface), // abi
46 | compiledDonationBag.bytecode, // bytecode
47 | [creatorIdentity.address] // constructor-arguments
48 | );
49 |
50 | // create create-tx
51 | const rawTx = {
52 | from: creatorIdentity.address,
53 | nonce: 0,
54 | gasLimit: 5000000,
55 | gasPrice: 5000000000,
56 | data: createCode
57 | };
58 | const serializedTx = EthCrypto.signTransaction(
59 | rawTx,
60 | creatorIdentity.privateKey
61 | );
62 |
63 | // submit
64 | const receipt = await web3.eth.sendSignedTransaction(serializedTx);
65 | const contractAddress = receipt.contractAddress;
66 | // console.log('contractAddress: ' + contractAddress);
67 | // console.log('creator address: ' + creatorIdentity.address);
68 |
69 | assert.ok(receipt.contractAddress);
70 | assert.equal(receipt.status, 1);
71 |
72 | // create contract instance
73 | // console.log('# create contract instance');
74 | const contractInstance = new web3.eth.Contract(
75 | JSON.parse(compiledDonationBag.interface),
76 | contractAddress
77 | );
78 |
79 | // check owner
80 | // console.log('# check owner');
81 | const owner = await contractInstance.methods.owner().call();
82 | assert.equal(owner, creatorIdentity.address);
83 |
84 | // send value
85 | // console.log('#send value:');
86 | const rawTx2 = {
87 | from: creatorIdentity.address,
88 | to: contractAddress,
89 | nonce: 1,
90 | value: new BN(web3.utils.toWei('3', 'ether')),
91 | gasLimit: 600000,
92 | gasPrice: 20000000000
93 | };
94 | const serializedTx2 = EthCrypto.signTransaction(
95 | rawTx2,
96 | creatorIdentity.privateKey
97 | );
98 | await web3.eth.sendSignedTransaction(serializedTx2);
99 |
100 | // check balance
101 | const balance = await contractInstance.methods.getBalance().call();
102 | assert.equal(balance, web3.utils.toWei('3', 'ether'));
103 |
104 | // check prefixedHash
105 | const solHash = await contractInstance
106 | .methods
107 | .prefixedHash(recieverIdentity.address)
108 | .call();
109 | // console.log('solHash: ' + solHash);
110 |
111 | // sign message
112 | const signHash = EthCrypto.hash.keccak256([{
113 | type: 'string',
114 | value: 'Signed for DonationBag:'
115 | }, {
116 | type: 'address',
117 | value: contractAddress
118 | }, {
119 | type: 'address',
120 | value: recieverIdentity.address
121 | }]);
122 | assert.equal(signHash, solHash);
123 |
124 | const signature = EthCrypto.sign(
125 | creatorIdentity.privateKey,
126 | signHash
127 | );
128 | const vrs = EthCrypto.vrs.fromString(signature);
129 | const isValid = await contractInstance
130 | .methods.isSignatureValid(
131 | recieverIdentity.address,
132 | vrs.v,
133 | vrs.r,
134 | vrs.s
135 | ).call();
136 | assert.ok(isValid);
137 |
138 | // claim donation by receiver
139 | const recieveCode = contractInstance
140 | .methods.recieveDonation(
141 | vrs.v,
142 | vrs.r,
143 | vrs.s
144 | ).encodeABI();
145 | const recieveTx = {
146 | from: recieverIdentity.address,
147 | to: contractAddress,
148 | nonce: 0,
149 | gasLimit: 5000000,
150 | gasPrice: 5000000000,
151 | data: recieveCode
152 | };
153 | const serializedRecieveTx = EthCrypto.signTransaction(
154 | recieveTx,
155 | recieverIdentity.privateKey
156 | );
157 | await web3.eth.sendSignedTransaction(serializedRecieveTx);
158 |
159 | // check receiver-balance
160 | const receiverBalance = await web3.eth.getBalance(recieverIdentity.address);
161 | // 1999802840000000000
162 | assert.ok(parseInt(receiverBalance) > parseInt(web3.utils.toWei('1', 'ether')));
163 | });
164 | });
165 |
--------------------------------------------------------------------------------
/test/typings.test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * checks if the typings are correct
3 | * run via 'npm run test:typings'
4 | */
5 | const assert = require('assert');
6 | assert.ok(true);
7 | const path = require('path');
8 | const AsyncTestUtil = require('async-test-util');
9 |
10 | describe('typings.test.ts', () => {
11 | const mainPath = path.join(__dirname, '../');
12 | const codeBase = `
13 | import EthCrypto from '${mainPath}';
14 | import * as EthCryptoAll from '${mainPath}';
15 | `;
16 | const transpileCode = async (code) => {
17 | const spawn = require('child-process-promise').spawn;
18 | const stdout = [];
19 | const stderr = [];
20 | const promise = spawn('ts-node', [
21 | '--compiler-options', '{"target":"es6", "strict": true}',
22 | '-e', codeBase + '\n' + code
23 | ]);
24 | const childProcess = promise.childProcess;
25 | childProcess.stdout.on('data', data => stdout.push(data.toString()));
26 | childProcess.stderr.on('data', data => stderr.push(data.toString()));
27 | try {
28 | await promise;
29 | } catch (err) {
30 | throw new Error(`could not run
31 | # Error: ${err}
32 | # Output: ${stdout}
33 | # ErrOut: ${stderr}
34 | `);
35 | }
36 | };
37 | describe('basic', () => {
38 | it('should sucess on basic test', async () => {
39 | await transpileCode('console.log("Hello, world!")');
40 | });
41 | it('should fail on broken code', async () => {
42 | const brokenCode = `
43 | let x: string = 'foo';
44 | x = 1337;
45 | `;
46 | await AsyncTestUtil.assertThrows(
47 | () => transpileCode(brokenCode)
48 | );
49 | });
50 | });
51 | describe('statics', () => {
52 | describe('.createIdentity()', () => {
53 | it('usage', async () => {
54 | const code = `
55 | (async()=>{
56 | const ident = EthCrypto.createIdentity();
57 | const privKey: string = ident.privateKey;
58 |
59 | const ident2 = EthCryptoAll.createIdentity();
60 | const privKey2: string = ident2.privateKey;
61 | })();
62 | `;
63 | await transpileCode(code);
64 | });
65 | it('EthCryptoAll.createIdentity() fail on wrong access', async () => {
66 | const code = `
67 | (async()=>{
68 | const ident = EthCryptoAll.createIdentity();
69 | const privKey: string = ident.privateKeyFoobar;
70 | })();
71 | `;
72 | await AsyncTestUtil.assertThrows(
73 | () => transpileCode(code)
74 | );
75 | });
76 | it('EthCrypto.createIdentity() fail on wrong access', async () => {
77 | const code = `
78 | (async()=>{
79 | const ident = EthCrypto.createIdentity();
80 | const privKey: string = ident.privateKeyFoobar;
81 | })();
82 | `;
83 | await AsyncTestUtil.assertThrows(
84 | () => transpileCode(code)
85 | );
86 | });
87 | });
88 | describe('.publicKey.compress()', () => {
89 | it('usage', async () => {
90 | const code = `
91 | (async()=>{
92 | const ident = EthCrypto.createIdentity();
93 | const pub1: string = EthCrypto.publicKey.compress(ident.publicKey);
94 | const pub2: string = EthCryptoAll.publicKey.compress(ident.publicKey);
95 | })();
96 | `;
97 | await transpileCode(code);
98 | });
99 | });
100 | describe('rawTx', () => {
101 | /**
102 | * @link https://github.com/pubkey/eth-crypto/issues/20
103 | */
104 | it('#20 should need a nonce for a RawTx', async () => {
105 | const code = `
106 | (async()=>{
107 | const rawTx: EthCryptoAll.RawTx = {
108 | from: '0xfoobar',
109 | to: '0xfoobar',
110 | value: 10,
111 | gasLimit: 10,
112 | gasPrice: 10,
113 | nonce: 20
114 | };
115 | })();
116 | `;
117 | await transpileCode(code);
118 |
119 | const badCodeWithoutNonce = `
120 | (async()=>{
121 | const rawTx: EthCryptoAll.RawTx = {
122 | from: '0xfoobar',
123 | to: '0xfoobar',
124 | value: 10,
125 | gasLimit: 10,
126 | gasPrice: 10
127 | };
128 | })();
129 | `;
130 | await AsyncTestUtil.assertThrows(
131 | () => transpileCode(badCodeWithoutNonce),
132 | Error,
133 | 'nonce'
134 | );
135 | });
136 | });
137 | });
138 | });
139 |
--------------------------------------------------------------------------------
/test/unit.test.js:
--------------------------------------------------------------------------------
1 | const AsyncTestUtil = require('async-test-util');
2 | const assert = require('assert');
3 | const BN = require('bn.js');
4 | const EthCrypto = require('../dist/lib/index');
5 | const crypto = require('crypto');
6 |
7 | const TEST_DATA = {
8 | address: '0x3f243FdacE01Cfd9719f7359c94BA11361f32471',
9 | privateKey: '0x107be946709e41b7895eea9f2dacf998a0a9124acbb786f0fd1a826101581a07',
10 | publicKey: 'bf1cc3154424dc22191941d9f4f50b063a2b663a2337e5548abea633c1d06eceacf2b81dd326d278cd992d5e03b0df140f2df389ac9a1c2415a220a4a9e8c046'
11 | };
12 | const HEX_STRING = '0x55030130e79efc853f8644d32c11a58d47018cc3a08a16ac4fb9c09af4a634b16d1e37f44c60be0001670b7147dbacc6e057ac7595d74ecfd7ff58a593ee9db3cee601ee06234d200e1f2e35533533754ecbf910b86c1b7fc556b1cc2516f6dd3a25360bcd68f1af4f9450952cc9ef53de5b0c42f8f07976a05d0cfc0ee21acda7ad31cc77640fdd55349c460f94d71656e79048e5991aeb8852ad094bc96e8983232710f5b983ba07bc542ac3f4116a5d066b965e9071cb9912ed1a3da98cdd06e5ef75738fb915a6cef05497f49215bba156c2ba525b2a268be95c3efabb3f1d10fc3b3a57f8a06ef048735a5f3cf9fbbe2203b1b39568ff99e78094bf78c61514ebcbdc75fa90e7d06bc11a49959c2c4632d87384a2667f06e03216bba3b345af2cf89c439c12d4c24dc392d3ffdc9e807b00772b99299178415966d86b59478f21ae005e74c68057d5a3ccbefa08';
13 |
14 |
15 | describe('unit.test.js', () => {
16 | describe('.createIdentity()', () => {
17 | it('should create an identity', () => {
18 | const ident = EthCrypto.createIdentity();
19 | assert.equal(typeof ident.privateKey, 'string');
20 | assert.equal(typeof ident.publicKey, 'string');
21 | assert.equal(typeof ident.address, 'string');
22 | });
23 | it('2 identities should never be equal', () => {
24 | const ident = EthCrypto.createIdentity();
25 | const ident2 = EthCrypto.createIdentity();
26 | assert.notEqual(ident.privateKey, ident2.privateKey);
27 | });
28 | it('should create an identity from the buffer', () => {
29 | const pseudoRand = Buffer.from(AsyncTestUtil.randomString(128), 'utf-8');
30 | const ident = EthCrypto.createIdentity(pseudoRand);
31 | assert.equal(typeof ident.privateKey, 'string');
32 | assert.equal(typeof ident.publicKey, 'string');
33 | assert.equal(typeof ident.address, 'string');
34 | });
35 | it('two identities from the same buffer should be equal', () => {
36 | const pseudoRand = Buffer.from(AsyncTestUtil.randomString(128), 'utf-8');
37 | const ident = EthCrypto.createIdentity(pseudoRand);
38 | const ident2 = EthCrypto.createIdentity(pseudoRand);
39 | assert.equal(ident.privateKey, ident2.privateKey);
40 | });
41 | it('two identities from a different buffer must not be equal', () => {
42 | const pseudoRand = Buffer.from(AsyncTestUtil.randomString(128), 'utf-8');
43 | const ident = EthCrypto.createIdentity(pseudoRand);
44 | const pseudoRand2 = Buffer.from(AsyncTestUtil.randomString(128), 'utf-8');
45 | const ident2 = EthCrypto.createIdentity(pseudoRand2);
46 | assert.notEqual(ident.privateKey, ident2.privateKey);
47 | });
48 | it('should throw when entropy is to small', async () => {
49 | const pseudoRand = Buffer.from(AsyncTestUtil.randomString(4), 'utf-8');
50 | await AsyncTestUtil.assertThrows(
51 | () => EthCrypto.createIdentity(pseudoRand),
52 | Error,
53 | 'Entropy-size must be at least'
54 | );
55 | });
56 | it('should throw when entropy is no buffer', async () => {
57 | const pseudoRand = AsyncTestUtil.randomString(128);
58 | await AsyncTestUtil.assertThrows(
59 | () => EthCrypto.createIdentity(pseudoRand),
60 | Error,
61 | 'is no Buffer'
62 | );
63 | });
64 | });
65 | describe('.publicKeyByPrivateKey()', () => {
66 | describe('positive', () => {
67 | it('should give the correct publicKey', () => {
68 | console.dir(EthCrypto);
69 | const publicKey = EthCrypto.publicKeyByPrivateKey(TEST_DATA.privateKey);
70 | assert.equal(publicKey, TEST_DATA.publicKey);
71 | });
72 | it('should auto-prefix 0x', () => {
73 | const noPrefixPrivate = '43137cdb869f4375abfce46910aa24d528b2152c5a396158550158fbdb160b4f';
74 | const publicKey = EthCrypto.publicKeyByPrivateKey(noPrefixPrivate);
75 | const publicKey2 = EthCrypto.publicKeyByPrivateKey('0x' + noPrefixPrivate);
76 | assert.equal(publicKey, publicKey2);
77 | });
78 | });
79 | describe('negative', () => {
80 | it('should crash when non-key given', () => {
81 | assert.throws(
82 | () => EthCrypto.publicKeyByPrivateKey(
83 | AsyncTestUtil.randomString(12)
84 | )
85 | );
86 | });
87 | });
88 | });
89 | describe('.sign()', () => {
90 | describe('positive', () => {
91 | it('should sign the data', () => {
92 | const message = AsyncTestUtil.randomString(12);
93 | const messageHash = EthCrypto.hash.keccak256(message);
94 | const signature = EthCrypto.sign(TEST_DATA.privateKey, messageHash);
95 | assert.equal(typeof signature, 'string');
96 | assert.ok(signature.length > 10);
97 | });
98 | });
99 | describe('negative', () => {
100 | it('should not sign with wrong key', () => {
101 | assert.throws(
102 | () => EthCrypto.sign(
103 | 'XXX' + AsyncTestUtil.randomString(222),
104 | AsyncTestUtil.randomString(12)
105 | )
106 | );
107 | });
108 | it('should throw when non-hash given', () => {
109 | assert.throws(
110 | () => EthCrypto.sign(
111 | TEST_DATA.privateKey,
112 | AsyncTestUtil.randomString(5)
113 | )
114 | );
115 | });
116 | });
117 | });
118 | describe('.recover()', () => {
119 | describe('positive', () => {
120 | it('should return the correct address', () => {
121 | const message = AsyncTestUtil.randomString(12);
122 | const messageHash = EthCrypto.hash.keccak256(message);
123 | const signature = EthCrypto.sign(TEST_DATA.privateKey, messageHash);
124 | const address = EthCrypto.recover(signature, messageHash);
125 | assert.equal(address, TEST_DATA.address);
126 |
127 | });
128 | });
129 | describe('negative', () => { });
130 | });
131 | describe('.recoverPublicKey()', () => {
132 | it('should recover the correct key', async () => {
133 | const message = AsyncTestUtil.randomString(12);
134 | const messageHash = EthCrypto.hash.keccak256(message);
135 | const signature = EthCrypto.sign(TEST_DATA.privateKey, messageHash);
136 | const publicKey = EthCrypto.recoverPublicKey(signature, messageHash);
137 | assert.equal(publicKey, TEST_DATA.publicKey);
138 | });
139 | });
140 | describe('.encryptWithPublicKey()', () => {
141 | describe('positive', () => {
142 | it('should encrypt the data', async () => {
143 | const message = AsyncTestUtil.randomString(12);
144 | const encrypted = await EthCrypto.encryptWithPublicKey(
145 | TEST_DATA.publicKey,
146 | message
147 | );
148 | assert.equal(typeof encrypted.iv, 'string');
149 | assert.equal(typeof encrypted.ephemPublicKey, 'string');
150 | assert.equal(typeof encrypted.ciphertext, 'string');
151 | assert.equal(typeof encrypted.mac, 'string');
152 | });
153 | it('should also work with compressed keys', async () => {
154 | const message = AsyncTestUtil.randomString(12);
155 | const ident = EthCrypto.createIdentity();
156 | const compressed = EthCrypto.publicKey.compress(ident.publicKey);
157 | const encrypted = await EthCrypto.encryptWithPublicKey(
158 | compressed,
159 | message
160 | );
161 | const decrypted = await EthCrypto.decryptWithPrivateKey(
162 | ident.privateKey,
163 | encrypted
164 | );
165 | assert.equal(decrypted, message);
166 | });
167 | it('should use a random iv if none provided', async () => {
168 | const message = AsyncTestUtil.randomString(12);
169 | const encrypted = await EthCrypto.encryptWithPublicKey(
170 | TEST_DATA.publicKey,
171 | message
172 | );
173 | const encrypted2 = await EthCrypto.encryptWithPublicKey(
174 | TEST_DATA.publicKey,
175 | message
176 | );
177 |
178 | assert.ok(encrypted.ciphertext !== encrypted2.ciphertext);
179 | });
180 | it('should have deterministic output if iv is provided', async () => {
181 | const message = AsyncTestUtil.randomString(12);
182 |
183 | const iv = crypto.randomBytes(16);
184 | const ephemPrivateKey = crypto.randomBytes(32);
185 | console.dir(iv);
186 | const encrypted = await EthCrypto.encryptWithPublicKey(
187 | TEST_DATA.publicKey,
188 | message,
189 | {
190 | iv,
191 | ephemPrivateKey
192 | }
193 | );
194 | const encrypted2 = await EthCrypto.encryptWithPublicKey(
195 | TEST_DATA.publicKey,
196 | message,
197 | {
198 | iv,
199 | ephemPrivateKey
200 | }
201 | );
202 |
203 | assert.strictEqual(encrypted.ciphertext, encrypted2.ciphertext);
204 | assert.strictEqual(encrypted.mac, encrypted2.mac);
205 | });
206 | });
207 | describe('negative', () => {
208 | it('should throw when non-key given', async () => {
209 | const message = AsyncTestUtil.randomString(12);
210 | await AsyncTestUtil.assertThrows(
211 | () => EthCrypto.encryptWithPublicKey(
212 | AsyncTestUtil.randomString(12),
213 | message
214 | ),
215 | 'Error'
216 | );
217 | });
218 | });
219 | });
220 | describe('.decryptWithPrivateKey()', () => {
221 | describe('positive', () => {
222 | it('should decrypt the data', async () => {
223 | const message = AsyncTestUtil.randomString(12);
224 | const encrypted = await EthCrypto.encryptWithPublicKey(
225 | TEST_DATA.publicKey,
226 | message
227 | );
228 | const decrypted = await EthCrypto.decryptWithPrivateKey(
229 | TEST_DATA.privateKey,
230 | encrypted
231 | );
232 | assert.equal(decrypted, message);
233 | });
234 | it('should also decrypt with stringified data', async () => {
235 | const message = AsyncTestUtil.randomString(12);
236 | const encrypted = await EthCrypto.encryptWithPublicKey(
237 | TEST_DATA.publicKey,
238 | message
239 | );
240 | const encryptedString = EthCrypto.cipher.stringify(encrypted);
241 | const decrypted = await EthCrypto.decryptWithPrivateKey(
242 | TEST_DATA.privateKey,
243 | encryptedString
244 | );
245 | assert.equal(decrypted, message);
246 | });
247 | });
248 | describe('negative', () => { });
249 | });
250 | describe('.cipher', () => {
251 | describe('.stringify()', () => {
252 | it('should stringify the cipher', async () => {
253 | const ident = EthCrypto.createIdentity();
254 | const message = AsyncTestUtil.randomString(12);
255 |
256 | const cipher = await EthCrypto.encryptWithPublicKey(
257 | ident.publicKey,
258 | message
259 | );
260 | const str = EthCrypto.cipher.stringify(cipher);
261 | assert.equal(typeof str, 'string');
262 | });
263 | it('should not stringify the string', async () => {
264 | const ident = EthCrypto.createIdentity();
265 | const message = AsyncTestUtil.randomString(12);
266 |
267 | const cipher = await EthCrypto.encryptWithPublicKey(
268 | ident.publicKey,
269 | message
270 | );
271 | const str = EthCrypto.cipher.stringify(cipher);
272 | const str2 = EthCrypto.cipher.stringify(str);
273 | assert.equal(str, str2);
274 | });
275 | });
276 | describe('.parse()', () => {
277 | it('should parse the equal object', async () => {
278 | const ident = EthCrypto.createIdentity();
279 | const message = AsyncTestUtil.randomString(12);
280 |
281 | const cipher = await EthCrypto.encryptWithPublicKey(
282 | ident.publicKey,
283 | message
284 | );
285 | const str = EthCrypto.cipher.stringify(cipher);
286 | const cipher2 = EthCrypto.cipher.parse(str);
287 | assert.deepEqual(cipher, cipher2);
288 | });
289 | it('should also work with different message-length', async () => {
290 | const ident = EthCrypto.createIdentity();
291 | const message = AsyncTestUtil.randomString(120);
292 |
293 | const cipher = await EthCrypto.encryptWithPublicKey(
294 | ident.publicKey,
295 | message
296 | );
297 | const str = EthCrypto.cipher.stringify(cipher);
298 | const cipher2 = EthCrypto.cipher.parse(str);
299 | assert.deepEqual(cipher, cipher2);
300 | });
301 | });
302 | });
303 | describe('.publicKey', () => {
304 | describe('.compress()', () => {
305 | it('should compress the key', () => {
306 | const uncompressed = 'a34d6aef3eb42335fb3cacb59478c0b44c0bbeb8bb4ca427dbc7044157a5d24b4adf14868d8449c9b3e50d3d6338f3e5a2d3445abe679cddbe75cb893475806f';
307 | const compressed = EthCrypto.publicKey.compress(uncompressed);
308 | assert.equal(typeof compressed, 'string');
309 | assert.ok(compressed.startsWith('03'));
310 | });
311 | it('should also work with trailing 04', () => {
312 | const uncompressed = '04a34d6aef3eb42335fb3cacb59478c0b44c0bbeb8bb4ca427dbc7044157a5d24b4adf14868d8449c9b3e50d3d6338f3e5a2d3445abe679cddbe75cb893475806f';
313 | const compressed = EthCrypto.publicKey.compress(uncompressed);
314 | assert.equal(typeof compressed, 'string');
315 | assert.ok(compressed.startsWith('03'));
316 | });
317 | it('should also work when compressed already given', () => {
318 | const uncompressed = '03a34d6aef3eb42335fb3cacb59478c0b44c0bbeb8bb4ca427dbc7044157a5d24b';
319 | const compressed = EthCrypto.publicKey.compress(uncompressed);
320 | assert.equal(typeof compressed, 'string');
321 | assert.ok(compressed.startsWith('03'));
322 | });
323 | });
324 | describe('.decompress()', () => {
325 | it('should decompress', () => {
326 | const compressed = '03a34d6aef3eb42335fb3cacb59478c0b44c0bbeb8bb4ca427dbc7044157a5d24b';
327 | const uncompressed = EthCrypto.publicKey.decompress(compressed);
328 | assert.equal(typeof uncompressed, 'string');
329 | const buf = Buffer.from(uncompressed, 'hex');
330 | assert.equal(buf.length, 64);
331 | });
332 | it('should work when already uncompressed', () => {
333 | const compressed = '04a34d6aef3eb42335fb3cacb59478c0b44c0bbeb8bb4ca427dbc7044157a5d24b4adf14868d8449c9b3e50d3d6338f3e5a2d3445abe679cddbe75cb893475806f';
334 | const uncompressed = EthCrypto.publicKey.decompress(compressed);
335 | assert.equal(typeof uncompressed, 'string');
336 | const buf = Buffer.from(uncompressed, 'hex');
337 | assert.equal(buf.length, 64);
338 | });
339 | it('should work when already uncompressed (no04)', () => {
340 | const compressed = 'a34d6aef3eb42335fb3cacb59478c0b44c0bbeb8bb4ca427dbc7044157a5d24b4adf14868d8449c9b3e50d3d6338f3e5a2d3445abe679cddbe75cb893475806f';
341 | const uncompressed = EthCrypto.publicKey.decompress(compressed);
342 | assert.equal(typeof uncompressed, 'string');
343 | const buf = Buffer.from(uncompressed, 'hex');
344 | assert.equal(buf.length, 64);
345 | });
346 | });
347 | describe('.toAddress()', () => {
348 | describe('positive', () => {
349 | it('should generate the correct address', () => {
350 | const address = EthCrypto.publicKey.toAddress(TEST_DATA.publicKey);
351 | assert.equal(address, TEST_DATA.address);
352 | });
353 | it('should work with compressed key', () => {
354 | const ident = EthCrypto.createIdentity();
355 | const compressed = EthCrypto.publicKey.compress(ident.publicKey);
356 | const address = EthCrypto.publicKey.toAddress(compressed);
357 | assert.equal(address, ident.address);
358 | });
359 | });
360 | describe('negative', () => {
361 | assert.throws(
362 | () => EthCrypto.publicKey.toAddress(
363 | AsyncTestUtil.randomString(12)
364 | )
365 | );
366 | });
367 | });
368 | });
369 | describe('.signTransaction()', () => {
370 | describe('positive', () => {
371 | it('should sign our transaction', () => {
372 | const ident = EthCrypto.createIdentity();
373 | const rawTx = {
374 | from: ident.address,
375 | to: '0x86Fa049857E0209aa7D9e616F7eb3b3B78ECfdb0',
376 | value: new BN('1000000000000000000'),
377 | gasPrice: 5000000000,
378 | gasLimit: 21000
379 | };
380 | const signed = EthCrypto.signTransaction(
381 | rawTx,
382 | ident.privateKey
383 | );
384 | assert.equal(typeof signed, 'string');
385 | });
386 | });
387 | describe('negative', () => {
388 | it('should throw on non-key', () => {
389 | const ident = EthCrypto.createIdentity();
390 | const rawTx = {
391 | from: ident.address,
392 | to: '0x86Fa049857E0209aa7D9e616F7eb3b3B78ECfdb0',
393 | value: new BN('1000000000000000000'),
394 | gasPrice: 5000000000,
395 | gasLimit: 21000
396 | };
397 | const ident2 = EthCrypto.createIdentity();
398 | assert.throws(
399 | () => EthCrypto.signTransaction(
400 | rawTx,
401 | ident2.privateKey
402 | )
403 | );
404 | });
405 | });
406 | });
407 | describe('hex', () => {
408 | it('compress/decompress to utf16', () => {
409 | const compressed = EthCrypto.hex.compress(HEX_STRING, false);
410 | assert.ok(compressed.length < HEX_STRING.length);
411 |
412 | const decompressed = EthCrypto.hex.decompress(compressed, false);
413 | assert.equal(decompressed, HEX_STRING);
414 | });
415 | it('compress/decompress to base64', () => {
416 | const compressed = EthCrypto.hex.compress(HEX_STRING, true);
417 | assert.ok(compressed.length < HEX_STRING.length);
418 |
419 | const decompressed = EthCrypto.hex.decompress(compressed, true);
420 | assert.equal(decompressed, HEX_STRING);
421 | });
422 | });
423 | /*
424 | describe('.testBlock()', ()=> {
425 | describe('positive', ()=> {});
426 | describe('negative', ()=> {});
427 | });
428 | */
429 | });
430 |
--------------------------------------------------------------------------------
/tutorials/creating-transactions.md:
--------------------------------------------------------------------------------
1 | # Tutorial: Creating Keys and use them for ethereum-transactions
2 |
3 | In this tutorial we will create an ethereum-identity and use it to send transactions to the blockchain.
4 |
5 |
6 | ## Creating a new identity
7 | An identity is an object with a privateKey and the corresponding publicKey and its address. To create a fresh identity, the function `createIdentity` is called which returns one.
8 |
9 | ```javascript
10 | const EthCrypto = require('eth-crypto');
11 |
12 | const identity = EthCrypto.createIdentity();
13 |
14 | console.dir(identity);
15 | /* > {
16 | address: '0x3f243FdacE01Cfd9719f7359c94BA11361f32471',
17 | privateKey: '0x107be946709e41b7895eea9f2dacf998a0a9124acbb786f0fd1a826101581a07',
18 | publicKey: 'bf1cc3154424dc22191941d9f4f50b063a2b663a2337e5548abea633c1d06ece...'
19 | } */
20 | ```
21 |
22 | When we code things in the ethereum ecosystem, it is standard to represent data as hex-strings. You can see that hex-strings start with a `0x` which marks them as so.
23 |
24 | The identity consists of:
25 |
26 | - The **privateKey** which must never be revealed to anyone. It can be used to sign and decrypt messages and to create its publicKey.
27 | - The **publicKey** is revealed whenever something is signed with the privateKey. It's also common to send the publicKey to other humans so that they can encrypt data with it, which then can only decrypted by the correct privateKey. It's important to know that there are [two ways to represent](https://github.com/bitpay/bitcore-lib/blob/master/docs/publickey.md) a publicKey compressed and uncompressed. EthCrypto always creates the uncompressed key which starts with `0x04`. Compressed keys start with `0x03` or `0x02`. To represent the key, we strip the starting `04` away from it and internally add it when doing cryptographic calls.
28 | - The **address** is calculated from the last 20 bytes of the keccak-256-hash of the publicKey. It is used to represent an identity. Notice that there is no way to calculate the publicKey from an address. This means that whenever we want to encrypt data for someone, we first have to get the publicKey. There are two ways to represent an address. The normal address is lowercase and represents just the 20 bytes of the hash. The checksum-format contains uppercase-letters which the purpose of detecting errors when the address is entered manually.
29 |
30 |
31 | # Creating a transaction
32 |
33 | An ethereum-transaction is basically a json-object with defined values. Lets create one where we send one ether to another address.
34 |
35 | ```javascript
36 | const rawTransaction = {
37 | from: identity.address, // sender address
38 | to: '0x86Fa049857E0209aa7D9e616F7eb3b3B78ECfdb0', // receiver address
39 | value: new BN('1000000000000000000'), // amount of wei we want to send (= 1 ether)
40 | nonce: 0, // incremental tx-number. Add +1 for every transaction you do
41 | gasPrice: 5000000000,
42 | gasLimit: 21000 // normal gasLimit for code-less transactions
43 | };
44 | ```
45 |
46 | Before the transaction can be submitted to an ethereum-node, it must be signed with the `privateKey` and serialized to a hex-string.
47 |
48 | ```javascript
49 | const serializedTx = EthCrypto.signTransaction(
50 | rawTransaction,
51 | identity.privateKey
52 | );
53 | console.log(serializedTx);
54 | // > 'f86c808504a817c80082ea609463dcee1fd1d814858acd4172bb20e1...'
55 | ```
56 |
57 | Now the transaction-string could be submitted to the blockchain. If we really wanted to send the value, we could do this by either sending it to a public node like [etherscan](https://etherscan.io/pushTx) or by pushing it to our local node. For testing-purposes it is usual to create a local test-chain and try things out there.
58 |
59 | ## Creating the local testnet
60 |
61 | To create a local testnet, we will use the [ganache-cli](https://github.com/trufflesuite/ganache-cli) and connect it to a web3-instance so we can interact with it.
62 |
63 | ```javascript
64 | const Web3 = require('web3');
65 | const ganache = require('ganache-cli');
66 |
67 | // create a web3-instance
68 | const web3 = new Web3();
69 |
70 | // create a ganache-provider
71 | const ganacheProvider = ganache.provider({
72 | // we preset the balance of our identity to 10 ether
73 | accounts: [{
74 | secretKey: identity.privateKey,
75 | balance: web3.utils.toWei('10', 'ether')
76 | }]
77 | });
78 |
79 | // set ganache to web3 as provider
80 | web3.setProvider(ganacheProvider);
81 | ```
82 |
83 | ## Submitting the transaction
84 |
85 | Call `sendSignedTransaction` to submit the signed transaction to the testchain. Ganache will instantly mine the transaction and we get a receipt back.
86 |
87 | ```javascript
88 | const receipt = await web3.eth.sendSignedTransaction(serializedTx);
89 | ```
90 |
91 | To ensure that the transaction worked, check the balance of the receivers address.
92 |
93 | ```javascript
94 | const balance = await web3.eth.getBalance('0x86Fa049857E0209aa7D9e616F7eb3b3B78ECfdb0');
95 | console.log(balance);
96 | // > '1000000000000000000'
97 | ```
98 |
99 |
100 | ## Done
101 | Awesome! You created your first ethereum-transaction.
102 |
--------------------------------------------------------------------------------
/tutorials/encrypted-message.md:
--------------------------------------------------------------------------------
1 | # Tutorial: Encrypt and sign a message
2 |
3 | With ethereum-keys you cannot only interact with the blockchain, but also use them to send messages over mutual untrusted channels in a secure way. In this tutorial we will use ethereum-identities to send messages like you would do in a decentralized chat-app.
4 |
5 | ## Prerequisites
6 |
7 | First we create two identities, `Alice` and `Bob`. In our case `Alice` wants to send the message `My name is Satoshi Buterin` to `Bob`.
8 |
9 | ```javascript
10 | const EthCrypto = require('eth-crypto');
11 |
12 | const alice = EthCrypto.createIdentity();
13 | const bob = EthCrypto.createIdentity();
14 | const secretMessage = 'My name is Satoshi Buterin';
15 | ```
16 |
17 | ## Encrypt and sign the message
18 |
19 | Before we send the message from `Alice` to `Bob`, we want to ensure that
20 |
21 | - Only `Bob` can read the message
22 | - `Bob` can be sure that the message really comes from `Alice`
23 |
24 | To do this, we first sign the message with alice's privateKey and then encrypt the message and the signature with bob's publicKey.
25 |
26 | ```javascript
27 | const signature = EthCrypto.sign(
28 | alice.privateKey,
29 | EthCrypto.hash.keccak256(secretMessage)
30 | );
31 | const payload = {
32 | message: secretMessage,
33 | signature
34 | };
35 | const encrypted = await EthCrypto.encryptWithPublicKey(
36 | bob.publicKey, // by encrypting with bobs publicKey, only bob can decrypt the payload with his privateKey
37 | JSON.stringify(payload) // we have to stringify the payload before we can encrypt it
38 | );
39 | /* { iv: 'c66fbc24cc7ef520a7...',
40 | ephemPublicKey: '048e34ce5cca0b69d4e1f5...',
41 | ciphertext: '27b91fe986e3ab030...',
42 | mac: 'dd7b78c16e462c42876745c7...'
43 | }
44 | */
45 |
46 | // we convert the object into a smaller string-representation
47 | const encryptedString = EthCrypto.cipher.stringify(encrypted);
48 | // > '812ee676cf06ba72316862fd3dabe7e403c7395bda62243b7b0eea5eb..'
49 |
50 | // now we send the encrypted string to bob over the internet.. *bieb, bieb, blob*
51 | ```
52 |
53 | ## Decrypt and verify the payload
54 |
55 | When bob receives the message, he starts with decrypting it with his privateKey and then verifies the signature.
56 |
57 | ```javascript
58 |
59 | // we parse the string into the object again
60 | const encryptedObject = EthCrypto.cipher.parse(encryptedString);
61 |
62 | const decrypted = await EthCrypto.decryptWithPrivateKey(
63 | bob.privateKey,
64 | encryptedObject
65 | );
66 | const decryptedPayload = JSON.parse(decrypted);
67 |
68 | // check signature
69 | const senderAddress = EthCrypto.recover(
70 | decryptedPayload.signature,
71 | EthCrypto.hash.keccak256(decryptedPayload.message)
72 | );
73 |
74 | console.log(
75 | 'Got message from ' +
76 | senderAddress +
77 | ': ' +
78 | decryptedPayload.message
79 | );
80 | // > 'Got message from 0x19C24B2d99FB91C5...: "My name is Satoshi Buterin" Buterin'
81 | ```
82 |
83 | ## Creating an answer
84 |
85 | Now that `Bob` got the message, he can also answer back to alice.
86 | To do this he has to recover the publicKey of alice with `recoverPublicKey()`.
87 |
88 | ```javascript
89 | const answerMessage = 'And I am Bob Kelso';
90 | const answerSignature = EthCrypto.sign(
91 | bob.privateKey,
92 | EthCrypto.hash.keccak256(answerMessage)
93 | );
94 | const answerPayload = {
95 | message: answerMessage,
96 | signature: answerSignature
97 | };
98 |
99 | const alicePublicKey = EthCrypto.recoverPublicKey(
100 | decryptedPayload.signature,
101 | EthCrypto.hash.keccak256(payload.message)
102 | );
103 |
104 | const encryptedAnswer = await EthCrypto.encryptWithPublicKey(
105 | alicePublicKey,
106 | JSON.stringify(answerPayload)
107 | );
108 | // now we send the encryptedAnswer to alice over the internet.. *bieb, bieb, blob*
109 | ```
110 |
--------------------------------------------------------------------------------
/tutorials/signed-data.md:
--------------------------------------------------------------------------------
1 | # Tutorial: Sign and validate data with solidity
2 |
3 | In this tutorial we will sign data with JavaScript and later validate the signature in a solidity smart-contract.
4 |
5 | ## Prerequisites
6 |
7 | First we create two identities, `creator` and `receiver`.
8 |
9 | ```javascript
10 | const EthCrypto = require('eth-crypto');
11 |
12 | const creatorIdentity = EthCrypto.createIdentity();
13 | const recieverIdentity = EthCrypto.createIdentity();
14 | ```
15 |
16 | Then we start a local testnet to use later. At the testnet, we give the `creatorIdentity` a balance of 10 ether. We also give one ether to the `recieverIdentity` so we have enough gas to send transactions.
17 |
18 | ```javascript
19 | const Web3 = require('web3');
20 | const ganache = require('ganache-cli');
21 |
22 | // create a web3-instance
23 | const web3 = new Web3('http://'); // set 'http://' because web3 needs a provider
24 | web3.transactionConfirmationBlocks = 1; // set confirmations-blocks to 1 for fast testing
25 |
26 | // create a ganache-provider
27 | const ganacheProvider = ganache.provider({
28 | accounts: [
29 | // we preset the balance of our creatorIdentity to 10 ether
30 | {
31 | secretKey: creatorIdentity.privateKey,
32 | balance: web3.utils.toWei('10', 'ether')
33 | },
34 | // we also give some wei to the recieverIdentity
35 | // so it can send transaction to the chain
36 | {
37 | secretKey: recieverIdentity.privateKey,
38 | balance: web3.utils.toWei('1', 'ether')
39 | }
40 | ]
41 | });
42 |
43 | // set ganache to web3 as provider
44 | web3.setProvider(ganacheProvider);
45 | ```
46 |
47 | ## Create a smart-contract that can validate signatures
48 |
49 | Let's create an example-contract. The contract will be a donation-bag which contains some ether and has an owner. Whenever someone submits a valid donation-signature, he receives a part of the contracts value. This allows the creator of the contract to give signed data to people **off-chain** which they can later use to claim the value **on-chain**.
50 |
51 | Write the contracts code in a file called `DonationBag.sol`. **Check out its content [here](../contracts/DonationBag.sol)**.
52 |
53 | As you can see, the contract has some methods:
54 |
55 | - **DonationBag()**: The constructor which is called when the contract is created. Here we set the owner of the DonationBag
56 | - **default-function**: The default function is called when we send ether to the contract without doing anything. This is needed so the contract can receive value.
57 | - **prefixedHash()**: Creates a hash of the data which must be signed by the creator.
58 | - **isSignatureValid()**: Checks if a given signature is really signed by the sender and contains the correct content.
59 | - **recieveDonation():** This is called by the receiver when the donation is claimed.
60 |
61 | ## Deploy the contract
62 |
63 | Before we can put the contract on our local blockchain. We have to compile the solidity-code to bytecode. We will do this by using the javascript-version of the `solc` compiler.
64 |
65 | ```javascript
66 | const path = require('path');
67 | const SolidityCli = require('solidity-cli');
68 | const contractPath = path.join(__dirname, '../../contracts/DonationBag.sol');
69 | const compiled = await SolidityCli.compileFile(contractPath);
70 | const compiledDonationBag = compiled[':DonationBag'];
71 |
72 | console.dir(compiledDonationBag);
73 | /* > {
74 | interface: [...],
75 | bytecode: '10390f35b34156101ef57600...'
76 | }
77 | */
78 | ```
79 |
80 | Now that we have the bytecode of the contract, we can submit a transaction to create a new instance of it at our local testchain.
81 |
82 | ```javascript
83 | // create contract-create-code
84 | const createCode = EthCrypto.txDataByCompiled(
85 | compiledDonationBag.interface, // abi
86 | compiledDonationBag.bytecode, // bytecode
87 | [creatorIdentity.address] // constructor-arguments
88 | );
89 |
90 | // create a transaction the deploys the contract
91 | const rawTx = {
92 | from: creatorIdentity.address,
93 | nonce: 0,
94 | gasLimit: 5000000,
95 | gasPrice: 5000000000,
96 | data: createCode
97 | };
98 | const serializedTx = EthCrypto.signTransaction(
99 | rawTx,
100 | creatorIdentity.privateKey
101 | );
102 |
103 | // submit to local chain
104 | const receipt = await web3.eth.sendSignedTransaction(serializedTx);
105 | const contractAddress = receipt.contractAddress;
106 |
107 | console.log(contractAddress);
108 | // > '0xCF3d784002721227F36575eD051Ea2171a528b7D' <- this is the address of our contract
109 | ```
110 |
111 | Awesome. The contract is now on the blockchain. To check if it is deployed correctly, lets call a function on it.
112 |
113 | ```javascript
114 |
115 | // create contract instance
116 | const contractInstance = new web3.eth.Contract(
117 | JSON.parse(compiledDonationBag.interface),
118 | contractAddress
119 | );
120 |
121 | // check owner
122 | const owner = await contractInstance.methods.owner().call();
123 | console.dir(owner); // same as creatorIdentity.address
124 | ```
125 |
126 | Before we can sign donations, we have to send some value to the contract.
127 |
128 | ```javascript
129 | const rawTx2 = {
130 | from: creatorIdentity.address,
131 | to: contractAddress,
132 | nonce: 1, // increased by one
133 | value: parseInt(web3.utils.toWei('3', 'ether')),
134 | gasLimit: 600000,
135 | gasPrice: 20000000000
136 | };
137 | const serializedTx2 = EthCrypto.signTransaction(
138 | rawTx2,
139 | creatorIdentity.privateKey
140 | );
141 | await web3.eth.sendSignedTransaction(serializedTx2);
142 |
143 | // check balance
144 | const balance = await contractInstance.methods.getBalance().call();
145 | console.log(balance); // > '1000000000000000000'
146 | ```
147 |
148 | ## Sign the message
149 |
150 | Lets sign a message with the `creatorIdentity` where the donator validates a donation to the `recieverIdentity`.
151 |
152 | ```javascript
153 | const signHash = EthCrypto.hash.keccak256([
154 | { // prefix
155 | type: 'string',
156 | value: 'Signed for DonationBag:'
157 | }, { // contractAddress
158 | type: 'address',
159 | value: contractAddress
160 | }, { // receiverAddress
161 | type: 'address',
162 | value: recieverIdentity.address
163 | }
164 | ]);
165 |
166 | const signature = EthCrypto.sign(
167 | creatorIdentity.privateKey,
168 | signHash
169 | );
170 | ```
171 |
172 | As you can see, we did not sign the reciever-address directly but a hash that was build of some concated data:
173 |
174 | - **Prefix:** To ensure the creator cannot be tricked into accidentally singing a valid ethereum-transaction, we prefix the signed data with something unique to our system. In this case lets take the string `Signed for DonationBag:`.
175 | - **contractAddress:** It might be possible that the creator has more than one instance of the contract deployed to the blockchain. In this case its signatures might be replayed to other instances. As prevention of this attack, we also add the contracts address to the signed hash.
176 | - **receiverAddress:** By signing this address, the creator proves that the given address should receive the donation.
177 |
178 | ## Recover the signature on the blockchain
179 |
180 | The receiver now has a signature from the creator which he can send to the contract to claim the donation.
181 |
182 | ```javascript
183 |
184 | // we have to split the signature-hex-string into its parts
185 | const vrs = EthCrypto.vrs.fromString(signature);
186 | /* > {
187 | v: '0x1c',
188 | r: '0x525db3ea66...',
189 | s: '0x78544aebe6...'
190 | }
191 | */
192 |
193 | // create the transaction-data for the recieveDonation()-call
194 | const recieveCode = contractInstance
195 | .methods.recieveDonation(
196 | vrs.v,
197 | vrs.r,
198 | vrs.s
199 | ).encodeABI();
200 |
201 | // create+sign the transaction
202 | const recieveTx = {
203 | from: recieverIdentity.address,
204 | to: contractAddress,
205 | nonce: 0,
206 | gasLimit: 5000000,
207 | gasPrice: 5000000000,
208 | data: recieveCode
209 | };
210 | const serializedRecieveTx = EthCrypto.signTransaction(
211 | recieveTx,
212 | recieverIdentity.privateKey
213 | );
214 |
215 | // submit the tx
216 | const receipt3 = await web3.eth.sendSignedTransaction(serializedRecieveTx);
217 | ```
218 |
219 | If everything has gone right, the receiver should now have more ether than before. Let's check this.
220 |
221 | ```javascript
222 | const receiverBalance = await web3.eth.getBalance(recieverIdentity.address);
223 | console.dir(receiverBalance);
224 | // '1999802840000000000'
225 | ```
226 |
--------------------------------------------------------------------------------
/typings/index.d.ts:
--------------------------------------------------------------------------------
1 | import BigNumber from 'bn.js';
2 | import { TxOptions } from '@ethereumjs/tx';
3 |
4 | type createIdentityType = (entropy?: Buffer) => {
5 | privateKey: string,
6 | publicKey: string,
7 | address: string
8 | }
9 | export const createIdentity: createIdentityType;
10 |
11 | type publicKeyType = {
12 | compress(publicKey: string): string;
13 | decompress(publicKey: string): string;
14 | toAddress(publicKey: string): string;
15 | }
16 | export const publicKey: publicKeyType;
17 |
18 | type publicKeyByPrivateKeyType = (privateKey: string) => string;
19 | export const publicKeyByPrivateKey: publicKeyByPrivateKeyType;
20 |
21 | export type Signature = {
22 | v: string,
23 | r: string,
24 | s: string
25 | };
26 |
27 | export type Encrypted = {
28 | iv: string,
29 | ephemPublicKey: string,
30 | ciphertext: string,
31 | mac: string
32 | };
33 |
34 | export type RawTx = {
35 | from: string;
36 | to: string;
37 | value: number | string | BigNumber;
38 | gasLimit: number;
39 | gasPrice: number;
40 | nonce: number;
41 | code?: string;
42 | };
43 |
44 | type signType = (privateKey: string, message: string) => string;
45 | export const sign: signType;
46 |
47 | type recoverType = (sig: string, message: string) => string;
48 | export const recover: recoverType;
49 |
50 | type recoverPublicKeyType = (sig: string, message: string) => string;
51 | export const recoverPublicKey: recoverPublicKeyType;
52 |
53 | type vrsType = {
54 | fromString(hexString: string): Signature;
55 | toString(sig: Signature): string;
56 | };
57 | export const vrs: vrsType;
58 |
59 | export type EncryptOptions = {
60 | iv?: Buffer,
61 | ephemPrivateKey?: Buffer
62 | };
63 | type encryptWithPublicKeyType = (publicKey: string, message: string, options?: EncryptOptions) => Promise;
64 | export const encryptWithPublicKey: encryptWithPublicKeyType;
65 |
66 | type decryptWithPrivateKeyType = (privateKey: string, encrypted: Encrypted) => Promise;
67 | export const decryptWithPrivateKey: decryptWithPrivateKeyType;
68 |
69 | type cipherType = {
70 | stringify(encrypted: Encrypted): string;
71 | parse(encrypted: string): Encrypted;
72 | };
73 | export const cipher: cipherType;
74 |
75 | type signTransactionType = (
76 | rawTx: RawTx,
77 | privateKey: string,
78 | txOptions?: TxOptions
79 | ) => string;
80 | export const signTransaction: signTransactionType;
81 |
82 | type txDataByCompiledType = (
83 | abi: any,
84 | bytecode: string,
85 | args?: Array
86 | ) => string;
87 | export const txDataByCompiled: txDataByCompiledType;
88 |
89 | type calculateContractAddressType = (
90 | creatorAddress: string,
91 | nonce: number
92 | ) => string;
93 | export const calculateContractAddress: calculateContractAddressType;
94 |
95 | export type TypedValue = {
96 | value: string | Number | BigNumber,
97 | type: 'string' | 'uint256' | 'int256' | 'bool' | 'bytes' | 'bytes32' | 'address'
98 | };
99 |
100 | type hashType = {
101 | keccak256(params: string | TypedValue[]): string;
102 | };
103 | export const hash: hashType;
104 |
105 | type utilType = {
106 | removeLeading0x(str: string): string;
107 | addLeading0x(str: string): string;
108 | };
109 | export const util: utilType;
110 |
111 | type hexType = {
112 | compress(hex: string, base64?: boolean): string;
113 | decompress(str: string, base64?: boolean): string;
114 | };
115 | export const hex: hexType;
116 |
117 | declare const _default: {
118 | createIdentity: createIdentityType,
119 | publicKey: publicKeyType,
120 | encryptWithPublicKey: encryptWithPublicKeyType,
121 | decryptWithPrivateKey: decryptWithPrivateKeyType,
122 | cipher: cipherType,
123 | signTransaction: signTransactionType,
124 | txDataByCompiled: txDataByCompiledType,
125 | publicKeyByPrivateKey: publicKeyByPrivateKeyType,
126 | recover: recoverType,
127 | recoverPublicKey: recoverPublicKeyType,
128 | sign: signType,
129 | calculateContractAddress: calculateContractAddressType,
130 | hash: hashType,
131 | hex: hexType,
132 | vrs: vrsType,
133 | util: utilType
134 | };
135 | export default _default;
136 |
--------------------------------------------------------------------------------