├── .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 | follow on Twitter 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 | follow on Twitter 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 | JavaScript Database 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 | --------------------------------------------------------------------------------