├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── dist ├── keythereum.js └── keythereum.min.js ├── exports.js ├── index.js ├── keystore └── .gitkeep ├── package.json └── test ├── browser └── index.html ├── checkKeyObj.js ├── fixtures └── keystore │ ├── 2c97f31d2db40aa57d0e6ca5fa8aedf7d99592db │ └── 2c97f31d2db40aa57d0e6ca5fa8aedf7d99592db │ ├── UTC--2015-08-11T05-46-48.706837816Z--5a79b93487966d0eafb5264ca0408e66b7db9269 │ ├── UTC--2015-08-11T05-48-49.615209477Z--00efeeb535b1b1c408cca2ffd55b2b233269728c │ ├── UTC--2015-08-13T03:26:11.386Z--c9a9adc70a9cbf077ae4bd0a170d88592914e0cc │ ├── UTC--2015-08-13T04:57:57.228Z--008aeeda4d805471df9b2a5b0f38a0c3bcba786b │ ├── ebb117ef11769e675e0245062a8e6296dfe42da4 │ └── ebb117ef11769e675e0245062a8e6296dfe42da4 │ └── f0c4ee355432a7c7da12bdef04543723d110d591 │ └── f0c4ee355432a7c7da12bdef04543723d110d591 ├── geth.js ├── keys.js └── mocha.opts /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true 5 | }, 6 | "extends": "eslint:recommended", 7 | "rules": { 8 | "indent": [ 9 | "error", 10 | 2, 11 | { 12 | "SwitchCase": 1 13 | } 14 | ], 15 | "linebreak-style": [ 16 | "error", 17 | "unix" 18 | ], 19 | "quotes": [ 20 | "error", 21 | "double" 22 | ], 23 | "semi": [ 24 | "error", 25 | "always" 26 | ], 27 | "curly": [ 28 | 2, 29 | "multi-line" 30 | ], 31 | "keyword-spacing": [ 32 | 2, {} 33 | ], 34 | "space-before-blocks": [ 35 | 2, 36 | "always" 37 | ], 38 | "wrap-iife": 2, 39 | "space-before-function-paren": [ 40 | 2, 41 | { 42 | "anonymous": "always", 43 | "named": "never", 44 | "asyncArrow": "ignore" 45 | } 46 | ], 47 | "vars-on-top": 2, 48 | "no-empty": [ 49 | 2, { 50 | "allowEmptyCatch": true 51 | } 52 | ], 53 | "array-bracket-spacing": [ 54 | 2, 55 | "never", {} 56 | ], 57 | "space-in-parens": [ 58 | 2, 59 | "never" 60 | ], 61 | "comma-style": [ 62 | 2, 63 | "last" 64 | ], 65 | "space-unary-ops": [ 66 | 2, { 67 | "words": false, 68 | "nonwords": false 69 | } 70 | ], 71 | "no-with": 2, 72 | "no-mixed-spaces-and-tabs": 2, 73 | "no-trailing-spaces": 2, 74 | "comma-dangle": [ 75 | 2, 76 | "never" 77 | ], 78 | "brace-style": [ 79 | 2, 80 | "1tbs", { 81 | "allowSingleLine": true 82 | } 83 | ], 84 | "eol-last": 2, 85 | "dot-notation": 2, 86 | "no-multi-str": 2, 87 | "key-spacing": [ 88 | 2, { 89 | "afterColon": true 90 | } 91 | ], 92 | "default-case": 2, 93 | "eqeqeq": [2, "allow-null"], 94 | "guard-for-in": 2, 95 | "no-caller": 2, 96 | "no-console": 0, 97 | "no-else-return": 2, 98 | "no-eval": 2, 99 | "no-extend-native": 2, 100 | "no-extra-bind": 2, 101 | "no-fallthrough": 2, 102 | "no-floating-decimal": 2, 103 | "no-implicit-coercion": 2, 104 | "no-implied-eval": 2, 105 | "no-iterator": 2, 106 | "no-labels": 2, 107 | "no-loop-func": 2, 108 | "no-native-reassign": 2, 109 | "no-new-func": 2, 110 | "no-new-wrappers": 2, 111 | "no-new": 2, 112 | "no-octal-escape": 2, 113 | "no-octal": 2, 114 | "no-proto": 2, 115 | "no-redeclare": 2, 116 | "no-return-assign": 2, 117 | "no-script-url": 2, 118 | "no-self-compare": 2, 119 | "no-throw-literal": 1, 120 | "no-unused-expressions": 2, 121 | "no-useless-call": 2, 122 | "no-useless-escape": 0, 123 | "no-void": 1, 124 | "no-warning-comments": 1, 125 | "radix": 2, 126 | "no-catch-shadow": 2, 127 | "no-delete-var": 2, 128 | "no-shadow-restricted-names": 2, 129 | "no-undef": 2, 130 | "no-unused-vars": 2, 131 | "no-use-before-define": 2, 132 | "comma-spacing": [2, { 133 | "after": true 134 | }], 135 | "no-array-constructor": 2, 136 | "no-continue": 2, 137 | "no-multiple-empty-lines": 2, 138 | "no-nested-ternary": 2, 139 | "no-new-object": 2, 140 | "no-spaced-func": 2 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | node_modules 3 | *.log 4 | coverage 5 | overflow 6 | data 7 | scripts 8 | test/fixtures/nodekey 9 | test/fixtures/.password 10 | test/fixtures/chaindata 11 | test/fixtures/dapp 12 | test/fixtures/blockchain 13 | test/fixtures/extra 14 | test/fixtures/state 15 | test/fixtures/nodes 16 | test/fixtures/geth 17 | test/browser/bundle.js 18 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | .gitignore 4 | .npmignore 5 | .travis.yml 6 | test 7 | coverage 8 | keystore 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | branches: 4 | only: 5 | - master 6 | 7 | node_js: 8 | - "14" 9 | - "13" 10 | - "12" 11 | - "10" 12 | - "8" 13 | 14 | before_script: 15 | - npm install 16 | 17 | script: 18 | - npm run lint 19 | - istanbul cover ./node_modules/mocha/bin/_mocha test/keys.js --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 6 | (modification: no type change headlines) and this project adheres to 7 | [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 8 | 9 | ## [2.0.0] - 2022-08-03 10 | 11 | Maintenance release with various library updates and dependency simplifications. 12 | 13 | Note that this version now uses the native JS [BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) datatype, which is not supported in some older versions of browsers and dev/build tools! 14 | 15 | Following Updates: 16 | 17 | 1. Replaced `crypto-browserify` with `browserify-aes`, which is a dependency of crypto-browserify; without all the unnecessary modules. We are still using `browserify-aes` because the keythereum's related api methods are synchronous - ethereum-cryptography/aes exposes async-only methods. 18 | 2. Replaced `keccak` with `ethereum-cryptography/keccak` 19 | 3. Replaced `scrypt-js` with `ethereum-cryptography/scrypt` 20 | 4. Replaced `secp256k1` with `ethereum-cryptography/secp256k1-compat` 21 | 5. Removed `sjcl` which had pbkdf2 implementation 22 | 6. Updated `uuid` from 3.0.0 to 8.3.2 23 | 7. The package versions are exact, like before - no version ranges 24 | 25 | `wc < dist/keythereum.js` output: 26 | - before `31994 118030 921363` 27 | - after `15243 63140 484003` 28 | 29 | Backwards incompatibilities: 30 | 31 | - BigInt support is now required 32 | - `keythereum.crypto` is now an emulated object instead of `crypto-browserify` stuff 33 | 34 | [2.0.0]: https://github.com/ethereumjs/keythereum/compare/v1.2.0...v2.0.0 35 | 36 | ## [1.2.0] - 2020-09-29 37 | 38 | This is a maintenance release after a longer period with no releases. 39 | See PR [#81](https://github.com/ethereumjs/keythereum/pull/81) for implementation 40 | details. 41 | 42 | **Changes** 43 | 44 | - `scrypt` to [`scrypt-js`](https://github.com/ricmoo/scrypt-js) for a pure js implementation (simplifies some code) 45 | - `keccak` from `1.4.0` to `3.1.0` for node 12 and n-api support 46 | - `secp256k1` from `3.5.0` to `4.0.2` for node 12 and n-api support 47 | - travis ci node versions from `[4, 5, 6, 7, 8]` to `[8, 10, 12, 13, 14]` 48 | - uglify-js to [`terser`](https://github.com/terser/terser) (build was having some trouble with es6 in node_modules) 49 | - browserify from `16.2.2` to `16.5.2` for misc. bug fixes and upgrades. 50 | 51 | [1.2.0]: https://github.com/ethereumjs/keythereum/compare/v1.0.4...v1.2.0 52 | 53 | ## [1.0.4] 54 | 55 | TODO 56 | 57 | ## Older releases: 58 | 59 | - [1.x.x](https://github.com/ethereumjs/keythereum/compare/v1.x.x...v1.y.y) - 20xx-xx-xx 60 | - ... 61 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015: Jack Peterson. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # keythereum 2 | 3 | > [!WARNING] 4 | > This package has been deprecated and usage is discouraged for security reasons. 5 | --- 6 | 7 | [![Build Status](https://travis-ci.org/ethereumjs/keythereum.svg?branch=master)](https://travis-ci.org/ethereumjs/keythereum) [![Coverage Status](https://coveralls.io/repos/github/ethereumjs/keythereum/badge.svg?branch=master)](https://coveralls.io/github/ethereumjs/keythereum?branch=master) [![npm version](https://badge.fury.io/js/keythereum.svg)](http://badge.fury.io/js/keythereum) 8 | 9 | Keythereum is a JavaScript tool to generate, import and export Ethereum keys. This provides a simple way to use the same account locally and in web wallets. It can be used for verifiable cold storage wallets. 10 | 11 | Keythereum uses the same key derivation functions (PBKDF2-SHA256 or scrypt), symmetric ciphers (AES-128-CTR or AES-128-CBC), and message authentication codes as [geth](https://github.com/ethereum/go-ethereum). You can export your generated key to file, copy it to your data directory's keystore, and immediately start using it in your local Ethereum client. 12 | 13 | *Note: starting in version 0.5.0, keythereum's `encrypt` and `decrypt` functions both return Buffers instead of strings. This is a breaking change for anyone using these functions directly!* 14 | 15 | ## Installation 16 | 17 | ``` 18 | npm install keythereum 19 | ``` 20 | 21 | ## Usage 22 | 23 | To use keythereum in Node.js, just `require` it: 24 | 25 | ```javascript 26 | var keythereum = require("keythereum"); 27 | ``` 28 | 29 | A minified, browserified file `dist/keythereum.min.js` is included for use in the browser. Including this file simply attaches the `keythereum` object to `window`: 30 | 31 | ```html 32 | 33 | ``` 34 | 35 | ### Key creation 36 | 37 | Generate a new random private key (256 bit), as well as the salt (256 bit) used by the key derivation function, and the initialization vector (128 bit) used to AES-128-CTR encrypt the key. `create` is asynchronous if it is passed a callback function, and synchronous otherwise. 38 | 39 | ```javascript 40 | // optional private key and initialization vector sizes in bytes 41 | // (if params is not passed to create, keythereum.constants is used by default) 42 | var params = { keyBytes: 32, ivBytes: 16 }; 43 | 44 | // synchronous 45 | var dk = keythereum.create(params); 46 | // dk: 47 | { 48 | privateKey: , 49 | iv: , 50 | salt: 51 | } 52 | 53 | // asynchronous 54 | keythereum.create(params, function (dk) { 55 | // do stuff! 56 | }); 57 | ``` 58 | 59 | ### Key export 60 | 61 | You will need to specify a password and (optionally) a key derivation function. If unspecified, PBKDF2-SHA256 will be used to derive the AES secret key. 62 | 63 | ```javascript 64 | var password = "wheethereum"; 65 | var kdf = "pbkdf2"; // or "scrypt" to use the scrypt kdf 66 | ``` 67 | 68 | The `dump` function is used to export key info to keystore ["secret-storage" format](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition). If a callback function is supplied as the sixth parameter to `dump`, it will run asynchronously: 69 | 70 | ```javascript 71 | // Note: if options is unspecified, the values in keythereum.constants are used. 72 | var options = { 73 | kdf: "pbkdf2", 74 | cipher: "aes-128-ctr", 75 | kdfparams: { 76 | c: 262144, 77 | dklen: 32, 78 | prf: "hmac-sha256" 79 | } 80 | }; 81 | 82 | // synchronous 83 | var keyObject = keythereum.dump(password, dk.privateKey, dk.salt, dk.iv, options); 84 | // keyObject: 85 | { 86 | address: "008aeeda4d805471df9b2a5b0f38a0c3bcba786b", 87 | Crypto: { 88 | cipher: "aes-128-ctr", 89 | ciphertext: "5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46", 90 | cipherparams: { 91 | iv: "6087dab2f9fdbbfaddc31a909735c1e6" 92 | }, 93 | mac: "517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2", 94 | kdf: "pbkdf2", 95 | kdfparams: { 96 | c: 262144, 97 | dklen: 32, 98 | prf: "hmac-sha256", 99 | salt: "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd" 100 | } 101 | }, 102 | id: "e13b209c-3b2f-4327-bab0-3bef2e51630d", 103 | version: 3 104 | } 105 | 106 | // asynchronous 107 | keythereum.dump(password, dk.privateKey, dk.salt, dk.iv, options, function (keyObject) { 108 | // do stuff! 109 | }); 110 | ``` 111 | 112 | `dump` creates an object and not a JSON string. In Node, the `exportToFile` method provides an easy way to export this formatted key object to file. It creates a JSON file in the `keystore` sub-directory, and uses geth's current file-naming convention (ISO timestamp concatenated with the key's derived Ethereum address). 113 | 114 | ```javascript 115 | keythereum.exportToFile(keyObject); 116 | ``` 117 | 118 | After successful key export, you will see a message like: 119 | 120 | ``` 121 | Saved to file: 122 | keystore/UTC--2015-08-11T06:13:53.359Z--008aeeda4d805471df9b2a5b0f38a0c3bcba786b 123 | 124 | To use with geth, copy this file to your Ethereum keystore folder 125 | (usually ~/.ethereum/keystore). 126 | ``` 127 | 128 | ### Key import 129 | 130 | Importing a key from geth's keystore can only be done on Node. The JSON file is parsed into an object with the same structure as `keyObject` above. 131 | 132 | ```javascript 133 | // Specify a data directory (optional; defaults to ~/.ethereum) 134 | var datadir = "/home/jack/.ethereum-test"; 135 | 136 | // Synchronous 137 | var keyObject = keythereum.importFromFile(address, datadir); 138 | 139 | // Asynchronous 140 | keythereum.importFromFile(address, datadir, function (keyObject) { 141 | // do stuff 142 | }); 143 | ``` 144 | This has been tested with version 3 and version 1, but not version 2, keys. (Please send me a version 2 keystore file if you have one, so I can test it!) 145 | 146 | To recover the plaintext private key from the key object, use `keythereum.recover`. The private key is returned as a Buffer. 147 | 148 | ```javascript 149 | // synchronous 150 | var privateKey = keythereum.recover(password, keyObject); 151 | // privateKey: 152 | 153 | 154 | // Asynchronous 155 | keythereum.recover(password, keyObject, function (privateKey) { 156 | // do stuff 157 | }); 158 | ``` 159 | 160 | ### Hashing rounds 161 | 162 | By default, keythereum uses 65536 hashing rounds in its key derivation functions, compared to the 262144 geth uses by default. (Keythereum's JSON output files are still compatible with geth, however, since they tell geth how many rounds to use.) These values are user-editable: `keythereum.constants.pbkdf2.c` is the number of rounds for PBKDF2, and `keythereum.constants.scrypt.n` is the number of rounds for scrypt. 163 | 164 | ## Tests 165 | 166 | Unit tests are in the `test` directory, and can be run with mocha: 167 | 168 | ``` 169 | npm test 170 | ``` 171 | 172 | `test/geth.js` is an integration test, which is run (along with `test/keys.js`) using: 173 | 174 | ``` 175 | npm run geth 176 | ``` 177 | 178 | `geth.js` generates 1000 random private keys, encrypts each key using a randomly-generated passphrase, dumps the encrypted key info to a JSON file, then spawns a geth instance and attempts to unlock each account using its passphrase and JSON file. The passphrases are between 1 and 100 random bytes. Each passphrase is tested in both hexadecimal and base-64 encodings, and with PBKDF2-SHA256 and scrypt key derivation functions. 179 | 180 | By default, the flags passed to geth are: 181 | 182 | ``` 183 | geth --etherbase --unlock --nodiscover --networkid "10101" --port 30304 --rpcport 8547 --datadir test/fixtures --password test/fixtures/.password 184 | ``` 185 | 186 | `test/fixtures/.password` is a file which contains the passphrase. The `.password` file, as well as the JSON key files generated by `geth.js`, are automatically deleted after the test. 187 | 188 | (Note: `geth.js` conducts 4000 tests, each of which can take up to 5 seconds, so running this file can take up to 5.56 hours.) 189 | -------------------------------------------------------------------------------- /exports.js: -------------------------------------------------------------------------------- 1 | var keythereum = global.keythereum || require('./'); 2 | global.keythereum = keythereum; 3 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Create, import, and export ethereum keys. 3 | * @author Jack Peterson (jack@tinybike.net) 4 | */ 5 | 6 | "use strict"; 7 | 8 | var isBrowser = typeof process === "undefined" || !process.nextTick || Boolean(process.browser); 9 | 10 | var aes = require("browserify-aes"); 11 | var scrypt = require("ethereum-cryptography/scrypt"); 12 | var ecpbkdf2 = require("ethereum-cryptography/pbkdf2"); 13 | var _keccak256 = require("ethereum-cryptography/keccak").keccak256; 14 | var random = require("ethereum-cryptography/random"); 15 | var secp256k1 = require("ethereum-cryptography/secp256k1-compat"); 16 | var uuid = require("uuid"); 17 | 18 | function isFunction(f) { 19 | return typeof f === "function"; 20 | } 21 | 22 | function keccak256(buffer) { 23 | return Buffer.from(_keccak256(buffer)); 24 | } 25 | 26 | module.exports = { 27 | 28 | version: "1.1.0", 29 | 30 | browser: isBrowser, 31 | 32 | scrypt: null, 33 | 34 | crypto: { 35 | pbkdf2: function (password, salt, iters, dklen, prf, cb) { 36 | setTimeout(function () { 37 | ecpbkdf2.pbkdf2(password, salt, iters, dklen, prf).then(function (res) { 38 | cb(Buffer.from(res)); 39 | }); 40 | }, 0); 41 | }, 42 | 43 | pbkdf2Sync: function (password, salt, iters, dklen, prf) { 44 | return Buffer.from(ecpbkdf2.pbkdf2Sync(password, salt, iters, dklen, prf)); 45 | }, 46 | 47 | randomBytes: function (bytes) { 48 | return random.getRandomBytesSync(bytes); 49 | } 50 | }, 51 | 52 | constants: { 53 | 54 | // Symmetric cipher for private key encryption 55 | cipher: "aes-128-ctr", 56 | 57 | // Initialization vector size in bytes 58 | ivBytes: 16, 59 | 60 | // ECDSA private key size in bytes 61 | keyBytes: 32, 62 | 63 | // Key derivation function parameters 64 | pbkdf2: { 65 | c: 262144, 66 | dklen: 32, 67 | hash: "sha256", 68 | prf: "hmac-sha256" 69 | }, 70 | scrypt: { 71 | memory: 280000000, 72 | dklen: 32, 73 | n: 262144, 74 | r: 8, 75 | p: 1 76 | } 77 | }, 78 | 79 | /** 80 | * Check whether a string is valid hex. 81 | * @param {string} str String to validate. 82 | * @return {boolean} True if the string is valid hex, false otherwise. 83 | */ 84 | isHex: function (str) { 85 | if (str.length % 2 === 0 && str.match(/^[0-9a-f]+$/i)) return true; 86 | return false; 87 | }, 88 | 89 | /** 90 | * Check whether a string is valid base-64. 91 | * @param {string} str String to validate. 92 | * @return {boolean} True if the string is valid base-64, false otherwise. 93 | */ 94 | isBase64: function (str) { 95 | var index; 96 | if (str.length % 4 > 0 || str.match(/[^0-9a-z+\/=]/i)) return false; 97 | index = str.indexOf("="); 98 | if (index === -1 || str.slice(index).match(/={1,2}/)) return true; 99 | return false; 100 | }, 101 | 102 | /** 103 | * Convert a string to a Buffer. If encoding is not specified, hex-encoding 104 | * will be used if the input is valid hex. If the input is valid base64 but 105 | * not valid hex, base64 will be used. Otherwise, utf8 will be used. 106 | * @param {string} str String to be converted. 107 | * @param {string=} enc Encoding of the input string (optional). 108 | * @return {Buffer} Buffer (bytearray) containing the input data. 109 | */ 110 | str2buf: function (str, enc) { 111 | if (!str || str.constructor !== String) return str; 112 | if (!enc && this.isHex(str)) enc = "hex"; 113 | if (!enc && this.isBase64(str)) enc = "base64"; 114 | return Buffer.from(str, enc); 115 | }, 116 | 117 | /** 118 | * Check if the selected cipher is available. 119 | * @param {string} cipher Encryption algorithm. 120 | * @return {boolean} If available true, otherwise false. 121 | */ 122 | isCipherAvailable: function (cipher) { 123 | return aes.getCiphers().some(function (name) { return name === cipher; }); 124 | }, 125 | 126 | /** 127 | * Symmetric private key encryption using secret (derived) key. 128 | * @param {Buffer|string} plaintext Data to be encrypted. 129 | * @param {Buffer|string} key Secret key. 130 | * @param {Buffer|string} iv Initialization vector. 131 | * @param {string=} algo Encryption algorithm (default: constants.cipher). 132 | * @return {Buffer} Encrypted data. 133 | */ 134 | encrypt: function (plaintext, key, iv, algo) { 135 | var cipher, ciphertext; 136 | algo = algo || this.constants.cipher; 137 | if (!this.isCipherAvailable(algo)) throw new Error(algo + " is not available"); 138 | cipher = aes.createCipheriv(algo, this.str2buf(key), this.str2buf(iv)); 139 | ciphertext = cipher.update(this.str2buf(plaintext)); 140 | return Buffer.concat([ciphertext, cipher.final()]); 141 | }, 142 | 143 | /** 144 | * Symmetric private key decryption using secret (derived) key. 145 | * @param {Buffer|string} ciphertext Data to be decrypted. 146 | * @param {Buffer|string} key Secret key. 147 | * @param {Buffer|string} iv Initialization vector. 148 | * @param {string=} algo Encryption algorithm (default: constants.cipher). 149 | * @return {Buffer} Decrypted data. 150 | */ 151 | decrypt: function (ciphertext, key, iv, algo) { 152 | var decipher, plaintext; 153 | algo = algo || this.constants.cipher; 154 | if (!this.isCipherAvailable(algo)) throw new Error(algo + " is not available"); 155 | decipher = aes.createDecipheriv(algo, this.str2buf(key), this.str2buf(iv)); 156 | plaintext = decipher.update(this.str2buf(ciphertext)); 157 | return Buffer.concat([plaintext, decipher.final()]); 158 | }, 159 | 160 | /** 161 | * Derive Ethereum address from private key. 162 | * @param {Buffer|string} privateKey ECDSA private key. 163 | * @return {string} Hex-encoded Ethereum address. 164 | */ 165 | privateKeyToAddress: function (privateKey) { 166 | var privateKeyBuffer, publicKey; 167 | privateKeyBuffer = this.str2buf(privateKey); 168 | if (privateKeyBuffer.length < 32) { 169 | privateKeyBuffer = Buffer.concat([ 170 | Buffer.alloc(32 - privateKeyBuffer.length, 0), 171 | privateKeyBuffer 172 | ]); 173 | } 174 | publicKey = Buffer.from( 175 | secp256k1.publicKeyCreate(privateKeyBuffer, false).slice(1) 176 | ); 177 | return "0x" + keccak256(publicKey).slice(-20).toString("hex"); 178 | }, 179 | 180 | /** 181 | * Calculate message authentication code from secret (derived) key and 182 | * encrypted text. The MAC is the keccak-256 hash of the byte array 183 | * formed by concatenating the second 16 bytes of the derived key with 184 | * the ciphertext key's contents. 185 | * @param {Buffer|string} derivedKey Secret key derived from password. 186 | * @param {Buffer|string} ciphertext Text encrypted with secret key. 187 | * @return {string} Hex-encoded MAC. 188 | */ 189 | getMAC: function (derivedKey, ciphertext) { 190 | if (derivedKey !== undefined && derivedKey !== null && ciphertext !== undefined && ciphertext !== null) { 191 | return keccak256(Buffer.concat([ 192 | this.str2buf(derivedKey).slice(16, 32), 193 | this.str2buf(ciphertext) 194 | ])).toString("hex"); 195 | } 196 | }, 197 | 198 | /** 199 | * Used internally. 200 | */ 201 | deriveKeyUsingScrypt: function (password, salt, options, cb) { 202 | var n = options.kdfparams.n || this.constants.scrypt.n; 203 | var r = options.kdfparams.r || this.constants.scrypt.r; 204 | var p = options.kdfparams.p || this.constants.scrypt.p; 205 | var dklen = options.kdfparams.dklen || this.constants.scrypt.dklen; 206 | if (isFunction(cb)) { 207 | scrypt 208 | .scrypt(password, salt, n, p, r, dklen) 209 | .then(function (key) { 210 | cb(Buffer.from(key)); 211 | }) 212 | .catch(cb); 213 | } else { 214 | return Buffer.from(scrypt.scryptSync(password, salt, n, p, r, dklen)); 215 | } 216 | }, 217 | 218 | /** 219 | * Derive secret key from password with key dervation function. 220 | * @param {string|Buffer} password User-supplied password. 221 | * @param {string|Buffer} salt Randomly generated salt. 222 | * @param {Object=} options Encryption parameters. 223 | * @param {string=} options.kdf Key derivation function (default: pbkdf2). 224 | * @param {string=} options.cipher Symmetric cipher (default: constants.cipher). 225 | * @param {Object=} options.kdfparams KDF parameters (default: constants.). 226 | * @param {function=} cb Callback function (optional). 227 | * @return {Buffer} Secret key derived from password. 228 | */ 229 | deriveKey: function (password, salt, options, cb) { 230 | var prf, iters, dklen; 231 | if (typeof password === "undefined" || password === null || !salt) { 232 | throw new Error("Must provide password and salt to derive a key"); 233 | } 234 | options = options || {}; 235 | options.kdfparams = options.kdfparams || {}; 236 | 237 | // convert strings to buffers 238 | password = this.str2buf(password, "utf8"); 239 | salt = this.str2buf(salt); 240 | 241 | // use scrypt as key derivation function 242 | if (options.kdf === "scrypt") { 243 | return this.deriveKeyUsingScrypt(password, salt, options, cb); 244 | } 245 | 246 | // use default key derivation function (PBKDF2) 247 | prf = options.kdfparams.prf || this.constants.pbkdf2.prf; 248 | if (prf === "hmac-sha256") prf = "sha256"; 249 | iters = options.kdfparams.c || this.constants.pbkdf2.c; 250 | dklen = options.kdfparams.dklen || this.constants.pbkdf2.dklen; 251 | if (!isFunction(cb)) { 252 | return Buffer.from(ecpbkdf2.pbkdf2Sync(password, salt, iters, dklen, prf)); 253 | } 254 | setTimeout(function () { 255 | ecpbkdf2.pbkdf2(password, salt, iters, dklen, prf).then(function (res) { 256 | cb(Buffer.from(res)); 257 | }); 258 | }, 0); 259 | }, 260 | 261 | /** 262 | * Generate random numbers for private key, initialization vector, 263 | * and salt (for key derivation). 264 | * @param {Object=} params Encryption options (defaults: constants). 265 | * @param {string=} params.keyBytes Private key size in bytes. 266 | * @param {string=} params.ivBytes Initialization vector size in bytes. 267 | * @param {function=} cb Callback function (optional). 268 | * @return {Object} Private key, IV and salt. 269 | */ 270 | create: function (params, cb) { 271 | var keyBytes, ivBytes, self = this; 272 | params = params || {}; 273 | keyBytes = params.keyBytes || this.constants.keyBytes; 274 | ivBytes = params.ivBytes || this.constants.ivBytes; 275 | 276 | function checkBoundsAndCreateObject(randomBytes) { 277 | var privateKey; 278 | randomBytes = Buffer.from(randomBytes); 279 | privateKey = randomBytes.slice(0, keyBytes); 280 | if (!secp256k1.privateKeyVerify(privateKey)) return self.create(params, cb); 281 | return { 282 | privateKey: privateKey, 283 | iv: randomBytes.slice(keyBytes, keyBytes + ivBytes), 284 | salt: randomBytes.slice(keyBytes + ivBytes) 285 | }; 286 | } 287 | 288 | // synchronous key generation if callback not provided 289 | if (!isFunction(cb)) { 290 | return checkBoundsAndCreateObject(random.getRandomBytesSync(keyBytes + ivBytes + keyBytes)); 291 | } 292 | 293 | // asynchronous key generation 294 | random.getRandomBytes(keyBytes + ivBytes + keyBytes).then(function (randomBytes) { 295 | cb(checkBoundsAndCreateObject(randomBytes)); 296 | }, function (err) { 297 | cb(err); 298 | }); 299 | }, 300 | 301 | /** 302 | * Assemble key data object in secret-storage format. 303 | * @param {Buffer} derivedKey Password-derived secret key. 304 | * @param {Buffer} privateKey Private key. 305 | * @param {Buffer} salt Randomly generated salt. 306 | * @param {Buffer} iv Initialization vector. 307 | * @param {Object=} options Encryption parameters. 308 | * @param {string=} options.kdf Key derivation function (default: pbkdf2). 309 | * @param {string=} options.cipher Symmetric cipher (default: constants.cipher). 310 | * @param {Object=} options.kdfparams KDF parameters (default: constants.). 311 | * @return {Object} 312 | */ 313 | marshal: function (derivedKey, privateKey, salt, iv, options) { 314 | var ciphertext, keyObject, algo; 315 | options = options || {}; 316 | options.kdfparams = options.kdfparams || {}; 317 | algo = options.cipher || this.constants.cipher; 318 | 319 | // encrypt using first 16 bytes of derived key 320 | ciphertext = this.encrypt(privateKey, derivedKey.slice(0, 16), iv, algo).toString("hex"); 321 | 322 | keyObject = { 323 | address: this.privateKeyToAddress(privateKey).slice(2), 324 | crypto: { 325 | cipher: options.cipher || this.constants.cipher, 326 | ciphertext: ciphertext, 327 | cipherparams: { iv: iv.toString("hex") }, 328 | mac: this.getMAC(derivedKey, ciphertext) 329 | }, 330 | id: uuid.v4(), // random 128-bit UUID 331 | version: 3 332 | }; 333 | 334 | if (options.kdf === "scrypt") { 335 | keyObject.crypto.kdf = "scrypt"; 336 | keyObject.crypto.kdfparams = { 337 | dklen: options.kdfparams.dklen || this.constants.scrypt.dklen, 338 | n: options.kdfparams.n || this.constants.scrypt.n, 339 | r: options.kdfparams.r || this.constants.scrypt.r, 340 | p: options.kdfparams.p || this.constants.scrypt.p, 341 | salt: salt.toString("hex") 342 | }; 343 | 344 | } else { 345 | keyObject.crypto.kdf = "pbkdf2"; 346 | keyObject.crypto.kdfparams = { 347 | c: options.kdfparams.c || this.constants.pbkdf2.c, 348 | dklen: options.kdfparams.dklen || this.constants.pbkdf2.dklen, 349 | prf: options.kdfparams.prf || this.constants.pbkdf2.prf, 350 | salt: salt.toString("hex") 351 | }; 352 | } 353 | 354 | return keyObject; 355 | }, 356 | 357 | /** 358 | * Export private key to keystore secret-storage format. 359 | * @param {string|Buffer} password User-supplied password. 360 | * @param {string|Buffer} privateKey Private key. 361 | * @param {string|Buffer} salt Randomly generated salt. 362 | * @param {string|Buffer} iv Initialization vector. 363 | * @param {Object=} options Encryption parameters. 364 | * @param {string=} options.kdf Key derivation function (default: pbkdf2). 365 | * @param {string=} options.cipher Symmetric cipher (default: constants.cipher). 366 | * @param {Object=} options.kdfparams KDF parameters (default: constants.). 367 | * @param {function=} cb Callback function (optional). 368 | * @return {Object} 369 | */ 370 | dump: function (password, privateKey, salt, iv, options, cb) { 371 | options = options || {}; 372 | iv = this.str2buf(iv); 373 | privateKey = this.str2buf(privateKey); 374 | 375 | // synchronous if no callback provided 376 | if (!isFunction(cb)) { 377 | return this.marshal(this.deriveKey(password, salt, options), privateKey, salt, iv, options); 378 | } 379 | 380 | // asynchronous if callback provided 381 | this.deriveKey(password, salt, options, function (derivedKey) { 382 | cb(this.marshal(derivedKey, privateKey, salt, iv, options)); 383 | }.bind(this)); 384 | }, 385 | 386 | /** 387 | * Recover plaintext private key from secret-storage key object. 388 | * @param {string|Buffer} password User-supplied password. 389 | * @param {Object} keyObject Keystore object. 390 | * @param {function=} cb Callback function (optional). 391 | * @return {Buffer} Plaintext private key. 392 | */ 393 | recover: function (password, keyObject, cb) { 394 | var keyObjectCrypto, iv, salt, ciphertext, algo, self = this; 395 | keyObjectCrypto = keyObject.Crypto || keyObject.crypto; 396 | 397 | // verify that message authentication codes match, then decrypt 398 | function verifyAndDecrypt(derivedKey, salt, iv, ciphertext, algo) { 399 | var key; 400 | if (self.getMAC(derivedKey, ciphertext) !== keyObjectCrypto.mac) { 401 | throw new Error("message authentication code mismatch"); 402 | } 403 | if (keyObject.version === "1") { 404 | key = keccak256(derivedKey.slice(0, 16)).slice(0, 16); 405 | } else { 406 | key = derivedKey.slice(0, 16); 407 | } 408 | return self.decrypt(ciphertext, key, iv, algo); 409 | } 410 | 411 | iv = this.str2buf(keyObjectCrypto.cipherparams.iv); 412 | salt = this.str2buf(keyObjectCrypto.kdfparams.salt); 413 | ciphertext = this.str2buf(keyObjectCrypto.ciphertext); 414 | algo = keyObjectCrypto.cipher; 415 | 416 | if (keyObjectCrypto.kdf === "pbkdf2" && keyObjectCrypto.kdfparams.prf !== "hmac-sha256") { 417 | throw new Error("PBKDF2 only supported with HMAC-SHA256"); 418 | } 419 | 420 | // derive secret key from password 421 | if (!isFunction(cb)) { 422 | return verifyAndDecrypt(this.deriveKey(password, salt, keyObjectCrypto), salt, iv, ciphertext, algo); 423 | } 424 | this.deriveKey(password, salt, keyObjectCrypto, function (derivedKey) { 425 | try { 426 | cb(verifyAndDecrypt(derivedKey, salt, iv, ciphertext, algo)); 427 | } catch (exc) { 428 | cb(exc); 429 | } 430 | }); 431 | }, 432 | 433 | /** 434 | * Generate filename for a keystore file. 435 | * @param {string} address Ethereum address. 436 | * @return {string} Keystore filename. 437 | */ 438 | generateKeystoreFilename: function (address) { 439 | var filename = "UTC--" + new Date().toISOString() + "--" + address; 440 | 441 | // Windows does not permit ":" in filenames, replace all with "-" 442 | if (process.platform === "win32") filename = filename.split(":").join("-"); 443 | 444 | return filename; 445 | }, 446 | 447 | /** 448 | * Export formatted JSON to keystore file. 449 | * @param {Object} keyObject Keystore object. 450 | * @param {string=} keystore Path to keystore folder (default: "keystore"). 451 | * @param {function=} cb Callback function (optional). 452 | * @return {string} JSON filename (Node.js) or JSON string (browser). 453 | */ 454 | exportToFile: function (keyObject, keystore, cb) { 455 | var outfile, outpath, json, fs; 456 | keystore = keystore || "keystore"; 457 | outfile = this.generateKeystoreFilename(keyObject.address); 458 | json = JSON.stringify(keyObject); 459 | if (this.browser) { 460 | if (!isFunction(cb)) return json; 461 | return cb(json); 462 | } 463 | outpath = require("path").join(keystore, outfile); 464 | fs = require("fs"); 465 | if (!isFunction(cb)) { 466 | fs.writeFileSync(outpath, json); 467 | return outpath; 468 | } 469 | fs.writeFile(outpath, json, function (err) { 470 | if (err) return cb(err); 471 | cb(outpath); 472 | }); 473 | }, 474 | 475 | /** 476 | * Import key data object from keystore JSON file. 477 | * (Note: Node.js only!) 478 | * @param {string} address Ethereum address to import. 479 | * @param {string=} datadir Ethereum data directory (default: ~/.ethereum). 480 | * @param {function=} cb Callback function (optional). 481 | * @return {Object} Keystore data file's contents. 482 | */ 483 | importFromFile: function (address, datadir, cb) { 484 | var keystore, filepath, path, fs; 485 | if (this.browser) throw new Error("method only available in Node.js"); 486 | path = require("path"); 487 | fs = require("fs"); 488 | address = address.replace("0x", ""); 489 | address = address.toLowerCase(); 490 | 491 | function findKeyfile(keystore, address, files) { 492 | var i, len, filepath = null; 493 | for (i = 0, len = files.length; i < len; ++i) { 494 | if (files[i].indexOf(address) > -1) { 495 | filepath = path.join(keystore, files[i]); 496 | if (fs.lstatSync(filepath).isDirectory()) { 497 | filepath = path.join(filepath, files[i]); 498 | } 499 | break; 500 | } 501 | } 502 | return filepath; 503 | } 504 | 505 | datadir = datadir || path.join(process.env.HOME, ".ethereum"); 506 | keystore = path.join(datadir, "keystore"); 507 | if (!isFunction(cb)) { 508 | filepath = findKeyfile(keystore, address, fs.readdirSync(keystore)); 509 | if (!filepath) { 510 | throw new Error("could not find key file for address " + address); 511 | } 512 | return JSON.parse(fs.readFileSync(filepath)); 513 | } 514 | fs.readdir(keystore, function (ex, files) { 515 | var filepath; 516 | if (ex) return cb(ex); 517 | filepath = findKeyfile(keystore, address, files); 518 | if (!filepath) { 519 | return cb(new Error("could not find key file for address " + address)); 520 | } 521 | return cb(JSON.parse(fs.readFileSync(filepath))); 522 | }); 523 | } 524 | 525 | }; 526 | -------------------------------------------------------------------------------- /keystore/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereumjs/keythereum/70f20aa5c00479e77177433d0b9a2d7b64fa5d28/keystore/.gitkeep -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "keythereum", 3 | "version": "2.0.0", 4 | "description": "Create, import and export Ethereum keys", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "mocha test/keys.js", 11 | "geth": "mocha -R progress test/keys.js && mocha -R progress test/geth.js", 12 | "lint": "eslint index.js && eslint gulpfile.js && eslint test/*.js", 13 | "coverage": "istanbul cover ./node_modules/mocha/bin/_mocha test/keys.js", 14 | "build": "browserify ./exports.js > ./dist/keythereum.js && terser ./dist/keythereum.js > ./dist/keythereum.min.js", 15 | "build:tests": "browserify test/keys.js > test/browser/bundle.js" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/ethereumjs/keythereum.git" 20 | }, 21 | "author": "Jack Peterson ", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/ethereumjs/keythereum/issues" 25 | }, 26 | "homepage": "https://github.com/ethereumjs/keythereum#readme", 27 | "dependencies": { 28 | "browserify-aes": "1.2.0", 29 | "ethereum-cryptography": "1.1.2", 30 | "uuid": "8.3.2" 31 | }, 32 | "devDependencies": { 33 | "browserify": "16.5.2", 34 | "chai": "4.1.2", 35 | "coveralls": "3.0.2", 36 | "eslint": "4.19.1", 37 | "geth": "0.2.2", 38 | "istanbul": "0.4.5", 39 | "mocha": "5.1.1", 40 | "terser": "5.0.0", 41 | "validator": "7.0.0" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/browser/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | keythereum tests 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /test/checkKeyObj.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var assert = require("chai").assert; 4 | var isUUID = require("validator").isUUID; 5 | var isHex = require("../").isHex; 6 | 7 | module.exports = { 8 | 9 | structure: function (keythereum, keyObject) { 10 | var keyObjectCrypto = keyObject.Crypto || keyObject.crypto; 11 | assert.instanceOf(keyObject, Object); 12 | assert.property(keyObject, "address"); 13 | assert(keyObject.Crypto || keyObject.crypto); 14 | assert.instanceOf(keyObjectCrypto, Object); 15 | assert.property(keyObjectCrypto, "cipher"); 16 | assert( 17 | keyObjectCrypto.cipher === "aes-128-ctr" || 18 | keyObjectCrypto.cipher === "aes-128-cbc" 19 | ); 20 | assert.property(keyObjectCrypto, "cipherparams"); 21 | assert.instanceOf(keyObjectCrypto.cipherparams, Object); 22 | assert.property(keyObjectCrypto.cipherparams, "iv"); 23 | assert.strictEqual(keyObjectCrypto.cipherparams.iv.length, 32); 24 | assert.property(keyObjectCrypto, "ciphertext"); 25 | assert(keyObjectCrypto.ciphertext.length >= 64); 26 | assert.isTrue(isHex(keyObjectCrypto.ciphertext)); 27 | assert.property(keyObjectCrypto, "kdf"); 28 | assert(keyObjectCrypto.kdf === "pbkdf2" || keyObjectCrypto.kdf === "scrypt"); 29 | assert.property(keyObjectCrypto, "kdfparams"); 30 | assert.instanceOf(keyObjectCrypto.kdfparams, Object); 31 | if (keyObjectCrypto.kdf === "pbkdf2") { 32 | assert.property(keyObjectCrypto.kdfparams, "c"); 33 | assert.property(keyObjectCrypto.kdfparams, "prf"); 34 | assert.strictEqual(keyObjectCrypto.kdfparams.prf, "hmac-sha256"); 35 | } else { 36 | assert.property(keyObjectCrypto.kdfparams, "n"); 37 | assert.property(keyObjectCrypto.kdfparams, "r"); 38 | assert.property(keyObjectCrypto.kdfparams, "p"); 39 | } 40 | assert.property(keyObjectCrypto.kdfparams, "dklen"); 41 | assert.isNumber(keyObjectCrypto.kdfparams.dklen); 42 | assert(keyObjectCrypto.kdfparams.dklen >= 32); 43 | assert.property(keyObjectCrypto.kdfparams, "salt"); 44 | assert(keyObjectCrypto.kdfparams.salt.length >= 32); 45 | assert.isTrue(isHex(keyObjectCrypto.kdfparams.salt)); 46 | assert.property(keyObjectCrypto, "mac"); 47 | assert.strictEqual(keyObjectCrypto.mac.length, 64); 48 | assert.isTrue(isHex(keyObjectCrypto.mac)); 49 | assert.property(keyObject, "id"); 50 | assert.strictEqual(keyObject.id.length, 36); 51 | assert.isTrue(isUUID(keyObject.id)); 52 | assert.property(keyObject, "version"); 53 | assert(keyObject.version === "1" || keyObject.version === 3); 54 | }, 55 | 56 | values: function (keythereum, t, keyObject) { 57 | var keyObjectCrypto = keyObject.Crypto || keyObject.crypto; 58 | assert.strictEqual(keyObject.address, t.expected.address); 59 | assert.strictEqual( 60 | keyObjectCrypto.cipher, 61 | t.expected.crypto.cipher 62 | ); 63 | if (t.input.iv) { 64 | assert.strictEqual( 65 | keyObjectCrypto.cipherparams.iv, 66 | t.input.iv.toString("hex") 67 | ); 68 | } 69 | assert.strictEqual( 70 | keyObjectCrypto.cipherparams.iv, 71 | t.expected.crypto.cipherparams.iv 72 | ); 73 | assert.strictEqual( 74 | keyObjectCrypto.ciphertext, 75 | t.expected.crypto.ciphertext 76 | ); 77 | assert.strictEqual( 78 | keyObjectCrypto.kdf, 79 | t.expected.crypto.kdf 80 | ); 81 | if (t.input.kdf) { 82 | if (t.input.kdf === "scrypt") { 83 | assert.strictEqual( 84 | keyObjectCrypto.kdfparams.n, 85 | t.expected.crypto.kdfparams.n 86 | ); 87 | assert.strictEqual( 88 | keyObjectCrypto.kdfparams.r, 89 | t.expected.crypto.kdfparams.r 90 | ); 91 | assert.strictEqual( 92 | keyObjectCrypto.kdfparams.p, 93 | t.expected.crypto.kdfparams.p 94 | ); 95 | } else { 96 | assert.strictEqual( 97 | keyObjectCrypto.kdfparams.c, 98 | t.expected.crypto.kdfparams.c 99 | ); 100 | assert.strictEqual( 101 | keyObjectCrypto.kdfparams.c, 102 | keythereum.constants.pbkdf2.c 103 | ); 104 | assert.strictEqual( 105 | keyObjectCrypto.kdfparams.prf, 106 | t.expected.crypto.kdfparams.prf 107 | ); 108 | assert.strictEqual( 109 | keyObjectCrypto.kdfparams.prf, 110 | keythereum.constants.pbkdf2.prf 111 | ); 112 | } 113 | assert.strictEqual( 114 | keyObjectCrypto.kdfparams.dklen, 115 | t.expected.crypto.kdfparams.dklen 116 | ); 117 | assert.strictEqual( 118 | keyObjectCrypto.kdfparams.dklen, 119 | keythereum.constants.pbkdf2.dklen 120 | ); 121 | assert.strictEqual( 122 | keyObjectCrypto.kdfparams.salt, 123 | t.expected.crypto.kdfparams.salt 124 | ); 125 | } 126 | assert.strictEqual( 127 | keyObjectCrypto.mac, 128 | t.expected.crypto.mac 129 | ); 130 | assert.strictEqual( 131 | keyObject.version, 132 | t.expected.version 133 | ); 134 | } 135 | }; 136 | -------------------------------------------------------------------------------- /test/fixtures/keystore/2c97f31d2db40aa57d0e6ca5fa8aedf7d99592db/2c97f31d2db40aa57d0e6ca5fa8aedf7d99592db: -------------------------------------------------------------------------------- 1 | {"address":"2c97f31d2db40aa57d0e6ca5fa8aedf7d99592db","Crypto":{"cipher":"aes-128-cbc","ciphertext":"b0d4523d2c49dcb0134fc5cd341e46099af70c32dbec776bf2d9665b8a5b1539ada61d1fe4962f4f536e1b980928e462","cipherparams":{"iv":"e00bc9b2a963b7491a8fb6bb2750bea0"},"kdf":"scrypt","kdfparams":{"n":262144,"r":8,"p":1,"dklen":32,"salt":"ea373fd764ef47f9ae28ea59824000e9d4f4dab89fa52502ee3c1cfe03582c87"},"mac":"3bfb8637cec761c2d7dd96f09d7eafaa39120360932cee9e2f6701efbe6426fb","version":"1"},"id":"5790f0a7-56ae-44b5-9b75-9fe694d6bc54","version":"1"} -------------------------------------------------------------------------------- /test/fixtures/keystore/UTC--2015-08-11T05-46-48.706837816Z--5a79b93487966d0eafb5264ca0408e66b7db9269: -------------------------------------------------------------------------------- 1 | {"address":"5a79b93487966d0eafb5264ca0408e66b7db9269","Crypto":{"cipher":"aes-128-ctr","ciphertext":"07f5ba9d3a90b8c33f57e903bba7541d42ccc1676a38195c65ff936e2437e7d9","cipherparams":{"iv":"5b65c6eb075c37685c08169b5a4d89d6"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"ff3c29472b4cc9e6e35ffa983fd0cfed6260a373ec9eb3b9ad1a9285a4067d88"},"mac":"aee429e0286079e5081ab4ec3040bfbf88aa38245bfbe9796405d3e1d376398b"},"id":"aa84e172-a45a-4084-ab85-796b04bb719d","version":3} -------------------------------------------------------------------------------- /test/fixtures/keystore/UTC--2015-08-11T05-48-49.615209477Z--00efeeb535b1b1c408cca2ffd55b2b233269728c: -------------------------------------------------------------------------------- 1 | {"address":"00efeeb535b1b1c408cca2ffd55b2b233269728c","Crypto":{"cipher":"aes-128-ctr","ciphertext":"8da6723594a551ca467d24fdfc92e9948505eb97e07be43564e61f9152ca3089","cipherparams":{"iv":"22a4c940f804e32a8dbd9ff4c90c913b"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"b55a4440b57210c0bafdcc5422c9b9d04e9bd7ab1e3dccaf51be838e6aa7c037"},"mac":"57d910c27c3ae13957062b8a3ac620cdbe27ed4e69292a852e072a4926e2eacf"},"id":"2a60191c-b718-4522-b487-fb7de1ad021f","version":3} -------------------------------------------------------------------------------- /test/fixtures/keystore/UTC--2015-08-13T03:26:11.386Z--c9a9adc70a9cbf077ae4bd0a170d88592914e0cc: -------------------------------------------------------------------------------- 1 | {"address":"c9a9adc70a9cbf077ae4bd0a170d88592914e0cc","Crypto":{"cipher":"aes-128-ctr","ciphertext":"92d71fb22fd51f54837c61ba3c03a511d26505dfdc72fc6425deaead0103ec5b","cipherparams":{"iv":"306e7a27057d7d3f350de0aa90239ca9"},"mac":"833a5d2de213523b87b3303799f7a5d74a3dba7158bb4669d1761b473ef67fc1","kdf":"pbkdf2","kdfparams":{"c":65536,"dklen":32,"prf":"hmac-sha256","salt":"c91893bdae79b1115405e3a546718227a2cc61a1528e5b53e467ad29a8225a7a"}},"id":"b34cf55b-4781-48f6-a321-6b1388aa5a4d","version":3} -------------------------------------------------------------------------------- /test/fixtures/keystore/UTC--2015-08-13T04:57:57.228Z--008aeeda4d805471df9b2a5b0f38a0c3bcba786b: -------------------------------------------------------------------------------- 1 | {"address":"008aeeda4d805471df9b2a5b0f38a0c3bcba786b","Crypto":{"cipher":"aes-128-ctr","ciphertext":"5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46","cipherparams":{"iv":"6087dab2f9fdbbfaddc31a909735c1e6"},"mac":"517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2","kdf":"pbkdf2","kdfparams":{"c":262144,"dklen":32,"prf":"hmac-sha256","salt":"ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd"}},"id":"e13b209c-3b2f-4327-bab0-3bef2e51630d","version":3} -------------------------------------------------------------------------------- /test/fixtures/keystore/ebb117ef11769e675e0245062a8e6296dfe42da4/ebb117ef11769e675e0245062a8e6296dfe42da4: -------------------------------------------------------------------------------- 1 | {"address":"ebb117ef11769e675e0245062a8e6296dfe42da4","Crypto":{"cipher":"aes-128-cbc","ciphertext":"edfa88ba7e67f26dd846e17fe5f1cabc0ef618949a5150287ac86b19dade146fb93df12716ae7e1b881f844738d60404","cipherparams":{"iv":"5d99a672d1ecc115671b75f4e852f573"},"kdf":"scrypt","kdfparams":{"n":262144,"r":8,"p":1,"dklen":32,"salt":"231d12dd08d728db6705a73f460eaa61650c39fc12ac266f6ccd577bd3f7cc74"},"mac":"ebe0dcc2e12a28a0b4a6040ec0198ed856ccf9f82718b989faee1e22626c36df","version":"1"},"id":"294724c7-8508-496d-8fdf-eef62872bc10","version":"1"} -------------------------------------------------------------------------------- /test/fixtures/keystore/f0c4ee355432a7c7da12bdef04543723d110d591/f0c4ee355432a7c7da12bdef04543723d110d591: -------------------------------------------------------------------------------- 1 | {"address":"f0c4ee355432a7c7da12bdef04543723d110d591","Crypto":{"cipher":"aes-128-cbc","ciphertext":"5dcd8d2678a492a88a5d4929e51016accf8cd5d3831989a85011642a463e24656c41e43159e9a35e978b79355dcb052c","cipherparams":{"iv":"bda427191686ac4455142bc449543129"},"kdf":"scrypt","kdfparams":{"n":262144,"r":8,"p":1,"dklen":32,"salt":"98e3f47b814f5a55a2298cf92a2572a047c31d30c6b8bb4d1e5f60cc4a437653"},"mac":"b2d8ef9d23fae559257bb52205b490776de6c94465d8947ecfbab9807604fb07","version":"1"},"id":"b5d5ef3a-d42e-4eeb-86ae-51a89131e38e","version":"1"} -------------------------------------------------------------------------------- /test/geth.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | 3 | "use strict"; 4 | 5 | var fs = require("fs"); 6 | var join = require("path").join; 7 | var crypto = require("crypto"); 8 | var assert = require("chai").assert; 9 | var geth = require("geth"); 10 | var keythereum = require("../"); 11 | var checkKeyObj = require("./checkKeyObj"); 12 | 13 | var NUM_TESTS = 1000; 14 | var TIMEOUT = 10000; 15 | var DATADIR = join(__dirname, "fixtures"); 16 | 17 | var options = { 18 | persist: false, 19 | flags: { 20 | networkid: "10101", 21 | port: 30304, 22 | rpcport: 8547, 23 | nodiscover: null, 24 | datadir: DATADIR, 25 | ipcpath: join(DATADIR, "geth.ipc"), 26 | password: join(DATADIR, ".password") 27 | } 28 | }; 29 | 30 | var pbkdf2 = keythereum.crypto.pbkdf2; 31 | var pbkdf2Sync = keythereum.crypto.pbkdf2Sync; 32 | 33 | // geth.debug = true; 34 | 35 | function createEthereumKey(passphrase) { 36 | var dk = keythereum.create(); 37 | var key = keythereum.dump(passphrase, dk.privateKey, dk.salt, dk.iv); 38 | return JSON.stringify(key); 39 | } 40 | 41 | keythereum.constants.quiet = true; 42 | 43 | describe("Unlock randomly-generated accounts in geth", function () { 44 | var password, hashRounds, i; 45 | 46 | var test = function (t) { 47 | 48 | var label = "[" + t.kdf + " | " + t.hashRounds + " rounds] generate key file using password '" + t.password +"'"; 49 | 50 | it(label, function (done) { 51 | var json, keyObject; 52 | this.timeout(TIMEOUT*2); 53 | 54 | if (t.sjcl) { 55 | keythereum.crypto.pbkdf2 = undefined; 56 | keythereum.crypto.pbkdf2Sync = undefined; 57 | } else { 58 | keythereum.crypto.pbkdf2 = pbkdf2; 59 | keythereum.crypto.pbkdf2Sync = pbkdf2Sync; 60 | } 61 | 62 | json = createEthereumKey(t.password); 63 | assert.isNotNull(json); 64 | 65 | keyObject = JSON.parse(json); 66 | assert.isObject(keyObject); 67 | checkKeyObj.structure(keythereum, keyObject); 68 | 69 | keythereum.exportToFile(keyObject, join(DATADIR, "keystore"), function (keypath) { 70 | fs.writeFile(options.flags.password, t.password, function (ex) { 71 | var fail; 72 | if (ex) return done(ex); 73 | options.flags.unlock = keyObject.address; 74 | options.flags.etherbase = keyObject.address; 75 | geth.start(options, { 76 | stderr: function (data) { 77 | if (geth.debug) process.stdout.write(data); 78 | if (data.toString().indexOf("16MB") > -1) { 79 | geth.trigger(null, geth.proc); 80 | } 81 | }, 82 | close: function () { 83 | fs.unlink(options.flags.password, function (exc) { 84 | if (exc) return done(exc); 85 | fs.unlink(keypath, function (exc) { 86 | if (exc) return done(exc); 87 | done(fail); 88 | }); 89 | }); 90 | } 91 | }, function (err, spawned) { 92 | if (err) return done(err); 93 | if (!spawned) return done(new Error("where's the geth?")); 94 | geth.stdout("data", function (data) { 95 | var unlocked = "Account '" + keyObject.address+ 96 | "' (" + keyObject.address + ") unlocked."; 97 | if (data.toString().indexOf(unlocked) > -1) { 98 | geth.stop(); 99 | } 100 | }); 101 | geth.stderr("data", function (data) { 102 | if (data.toString().indexOf("Fatal") > -1) { 103 | fail = new Error(data); 104 | geth.stop(); 105 | } 106 | }); 107 | }); 108 | }); 109 | }); 110 | }); 111 | }; 112 | 113 | for (i = 0; i < NUM_TESTS; ++i) { 114 | 115 | password = crypto.randomBytes(Math.ceil(Math.random()*100)); 116 | hashRounds = Math.ceil(Math.random() * 300000); 117 | 118 | keythereum.constants.pbkdf2.c = hashRounds; 119 | keythereum.constants.scrypt.n = hashRounds; 120 | 121 | test({ 122 | sjcl: false, 123 | password: password.toString("hex"), 124 | hashRounds: hashRounds, 125 | kdf: "pbkdf2" 126 | }); 127 | test({ 128 | sjcl: true, 129 | password: password.toString("hex"), 130 | hashRounds: hashRounds, 131 | kdf: "pbkdf2" 132 | }); 133 | test({ 134 | password: password.toString("base64"), 135 | hashRounds: hashRounds, 136 | kdf: "scrypt" 137 | }); 138 | 139 | test({ 140 | sjcl: false, 141 | password: password.toString("hex"), 142 | hashRounds: hashRounds, 143 | kdf: "pbkdf2" 144 | }); 145 | test({ 146 | sjcl: true, 147 | password: password.toString("hex"), 148 | hashRounds: hashRounds, 149 | kdf: "pbkdf2" 150 | }); 151 | test({ 152 | password: password.toString("base64"), 153 | hashRounds: hashRounds, 154 | kdf: "scrypt" 155 | }); 156 | } 157 | 158 | }); 159 | -------------------------------------------------------------------------------- /test/keys.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | 3 | "use strict"; 4 | 5 | var fs = require("fs"); 6 | var path = require("path"); 7 | var crypto = require("crypto"); 8 | var assert = require("chai").assert; 9 | var keythereum = require("../"); 10 | var checkKeyObj = require("./checkKeyObj"); 11 | 12 | // timeout for asynchronous unit tests 13 | var TIMEOUT = 120000; 14 | 15 | // create private key 16 | var privateKey = crypto.randomBytes(32); 17 | 18 | describe("Check if valid hex-encoded string", function () { 19 | var test = function (t) { 20 | it(t.description, function () { 21 | t.assertions(keythereum.isHex(t.s)); 22 | }); 23 | }; 24 | test({ 25 | description: "deadbeef -> true", 26 | s: "deadbeef", 27 | assertions: function (isHex) { 28 | assert.isTrue(isHex); 29 | } 30 | }); 31 | test({ 32 | description: "deadbee -> false", 33 | s: "deadbee", 34 | assertions: function (isHex) { 35 | assert.isFalse(isHex); 36 | } 37 | }); 38 | test({ 39 | description: "dEaDbEeF -> true", 40 | s: "dEaDbEeF", 41 | assertions: function (isHex) { 42 | assert.isTrue(isHex); 43 | } 44 | }); 45 | test({ 46 | description: "123456 -> true", 47 | s: "123456", 48 | assertions: function (isHex) { 49 | assert.isTrue(isHex); 50 | } 51 | }); 52 | test({ 53 | description: "00aa33 -> true", 54 | s: "00aa33", 55 | assertions: function (isHex) { 56 | assert.isTrue(isHex); 57 | } 58 | }); 59 | test({ 60 | description: "0xdEaDbEeF -> false", 61 | s: "0xdEaDbEeF", 62 | assertions: function (isHex) { 63 | assert.isFalse(isHex); 64 | } 65 | }); 66 | test({ 67 | description: ".. -> false", 68 | s: "..", 69 | assertions: function (isHex) { 70 | assert.isFalse(isHex); 71 | } 72 | }); 73 | }); 74 | 75 | describe("Check if valid base64-encoded string", function () { 76 | var test = function (t) { 77 | it(t.description, function () { 78 | t.assertions(keythereum.isBase64(t.s)); 79 | }); 80 | }; 81 | // test cases: https://github.com/chriso/validator.js/blob/master/test/validators.js 82 | [ 83 | "aGVsbG8gd29ybGQ=", 84 | "ZGVhZGIwYg==", 85 | "YWxpdmViZWVm", 86 | "Zg==", 87 | "Zm8=", 88 | "Zm9v", 89 | "Zm9vYg==", 90 | "Zm9vYmE=", 91 | "Zm9vYmFy", 92 | "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdC4=", 93 | "Vml2YW11cyBmZXJtZW50dW0gc2VtcGVyIHBvcnRhLg==", 94 | "U3VzcGVuZGlzc2UgbGVjdHVzIGxlbw==", 95 | "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuMPNS1Ufof9EW/M98FNw" + 96 | "UAKrwflsqVxaxQjBQnHQmiI7Vac40t8x7pIb8gLGV6wL7sBTJiPovJ0V7y7oc0Ye" + 97 | "rhKh0Rm4skP2z/jHwwZICgGzBvA0rH8xlhUiTvcwDCJ0kc+fh35hNt8srZQM4619" + 98 | "FTgB66Xmp4EtVyhpQV+t02g6NzK72oZI0vnAvqhpkxLeLiMCyrI416wHm5Tkukhx" + 99 | "QmcL2a6hNOyu0ixX/x2kSFXApEnVrJ+/IxGyfyw8kf4N2IZpW5nEP847lpfj0SZZ" + 100 | "Fwrd1mnfnDbYohX2zRptLy2ZUn06Qo9pkG5ntvFEPo9bfZeULtjYzIl6K8gJ2uGZ" + 101 | "HQIDAQAB" 102 | ].forEach(function (s) { 103 | test({ 104 | description: s + " -> true", 105 | s: s, 106 | assertions: function (isBase64) { 107 | assert.isTrue(isBase64); 108 | } 109 | }); 110 | }); 111 | [ 112 | "12345", 113 | "", 114 | "Vml2YW11cyBmZXJtZtesting123", 115 | "Zg=", 116 | "Z===", 117 | "Zm=8", 118 | "=m9vYg==", 119 | "Zm9vYmFy====" 120 | ].forEach(function (s) { 121 | test({ 122 | description: s + " -> false", 123 | s: "s", 124 | assertions: function (isBase64) { 125 | assert.isFalse(isBase64); 126 | } 127 | }); 128 | }); 129 | }); 130 | 131 | describe("Convert a string to a Buffer", function () { 132 | var test = function (t) { 133 | it(t.description, function () { 134 | t.assertions(keythereum.str2buf(t.params.str, t.params.enc)); 135 | }); 136 | }; 137 | test({ 138 | description: "[ascii] hello world", 139 | params: { 140 | str: "hello world", 141 | enc: "ascii" 142 | }, 143 | assertions: function (output) { 144 | assert.strictEqual(output.toString("utf8"), "hello world"); 145 | } 146 | }); 147 | test({ 148 | description: "[utf8] hello world", 149 | params: { 150 | str: "hello world", 151 | enc: "utf8" 152 | }, 153 | assertions: function (output) { 154 | assert.strictEqual(output.toString("utf8"), "hello world"); 155 | } 156 | }); 157 | test({ 158 | description: "[hex] 68656c6c6f20776f726c64", 159 | params: { 160 | str: "68656c6c6f20776f726c64", 161 | enc: "hex" 162 | }, 163 | assertions: function (output) { 164 | assert.strictEqual(output.toString("utf8"), "hello world"); 165 | } 166 | }); 167 | test({ 168 | description: "[inferred hex] 68656c6c6f20776f726c64", 169 | params: { 170 | str: "68656c6c6f20776f726c64" 171 | }, 172 | assertions: function (output) { 173 | assert.strictEqual(output.toString("utf8"), "hello world"); 174 | } 175 | }); 176 | test({ 177 | description: "[inferred utf8] hello world", 178 | params: { 179 | str: "hello world" 180 | }, 181 | assertions: function (output) { 182 | assert.strictEqual(output.toString("utf8"), "hello world"); 183 | } 184 | }); 185 | test({ 186 | description: "[inferred utf8] hello", 187 | params: { 188 | str: "hello" 189 | }, 190 | assertions: function (output) { 191 | assert.strictEqual(output.toString("utf8"), "hello"); 192 | } 193 | }); 194 | test({ 195 | description: "[inferred base64] aGVsbG8gd29ybGQ=", 196 | params: { 197 | str: "aGVsbG8gd29ybGQ=" 198 | }, 199 | assertions: function (output) { 200 | assert.strictEqual(output.toString("utf8"), "hello world"); 201 | } 202 | }); 203 | test({ 204 | description: "[inferred base64] ZGVhZGIwYg==", 205 | params: { 206 | str: "ZGVhZGIwYg==" 207 | }, 208 | assertions: function (output) { 209 | assert.strictEqual(output.toString("utf8"), "deadb0b"); 210 | } 211 | }); 212 | test({ 213 | description: "[inferred base64] aGVsbG8gd29ybGQ=", 214 | params: { 215 | str: "aGVsbG8gd29ybGQ=" 216 | }, 217 | assertions: function (output) { 218 | assert.strictEqual(output.toString("utf8"), "hello world"); 219 | } 220 | }); 221 | test({ 222 | description: "[inferred base64] YWxpdmViZWVm", 223 | params: { 224 | str: "YWxpdmViZWVm" 225 | }, 226 | assertions: function (output) { 227 | assert.strictEqual(output.toString("utf8"), "alivebeef"); 228 | } 229 | }); 230 | }); 231 | 232 | describe("Check if selected cipher is available", function () { 233 | var test = function (t) { 234 | it(t.description, function () { 235 | t.assertions(keythereum.isCipherAvailable(t.cipher)); 236 | }); 237 | }; 238 | test({ 239 | description: "aes-128-ctr should be available", 240 | cipher: "aes-128-ctr", 241 | assertions: function (isAvailable) { 242 | assert.isTrue(isAvailable); 243 | } 244 | }); 245 | test({ 246 | description: "aes-128-cbc should be available", 247 | cipher: "aes-128-cbc", 248 | assertions: function (isAvailable) { 249 | assert.isTrue(isAvailable); 250 | } 251 | }); 252 | test({ 253 | description: "roflcipher should not be available", 254 | cipher: "roflcipher", 255 | assertions: function (isAvailable) { 256 | assert.isFalse(isAvailable); 257 | } 258 | }); 259 | }); 260 | 261 | describe("Private key recovery", function () { 262 | 263 | // password used as secret key for aes-256 cipher 264 | var password = "wheethereum"; 265 | var secret = crypto.createHash("sha256").update(password).digest("hex"); 266 | var cipher = crypto.createCipher("aes-256-cbc", secret); 267 | var encryptedPrivateKey = cipher.update(privateKey, "hex", "base64"); 268 | encryptedPrivateKey += cipher.final("base64"); 269 | 270 | // verify private key is recovered by decryption 271 | it(encryptedPrivateKey + " -> " + privateKey.toString("hex"), function () { 272 | var decipher = crypto.createDecipher("aes-256-cbc", secret); 273 | var decryptedPrivateKey = decipher.update(encryptedPrivateKey, "base64", "hex"); 274 | decryptedPrivateKey += decipher.final("hex"); 275 | assert.strictEqual(decryptedPrivateKey, privateKey.toString("hex")); 276 | }); 277 | }); 278 | 279 | describe("Derive Ethereum address from private key", function () { 280 | var test = function (t) { 281 | it(t.description + ": " + t.privateKey, function () { 282 | t.assertions(keythereum.privateKeyToAddress(t.privateKey)); 283 | t.assertions(keythereum.privateKeyToAddress(Buffer.from(t.privateKey, "hex"))); 284 | t.assertions(keythereum.privateKeyToAddress(Buffer.from(t.privateKey, "hex").toString("base64"))); 285 | }); 286 | }; 287 | test({ 288 | description: "32-byte private key", 289 | privateKey: "d1b1178d3529626a1a93e073f65028370d14c7eb0936eb42abef05db6f37ad7d", 290 | assertions: function (address) { 291 | assert.strictEqual(address, "0xcb61d5a9c4896fb9658090b597ef0e7be6f7b67e"); 292 | } 293 | }); 294 | test({ 295 | description: "32-byte private key", 296 | privateKey: "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d", 297 | assertions: function (address) { 298 | assert.strictEqual(address, "0x008aeeda4d805471df9b2a5b0f38a0c3bcba786b"); 299 | } 300 | }); 301 | test({ 302 | description: "32-byte private key", 303 | privateKey: "6445042b8e8cc121fb6a8985606a84b4cb07dac6dfb3633e769ec27dd2370984", 304 | assertions: function (address) { 305 | assert.strictEqual(address, "0xe1e212c353f7a682693c198ba5ff85849f8300cc"); 306 | } 307 | }); 308 | test({ 309 | description: "32-byte private key", 310 | privateKey: "490127c2782fb55943beeb31943ec26f48a9a5121cd7e91799eb354d30d46529", 311 | assertions: function (address) { 312 | assert.strictEqual(address, "0xf0c4ee355432a7c7da12bdef04543723d110d591"); 313 | } 314 | }); 315 | test({ 316 | description: "31-byte private key", 317 | privateKey: "fa7b3db73dc7dfdf8c5fbdb796d741e4488628c41fc4febd9160a866ba0f35", 318 | assertions: function (address) { 319 | assert.strictEqual(address, "0xd1e64e5480bfaf733ba7d48712decb8227797a4e"); 320 | } 321 | }); 322 | test({ 323 | description: "30-byte private key", 324 | privateKey: "81c29e8142bb6a81bef5a92bda7a8328a5c85bb2f9542e76f9b0f94fc018", 325 | assertions: function (address) { 326 | assert.strictEqual(address, "0x31e9d1e6d844bd3a536800ef8d8be6a9975db509"); 327 | } 328 | }); 329 | }); 330 | 331 | describe("Create random private key, salt and initialization vector", function () { 332 | 333 | var test = function (dk, params) { 334 | assert.property(dk, "privateKey"); 335 | assert.isNotNull(dk.privateKey); 336 | assert.instanceOf(dk.privateKey, Buffer); 337 | assert.strictEqual(dk.privateKey.length, params.keyBytes); 338 | 339 | assert.property(dk, "iv"); 340 | assert.isNotNull(dk.iv); 341 | assert.instanceOf(dk.iv, Buffer); 342 | assert.strictEqual(dk.iv.length, params.ivBytes); 343 | 344 | assert.property(dk, "salt"); 345 | assert.isNotNull(dk.salt); 346 | assert.instanceOf(dk.salt, Buffer); 347 | assert.strictEqual(dk.salt.length, params.keyBytes); 348 | }; 349 | 350 | var runtests = function (i) { 351 | var runtest = function (params) { 352 | it("create key " + i + ": " + JSON.stringify(params), function (done) { 353 | 354 | // synchronous 355 | test(keythereum.create(), keythereum.constants); 356 | test(keythereum.create(params), params); 357 | 358 | // asynchronous 359 | keythereum.create(null, function (dk) { 360 | test(dk, keythereum.constants); 361 | keythereum.create(params, function (dk) { 362 | test(dk, params); 363 | done(); 364 | }); 365 | }); 366 | }); 367 | }; 368 | 369 | runtest(keythereum.constants); 370 | runtest({ keyBytes: 32, ivBytes: 16 }); 371 | }; 372 | 373 | var i; 374 | for (i = 0; i < 25; ++i) runtests(i); 375 | }); 376 | 377 | describe("Encryption", function () { 378 | 379 | var test = function (t) { 380 | var label = t.input.cipher + ": " + JSON.stringify(t.input.plaintext)+ 381 | " -> " + t.expected.ciphertext; 382 | it(label, function () { 383 | var oldCipher = keythereum.constants.cipher; 384 | keythereum.constants.cipher = t.input.cipher; 385 | assert.strictEqual( 386 | keythereum.encrypt(t.input.plaintext, t.input.key, t.input.iv).toString("base64"), 387 | t.expected.ciphertext 388 | ); 389 | keythereum.constants.cipher = oldCipher; 390 | }); 391 | }; 392 | 393 | var runtests = function (t) { 394 | test({ 395 | input: { 396 | plaintext: t.plaintext, 397 | key: t.key, 398 | iv: t.iv, 399 | cipher: "aes-128-ctr" 400 | }, 401 | expected: { 402 | ciphertext: t.ciphertext.toString("base64") 403 | } 404 | }); 405 | test({ 406 | input: { 407 | plaintext: t.plaintext.toString("hex"), 408 | key: t.key.toString("hex"), 409 | iv: t.iv.toString("hex"), 410 | cipher: "aes-128-ctr" 411 | }, 412 | expected: { 413 | ciphertext: t.ciphertext.toString("base64") 414 | } 415 | }); 416 | test({ 417 | input: { 418 | plaintext: t.plaintext.toString("base64"), 419 | key: t.key.toString("base64"), 420 | iv: t.iv.toString("base64"), 421 | cipher: "aes-128-ctr" 422 | }, 423 | expected: { 424 | ciphertext: t.ciphertext.toString("base64") 425 | } 426 | }); 427 | }; 428 | 429 | runtests({ 430 | plaintext: Buffer.from( 431 | "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d", 432 | "hex" 433 | ), 434 | ciphertext: Buffer.from( 435 | "5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46", 436 | "hex" 437 | ), 438 | key: Buffer.from("f06d69cdc7da0faffb1008270bca38f5", "hex"), 439 | iv: Buffer.from("6087dab2f9fdbbfaddc31a909735c1e6", "hex") 440 | }); 441 | runtests({ 442 | plaintext: Buffer.from( 443 | "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d", 444 | "hex" 445 | ), 446 | ciphertext: Buffer.from( 447 | "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c", 448 | "hex" 449 | ), 450 | key: Buffer.from("fac192ceb5fd772906bea3e118a69e8b", "hex"), 451 | iv: Buffer.from("83dbcc02d8ccb40e466191a123791e0e", "hex") 452 | }); 453 | }); 454 | 455 | describe("Decryption", function () { 456 | 457 | var test = function (t) { 458 | var label = t.input.cipher + ": " + JSON.stringify(t.input.ciphertext) + " -> " + t.expected.plaintext; 459 | it(label, function () { 460 | var oldCipher = keythereum.constants.cipher; 461 | keythereum.constants.cipher = t.input.cipher; 462 | assert.strictEqual( 463 | keythereum.decrypt(t.input.ciphertext, t.input.key, t.input.iv).toString("hex"), 464 | t.expected.plaintext 465 | ); 466 | keythereum.constants.cipher = oldCipher; 467 | }); 468 | }; 469 | 470 | var runtests = function (t) { 471 | test({ 472 | input: { 473 | ciphertext: t.ciphertext, 474 | key: t.key, 475 | iv: t.iv, 476 | cipher: "aes-128-ctr" 477 | }, 478 | expected: { 479 | plaintext: t.plaintext.toString("hex") 480 | } 481 | }); 482 | test({ 483 | input: { 484 | ciphertext: t.ciphertext.toString("hex"), 485 | key: t.key.toString("hex"), 486 | iv: t.iv.toString("hex"), 487 | cipher: "aes-128-ctr" 488 | }, 489 | expected: { 490 | plaintext: t.plaintext.toString("hex") 491 | } 492 | }); 493 | test({ 494 | input: { 495 | ciphertext: t.ciphertext.toString("base64"), 496 | key: t.key.toString("base64"), 497 | iv: t.iv.toString("base64"), 498 | cipher: "aes-128-ctr" 499 | }, 500 | expected: { 501 | plaintext: t.plaintext.toString("hex") 502 | } 503 | }); 504 | }; 505 | runtests({ 506 | plaintext: Buffer.from( 507 | "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d", 508 | "hex" 509 | ), 510 | ciphertext: Buffer.from( 511 | "5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46", 512 | "hex" 513 | ), 514 | key: Buffer.from("f06d69cdc7da0faffb1008270bca38f5", "hex"), 515 | iv: Buffer.from("6087dab2f9fdbbfaddc31a909735c1e6", "hex") 516 | }); 517 | runtests({ 518 | plaintext: Buffer.from( 519 | "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d", 520 | "hex" 521 | ), 522 | ciphertext: Buffer.from( 523 | "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c", 524 | "hex" 525 | ), 526 | key: Buffer.from("fac192ceb5fd772906bea3e118a69e8b", "hex"), 527 | iv: Buffer.from("83dbcc02d8ccb40e466191a123791e0e", "hex") 528 | }); 529 | }); 530 | 531 | // Test vectors: 532 | // https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition 533 | 534 | describe("Key derivation", function () { 535 | 536 | var test = function (t) { 537 | var pbkdf2, pbkdf2Sync; 538 | 539 | before(function () { 540 | pbkdf2 = keythereum.crypto.pbkdf2; 541 | pbkdf2Sync = keythereum.crypto.pbkdf2Sync; 542 | }); 543 | 544 | after(function () { 545 | keythereum.crypto.pbkdf2 = pbkdf2; 546 | keythereum.crypto.pbkdf2Sync = pbkdf2Sync; 547 | }); 548 | 549 | it("using crypto: " + t.input.kdf, function (done) { 550 | var derivedKey; 551 | this.timeout(TIMEOUT); 552 | keythereum.crypto.pbkdf2 = pbkdf2; 553 | keythereum.crypto.pbkdf2Sync = pbkdf2Sync; 554 | 555 | // synchronous 556 | derivedKey = keythereum.deriveKey( 557 | t.input.password, 558 | t.input.salt, 559 | { kdf: t.input.kdf, kdfparams: t.input.kdfparams } 560 | ); 561 | if (derivedKey.error) return done(derivedKey); 562 | assert.strictEqual(derivedKey.toString("hex"), t.expected); 563 | 564 | // asynchronous 565 | keythereum.deriveKey( 566 | t.input.password, 567 | t.input.salt, 568 | { kdf: t.input.kdf, kdfparams: t.input.kdfparams }, 569 | function (derivedKey) { 570 | if (derivedKey.error) return done(derivedKey); 571 | assert.strictEqual(derivedKey.toString("hex"), t.expected); 572 | done(); 573 | } 574 | ); 575 | }); 576 | it("using sjcl: " + t.input.kdf, function (done) { 577 | var derivedKey; 578 | this.timeout(TIMEOUT); 579 | keythereum.crypto.pbkdf2 = undefined; 580 | keythereum.crypto.pbkdf2Sync = undefined; 581 | 582 | // synchronous 583 | derivedKey = keythereum.deriveKey( 584 | t.input.password, 585 | t.input.salt, 586 | { kdf: t.input.kdf, kdfparams: t.input.kdfparams } 587 | ); 588 | if (derivedKey.error) return done(derivedKey); 589 | assert.strictEqual(derivedKey.toString("hex"), t.expected); 590 | 591 | // asynchronous 592 | keythereum.deriveKey( 593 | t.input.password, 594 | t.input.salt, 595 | { kdf: t.input.kdf, kdfparams: t.input.kdfparams }, 596 | function (derivedKey) { 597 | if (derivedKey.error) return done(derivedKey); 598 | assert.strictEqual(derivedKey.toString("hex"), t.expected); 599 | done(); 600 | } 601 | ); 602 | }); 603 | }; 604 | 605 | test({ 606 | input: { 607 | password: "testpassword", 608 | salt: "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd", 609 | kdf: "pbkdf2-sha256" 610 | }, 611 | expected: "f06d69cdc7da0faffb1008270bca38f5e31891a3a773950e6d0fea48a7188551" 612 | }); 613 | test({ 614 | input: { 615 | password: "testpassword", 616 | salt: "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19", 617 | kdf: "scrypt", 618 | kdfparams: { p: 8, r: 1 } 619 | }, 620 | expected: "fac192ceb5fd772906bea3e118a69e8bbb5cc24229e20d8766fd298291bba6bd" 621 | }); 622 | }); 623 | 624 | describe("Message authentication code", function () { 625 | 626 | var test = function (t) { 627 | it("convert " + JSON.stringify(t.input) + " -> " + t.output, function () { 628 | var mac = keythereum.getMAC(t.input.derivedKey, t.input.ciphertext); 629 | assert.strictEqual(mac, t.output); 630 | }); 631 | }; 632 | 633 | test({ 634 | input: { 635 | derivedKey: "f06d69cdc7da0faffb1008270bca38f5e31891a3a773950e6d0fea48a7188551", 636 | ciphertext: "5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46" 637 | }, 638 | output: "517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2" 639 | }); 640 | test({ 641 | input: { 642 | derivedKey: "fac192ceb5fd772906bea3e118a69e8bbb5cc24229e20d8766fd298291bba6bd", 643 | ciphertext: "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c" 644 | }, 645 | output: "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097" 646 | }); 647 | }); 648 | 649 | describe("Dump private key", function () { 650 | 651 | var test = function (t) { 652 | 653 | it(t.input.kdf, function (done) { 654 | var keyObject; 655 | this.timeout(TIMEOUT); 656 | 657 | // synchronous 658 | keyObject = keythereum.dump( 659 | t.input.password, 660 | t.input.privateKey, 661 | t.input.salt, 662 | t.input.iv, 663 | { kdf: t.input.kdf, kdfparams: t.input.kdfparams } 664 | ); 665 | if (keyObject.error) return done(keyObject); 666 | checkKeyObj.structure(keythereum, keyObject); 667 | checkKeyObj.values(keythereum, t, keyObject); 668 | 669 | // asynchronous 670 | keythereum.dump( 671 | t.input.password, 672 | t.input.privateKey, 673 | t.input.salt, 674 | t.input.iv, 675 | { kdf: t.input.kdf, kdfparams: t.input.kdfparams }, 676 | function (keyObj) { 677 | if (keyObj.error) return done(keyObj); 678 | checkKeyObj.structure(keythereum, keyObj); 679 | checkKeyObj.values(keythereum, t, keyObj); 680 | done(); 681 | } 682 | ); 683 | }); 684 | }; 685 | test({ 686 | input: { 687 | password: "testpassword", 688 | privateKey: Buffer.from( 689 | "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d", 690 | "hex" 691 | ), 692 | salt: "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd", 693 | iv: Buffer.from("6087dab2f9fdbbfaddc31a909735c1e6", "hex"), 694 | kdf: "pbkdf2-sha256" 695 | }, 696 | expected: { 697 | address: "008aeeda4d805471df9b2a5b0f38a0c3bcba786b", 698 | crypto: { 699 | cipher: "aes-128-ctr", 700 | cipherparams: { 701 | iv: "6087dab2f9fdbbfaddc31a909735c1e6" 702 | }, 703 | ciphertext: "5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46", 704 | kdf: "pbkdf2", 705 | kdfparams: { 706 | c: 262144, 707 | dklen: 32, 708 | prf: "hmac-sha256", 709 | salt: "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd" 710 | }, 711 | mac: "517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2" 712 | }, 713 | version: 3 714 | } 715 | }); 716 | test({ 717 | input: { 718 | password: "testpassword", 719 | privateKey: "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d", 720 | salt: "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19", 721 | iv: "83dbcc02d8ccb40e466191a123791e0e", 722 | kdf: "scrypt", 723 | kdfparams: { p: 8, r: 1 } 724 | }, 725 | expected: { 726 | address: "008aeeda4d805471df9b2a5b0f38a0c3bcba786b", 727 | crypto: { 728 | cipher: "aes-128-ctr", 729 | cipherparams: { 730 | iv: "83dbcc02d8ccb40e466191a123791e0e" 731 | }, 732 | ciphertext: "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c", 733 | kdf: "scrypt", 734 | kdfparams: { 735 | dklen: 32, 736 | n: 262144, 737 | r: 1, 738 | p: 8, 739 | salt: "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19" 740 | }, 741 | mac: "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097" 742 | }, 743 | version: 3 744 | } 745 | }); 746 | }); 747 | 748 | describe("Generate keystore filename", function () { 749 | var test = function (t) { 750 | it(t.address, function () { 751 | t.assertions(keythereum.generateKeystoreFilename(t.address)); 752 | }); 753 | }; 754 | test({ 755 | address: "0000000000000000000000000000000000000b0b", 756 | assertions: function (filename) { 757 | var splitFilename = filename.split("--"); 758 | assert.strictEqual(splitFilename.length, 3); 759 | assert.strictEqual(splitFilename[0], "UTC"); 760 | assert.strictEqual(splitFilename[2], "0000000000000000000000000000000000000b0b"); 761 | } 762 | }); 763 | test({ 764 | address: "008aeeda4d805471df9b2a5b0f38a0c3bcba786b", 765 | assertions: function (filename) { 766 | var splitFilename = filename.split("--"); 767 | assert.strictEqual(splitFilename.length, 3); 768 | assert.strictEqual(splitFilename[0], "UTC"); 769 | assert.strictEqual(splitFilename[2], "008aeeda4d805471df9b2a5b0f38a0c3bcba786b"); 770 | } 771 | }); 772 | test({ 773 | address: "c9a9adc70a9cbf077ae4bd0a170d88592914e0cc", 774 | assertions: function (filename) { 775 | var splitFilename = filename.split("--"); 776 | assert.strictEqual(splitFilename.length, 3); 777 | assert.strictEqual(splitFilename[0], "UTC"); 778 | assert.strictEqual(splitFilename[2], "c9a9adc70a9cbf077ae4bd0a170d88592914e0cc"); 779 | } 780 | }); 781 | }); 782 | 783 | describe("Export to file", function () { 784 | 785 | var keyObj; 786 | 787 | if (keythereum.browser) return; 788 | 789 | keyObj = { 790 | address: "008aeeda4d805471df9b2a5b0f38a0c3bcba786b", 791 | crypto: { 792 | cipher: "aes-128-ctr", 793 | ciphertext: "5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46", 794 | cipherparams: { 795 | iv: "6087dab2f9fdbbfaddc31a909735c1e6" 796 | }, 797 | mac: "517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2", 798 | kdf: "pbkdf2", 799 | kdfparams: { 800 | c: 262144, 801 | dklen: 32, 802 | prf: "hmac-sha256", 803 | salt: "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd" 804 | } 805 | }, 806 | id: "e13b209c-3b2f-4327-bab0-3bef2e51630d", 807 | version: 3 808 | }; 809 | 810 | it("export key to json file", function (done) { 811 | var keypath, outfile; 812 | this.timeout(TIMEOUT); 813 | 814 | // synchronous 815 | keypath = keythereum.exportToFile(keyObj); 816 | outfile = keypath.split("/"); 817 | assert.isArray(outfile); 818 | outfile = outfile[outfile.length - 1]; 819 | assert.strictEqual(outfile.slice(0, 5), "UTC--"); 820 | assert.isAbove(outfile.indexOf(keyObj.address), -1); 821 | fs.unlinkSync(keypath); 822 | 823 | // asynchronous 824 | keythereum.exportToFile(keyObj, null, function (keyPath) { 825 | var outFile = keyPath.split("/"); 826 | assert.isArray(outFile); 827 | outFile = outFile[outFile.length - 1]; 828 | assert.strictEqual(outFile.slice(0, 5), "UTC--"); 829 | assert.isAbove(outFile.indexOf(keyObj.address), -1); 830 | fs.unlink(keyPath, function (exc) { 831 | if (exc) return done(exc); 832 | done(); 833 | }); 834 | }); 835 | }); 836 | it("export key to json (browser)", function (done) { 837 | var json; 838 | this.timeout(TIMEOUT); 839 | keythereum.browser = true; 840 | 841 | // synchronous 842 | json = keythereum.exportToFile(keyObj); 843 | assert.strictEqual(json, JSON.stringify(keyObj)); 844 | 845 | // asynchronous 846 | keythereum.exportToFile(keyObj, null, function (json) { 847 | assert.strictEqual(json, JSON.stringify(keyObj)); 848 | keythereum.browser = false; 849 | done(); 850 | }); 851 | }); 852 | }); 853 | 854 | describe("Import from keystore file", function () { 855 | 856 | if (keythereum.browser) return; 857 | 858 | function test(t) { 859 | var label = "[" + t.expected.crypto.kdf + "] import " + t.input.address + " from file"; 860 | it(label, function (done) { 861 | var keyObject; 862 | this.timeout(TIMEOUT); 863 | keyObject = keythereum.importFromFile(t.input.address, t.input.datadir); 864 | checkKeyObj.structure(keythereum, keyObject); 865 | checkKeyObj.values(keythereum, t, keyObject); 866 | keythereum.importFromFile(t.input.address, t.input.datadir, function (keyObj) { 867 | checkKeyObj.structure(keythereum, keyObj); 868 | checkKeyObj.values(keythereum, t, keyObj); 869 | done(); 870 | }); 871 | }); 872 | } 873 | 874 | describe("Version 3", function () { 875 | test({ 876 | input: { 877 | address: "008aeeda4d805471df9b2a5b0f38a0c3bcba786b", 878 | datadir: path.join(__dirname, "fixtures") 879 | }, 880 | expected: { 881 | address: "008aeeda4d805471df9b2a5b0f38a0c3bcba786b", 882 | crypto: { 883 | cipher: "aes-128-ctr", 884 | cipherparams: { 885 | iv: "6087dab2f9fdbbfaddc31a909735c1e6" 886 | }, 887 | ciphertext: "5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46", 888 | kdf: "pbkdf2", 889 | kdfparams: { 890 | c: 262144, 891 | dklen: 32, 892 | prf: "hmac-sha256", 893 | salt: "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd" 894 | }, 895 | mac: "517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2" 896 | }, 897 | id: "e13b209c-3b2f-4327-bab0-3bef2e51630d", 898 | version: 3 899 | } 900 | }); 901 | test({ 902 | input: { 903 | address: "c9a9adc70a9cbf077ae4bd0a170d88592914e0cc", 904 | datadir: path.join(__dirname, "fixtures") 905 | }, 906 | expected: { 907 | address: "c9a9adc70a9cbf077ae4bd0a170d88592914e0cc", 908 | crypto: { 909 | cipher: "aes-128-ctr", 910 | ciphertext: "92d71fb22fd51f54837c61ba3c03a511d26505dfdc72fc6425deaead0103ec5b", 911 | cipherparams: { 912 | iv: "306e7a27057d7d3f350de0aa90239ca9" 913 | }, 914 | mac: "833a5d2de213523b87b3303799f7a5d74a3dba7158bb4669d1761b473ef67fc1", 915 | kdf: "pbkdf2", 916 | kdfparams: { 917 | c: 65536, 918 | dklen: 32, 919 | prf: "hmac-sha256", 920 | salt: "c91893bdae79b1115405e3a546718227a2cc61a1528e5b53e467ad29a8225a7a" 921 | } 922 | }, 923 | id: "b34cf55b-4781-48f6-a321-6b1388aa5a4d", 924 | version: 3 925 | } 926 | }); 927 | test({ 928 | input: { 929 | address: "c9a9adc70a9cbf077ae4bd0a170d88592914e0cc", 930 | datadir: path.join(__dirname, "fixtures") 931 | }, 932 | expected: { 933 | address: "c9a9adc70a9cbf077ae4bd0a170d88592914e0cc", 934 | crypto: { 935 | cipher: "aes-128-ctr", 936 | ciphertext: "92d71fb22fd51f54837c61ba3c03a511d26505dfdc72fc6425deaead0103ec5b", 937 | cipherparams: { 938 | iv: "306e7a27057d7d3f350de0aa90239ca9" 939 | }, 940 | mac: "833a5d2de213523b87b3303799f7a5d74a3dba7158bb4669d1761b473ef67fc1", 941 | kdf: "pbkdf2", 942 | kdfparams: { 943 | c: 65536, 944 | dklen: 32, 945 | prf: "hmac-sha256", 946 | salt: "c91893bdae79b1115405e3a546718227a2cc61a1528e5b53e467ad29a8225a7a" 947 | } 948 | }, 949 | id: "b34cf55b-4781-48f6-a321-6b1388aa5a4d", 950 | version: 3 951 | } 952 | }); 953 | test({ 954 | input: { 955 | address: "00efeeb535b1b1c408cca2ffd55b2b233269728c", 956 | datadir: path.join(__dirname, "fixtures") 957 | }, 958 | expected: { 959 | address: "00efeeb535b1b1c408cca2ffd55b2b233269728c", 960 | crypto: { 961 | cipher: "aes-128-ctr", 962 | ciphertext: "8da6723594a551ca467d24fdfc92e9948505eb97e07be43564e61f9152ca3089", 963 | cipherparams: { 964 | iv: "22a4c940f804e32a8dbd9ff4c90c913b" 965 | }, 966 | kdf: "scrypt", 967 | kdfparams: { 968 | dklen: 32, 969 | n: 262144, 970 | p: 1, 971 | r: 8, 972 | salt: "b55a4440b57210c0bafdcc5422c9b9d04e9bd7ab1e3dccaf51be838e6aa7c037" 973 | }, 974 | mac: "57d910c27c3ae13957062b8a3ac620cdbe27ed4e69292a852e072a4926e2eacf" 975 | }, 976 | id: "2a60191c-b718-4522-b487-fb7de1ad021f", 977 | version: 3 978 | } 979 | }); 980 | test({ 981 | input: { 982 | address: "5a79b93487966d0eafb5264ca0408e66b7db9269", 983 | datadir: path.join(__dirname, "fixtures") 984 | }, 985 | expected: { 986 | address: "5a79b93487966d0eafb5264ca0408e66b7db9269", 987 | crypto: { 988 | cipher: "aes-128-ctr", 989 | ciphertext: "07f5ba9d3a90b8c33f57e903bba7541d42ccc1676a38195c65ff936e2437e7d9", 990 | cipherparams: { 991 | iv: "5b65c6eb075c37685c08169b5a4d89d6" 992 | }, 993 | kdf: "scrypt", 994 | kdfparams: { 995 | dklen: 32, 996 | n: 262144, 997 | p: 1, 998 | r: 8, 999 | salt: "ff3c29472b4cc9e6e35ffa983fd0cfed6260a373ec9eb3b9ad1a9285a4067d88" 1000 | }, 1001 | mac: "aee429e0286079e5081ab4ec3040bfbf88aa38245bfbe9796405d3e1d376398b" 1002 | }, 1003 | id: "aa84e172-a45a-4084-ab85-796b04bb719d", 1004 | version: 3 1005 | } 1006 | }); 1007 | }); 1008 | 1009 | describe("Version 1", function () { 1010 | test({ 1011 | input: { 1012 | address: "ebb117ef11769e675e0245062a8e6296dfe42da4", 1013 | datadir: path.join(__dirname, "fixtures") 1014 | }, 1015 | expected: { 1016 | address: "ebb117ef11769e675e0245062a8e6296dfe42da4", 1017 | crypto: { 1018 | cipher: "aes-128-cbc", 1019 | ciphertext: "edfa88ba7e67f26dd846e17fe5f1cabc0ef618949a5150287ac86b19dade146fb93df12716ae7e1b881f844738d60404", 1020 | cipherparams: { 1021 | iv: "5d99a672d1ecc115671b75f4e852f573" 1022 | }, 1023 | kdf: "scrypt", 1024 | kdfparams: { 1025 | n: 262144, 1026 | r: 8, 1027 | p: 1, 1028 | dklen: 32, 1029 | salt: "231d12dd08d728db6705a73f460eaa61650c39fc12ac266f6ccd577bd3f7cc74" 1030 | }, 1031 | mac: "ebe0dcc2e12a28a0b4a6040ec0198ed856ccf9f82718b989faee1e22626c36df", 1032 | version: "1" 1033 | }, 1034 | id: "294724c7-8508-496d-8fdf-eef62872bc10", 1035 | version: "1" 1036 | } 1037 | }); 1038 | test({ 1039 | input: { 1040 | address: "f0c4ee355432a7c7da12bdef04543723d110d591", 1041 | datadir: path.join(__dirname, "fixtures") 1042 | }, 1043 | expected: { 1044 | address: "f0c4ee355432a7c7da12bdef04543723d110d591", 1045 | crypto: { 1046 | cipher: "aes-128-cbc", 1047 | ciphertext: "5dcd8d2678a492a88a5d4929e51016accf8cd5d3831989a85011642a463e24656c41e43159e9a35e978b79355dcb052c", 1048 | cipherparams: { 1049 | iv: "bda427191686ac4455142bc449543129" 1050 | }, 1051 | kdf: "scrypt", 1052 | kdfparams: { 1053 | n: 262144, 1054 | r: 8, 1055 | p: 1, 1056 | dklen: 32, 1057 | salt: "98e3f47b814f5a55a2298cf92a2572a047c31d30c6b8bb4d1e5f60cc4a437653" 1058 | }, 1059 | mac: "b2d8ef9d23fae559257bb52205b490776de6c94465d8947ecfbab9807604fb07", 1060 | version: "1" 1061 | }, 1062 | id: "b5d5ef3a-d42e-4eeb-86ae-51a89131e38e", 1063 | version: "1" 1064 | } 1065 | }); 1066 | test({ 1067 | input: { 1068 | address: "2c97f31d2db40aa57d0e6ca5fa8aedf7d99592db", 1069 | datadir: path.join(__dirname, "fixtures") 1070 | }, 1071 | expected: { 1072 | address: "2c97f31d2db40aa57d0e6ca5fa8aedf7d99592db", 1073 | crypto: { 1074 | cipher: "aes-128-cbc", 1075 | ciphertext: "b0d4523d2c49dcb0134fc5cd341e46099af70c32dbec776bf2d9665b8a5b1539ada61d1fe4962f4f536e1b980928e462", 1076 | cipherparams: { 1077 | iv: "e00bc9b2a963b7491a8fb6bb2750bea0" 1078 | }, 1079 | kdf: "scrypt", 1080 | kdfparams: { 1081 | n: 262144, 1082 | r: 8, 1083 | p: 1, 1084 | dklen: 32, 1085 | salt: "ea373fd764ef47f9ae28ea59824000e9d4f4dab89fa52502ee3c1cfe03582c87" 1086 | }, 1087 | mac: "3bfb8637cec761c2d7dd96f09d7eafaa39120360932cee9e2f6701efbe6426fb", 1088 | version: "1" 1089 | }, 1090 | id: "5790f0a7-56ae-44b5-9b75-9fe694d6bc54", 1091 | version: "1" 1092 | } 1093 | }); 1094 | }); 1095 | }); 1096 | 1097 | describe("Recover plaintext private key from key object", function () { 1098 | 1099 | var test = function (t) { 1100 | var keyObjectCrypto = t.input.keyObject.Crypto || t.input.keyObject.crypto; 1101 | var label = "[" + keyObjectCrypto.kdf + "] "+ "recover key for " + t.input.keyObject.address; 1102 | it(label, function (done) { 1103 | var dk; 1104 | this.timeout(TIMEOUT); 1105 | 1106 | // synchronous 1107 | dk = keythereum.recover(t.input.password, t.input.keyObject); 1108 | assert.strictEqual(dk.toString("hex"), t.expected); 1109 | 1110 | // asynchronous 1111 | keythereum.recover(t.input.password, t.input.keyObject, function (dk) { 1112 | assert.strictEqual(dk.toString("hex"), t.expected); 1113 | done(); 1114 | }); 1115 | }); 1116 | }; 1117 | 1118 | var foobarKeyObject = { 1119 | address: "7ef5a6135f1fd6a02593eedc869c6d41d934aef8", 1120 | crypto: { 1121 | cipher: "aes-128-ctr", 1122 | ciphertext: "1d0839166e7a15b9c1333fc865d69858b22df26815ccf601b28219b6192974e1", 1123 | cipherparams: { 1124 | iv: "8df6caa7ff1b00c4e871f002cb7921ed" 1125 | }, 1126 | kdf: "scrypt", 1127 | kdfparams: { 1128 | dklen: 32, 1129 | n: 8, 1130 | p: 16, 1131 | r: 8, 1132 | salt: "e5e6ef3f4ea695f496b643ebd3f75c0aa58ef4070e90c80c5d3fb0241bf1595c" 1133 | }, 1134 | mac: "6d16dfde774845e4585357f24bce530528bc69f4f84e1e22880d34fa45c273e5" 1135 | }, 1136 | id: "950077c7-71e3-4c44-a4a1-143919141ed4", 1137 | version: 3 1138 | }; 1139 | 1140 | it("should fail if the password is wrong", function (done) { 1141 | assert.throws(function () { keythereum.recover("barfoo", foobarKeyObject); }, "message authentication code mismatch"); 1142 | keythereum.recover("barfoo", foobarKeyObject, function (err) { 1143 | assert.strictEqual(err.message, "message authentication code mismatch"); 1144 | done(); 1145 | }); 1146 | }); 1147 | 1148 | test({ 1149 | input: { 1150 | password: "foobar", 1151 | keyObject: { 1152 | address: "7ef5a6135f1fd6a02593eedc869c6d41d934aef8", 1153 | crypto: { 1154 | cipher: "aes-128-ctr", 1155 | ciphertext: "1d0839166e7a15b9c1333fc865d69858b22df26815ccf601b28219b6192974e1", 1156 | cipherparams: { 1157 | iv: "8df6caa7ff1b00c4e871f002cb7921ed" 1158 | }, 1159 | kdf: "scrypt", 1160 | kdfparams: { 1161 | dklen: 32, 1162 | n: 8, 1163 | p: 16, 1164 | r: 8, 1165 | salt: "e5e6ef3f4ea695f496b643ebd3f75c0aa58ef4070e90c80c5d3fb0241bf1595c" 1166 | }, 1167 | mac: "6d16dfde774845e4585357f24bce530528bc69f4f84e1e22880d34fa45c273e5" 1168 | }, 1169 | id: "950077c7-71e3-4c44-a4a1-143919141ed4", 1170 | version: 3 1171 | } 1172 | }, 1173 | expected: "976f9f7772781ff6d1c93941129d417c49a209c674056a3cf5e27e225ee55fa8" 1174 | }); 1175 | test({ 1176 | input: { 1177 | password: "foobar", 1178 | keyObject: { 1179 | address: "f466859ead1932d743d622cb74fc058882e8648a", 1180 | crypto: { 1181 | cipher: "aes-128-ctr", 1182 | ciphertext: "cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d", 1183 | cipherparams: { 1184 | iv: "dfd9ee70812add5f4b8f89d0811c9158" 1185 | }, 1186 | kdf: "scrypt", 1187 | kdfparams: { 1188 | dklen: 32, 1189 | n: 8, 1190 | p: 16, 1191 | r: 8, 1192 | salt: "0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1" 1193 | }, 1194 | mac: "bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9" 1195 | }, 1196 | id: "472e8b3d-afb6-45b5-8111-72c89895099a", 1197 | version: 3 1198 | } 1199 | }, 1200 | expected: "539f9b4106fb452408e1ee43d177077f057a8fdc1e1fad92c61e68982b4e3c4b" 1201 | }); 1202 | test({ 1203 | input: { 1204 | password: "g", 1205 | keyObject: { 1206 | address: "cb61d5a9c4896fb9658090b597ef0e7be6f7b67e", 1207 | Crypto: { 1208 | cipher: "aes-128-cbc", 1209 | ciphertext: "6143d3192db8b66eabd693d9c4e414dcfaee52abda451af79ccf474dafb35f1bfc7ea013aa9d2ee35969a1a2e8d752d0", 1210 | cipherparams: { 1211 | iv: "35337770fc2117994ecdcad026bccff4" 1212 | }, 1213 | kdf: "scrypt", 1214 | kdfparams: { 1215 | n: 262144, 1216 | r: 8, 1217 | p: 1, 1218 | dklen: 32, 1219 | salt: "9afcddebca541253a2f4053391c673ff9fe23097cd8555d149d929e4ccf1257f" 1220 | }, 1221 | mac: "3f3d5af884b17a100b0b3232c0636c230a54dc2ac8d986227219b0dd89197644", 1222 | version: "1" 1223 | }, 1224 | id: "e25f7c1f-d318-4f29-b62c-687190d4d299", 1225 | version: "1" 1226 | } 1227 | }, 1228 | expected: "d1b1178d3529626a1a93e073f65028370d14c7eb0936eb42abef05db6f37ad7d" 1229 | }); 1230 | test({ 1231 | input: { 1232 | password: "foo", 1233 | keyObject: { 1234 | address: "d1e64e5480bfaf733ba7d48712decb8227797a4e", 1235 | crypto: { 1236 | cipher: "aes-128-ctr", 1237 | cipherparams: { 1238 | iv: "e0c41130a323adc1446fc82f724bca2f" 1239 | }, 1240 | ciphertext: "9517cd5bdbe69076f9bf5057248c6c050141e970efa36ce53692d5d59a3984", 1241 | kdf: "scrypt", 1242 | kdfparams: { 1243 | dklen: 32, 1244 | n: 2, 1245 | r: 8, 1246 | p: 1, 1247 | salt: "711f816911c92d649fb4c84b047915679933555030b3552c1212609b38208c63" 1248 | }, 1249 | mac: "d5e116151c6aa71470e67a7d42c9620c75c4d23229847dcc127794f0732b0db5" 1250 | }, 1251 | id: "fecfc4ce-e956-48fd-953b-30f8b52ed66c", 1252 | version: 3 1253 | } 1254 | }, 1255 | expected: "fa7b3db73dc7dfdf8c5fbdb796d741e4488628c41fc4febd9160a866ba0f35" 1256 | }); 1257 | test({ 1258 | input: { 1259 | password: "foo", 1260 | keyObject: { 1261 | address: "31e9d1e6d844bd3a536800ef8d8be6a9975db509", 1262 | crypto: { 1263 | cipher: "aes-128-ctr", 1264 | cipherparams: { 1265 | iv: "3ca92af36ad7c2cd92454c59cea5ef00" 1266 | }, 1267 | ciphertext: "108b7d34f3442fc26ab1ab90ca91476ba6bfa8c00975a49ef9051dc675aa", 1268 | kdf: "scrypt", 1269 | kdfparams: { 1270 | dklen: 32, 1271 | n: 2, 1272 | r: 8, 1273 | p: 1, 1274 | salt: "d0769e608fb86cda848065642a9c6fa046845c928175662b8e356c77f914cd3b" 1275 | }, 1276 | mac: "75d0e6759f7b3cefa319c3be41680ab6beea7d8328653474bd06706d4cc67420" 1277 | }, 1278 | id: "a37e1559-5955-450d-8075-7b8931b392b2", 1279 | version: 3 1280 | } 1281 | }, 1282 | expected: "81c29e8142bb6a81bef5a92bda7a8328a5c85bb2f9542e76f9b0f94fc018" 1283 | }); 1284 | test({ 1285 | input: { 1286 | password: "foobar", 1287 | keyObject: { 1288 | address: "289d485d9771714cce91d3393d764e1311907acc", 1289 | crypto: { 1290 | cipher: "aes-128-ctr", 1291 | ciphertext: "faf32ca89d286b107f5e6d842802e05263c49b78d46eac74e6109e9a963378ab", 1292 | cipherparams: { 1293 | iv: "558833eec4a665a8c55608d7d503407d" 1294 | }, 1295 | kdf: "scrypt", 1296 | kdfparams: { 1297 | dklen: 32, 1298 | n: 8, 1299 | p: 16, 1300 | r: 8, 1301 | salt: "d571fff447ffb24314f9513f5160246f09997b857ac71348b73e785aab40dc04" 1302 | }, 1303 | mac: "21edb85ff7d0dab1767b9bf498f2c3cb7be7609490756bd32300bb213b59effe" 1304 | }, 1305 | id: "3279afcf-55ba-43ff-8997-02dcc46a6525", 1306 | version: 3 1307 | } 1308 | }, 1309 | expected: "14a447d8d4c69714f8750e1688feb98857925e1fec6dee7c75f0079d10519d25" 1310 | }); 1311 | test({ 1312 | input: { 1313 | password: "testpassword", 1314 | keyObject: { 1315 | address: "008aeeda4d805471df9b2a5b0f38a0c3bcba786b", 1316 | Crypto: { 1317 | cipher: "aes-128-ctr", 1318 | cipherparams: { 1319 | iv: "6087dab2f9fdbbfaddc31a909735c1e6" 1320 | }, 1321 | ciphertext: "5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46", 1322 | kdf: "pbkdf2", 1323 | kdfparams: { 1324 | c: 262144, 1325 | dklen: 32, 1326 | prf: "hmac-sha256", 1327 | salt: "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd" 1328 | }, 1329 | mac: "517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2" 1330 | }, 1331 | id: "e13b209c-3b2f-4327-bab0-3bef2e51630d", 1332 | version: 3 1333 | } 1334 | }, 1335 | expected: "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d" 1336 | }); 1337 | test({ 1338 | input: { 1339 | password: "testpassword", 1340 | keyObject: { 1341 | address: "008aeeda4d805471df9b2a5b0f38a0c3bcba786b", 1342 | crypto: { 1343 | cipher: "aes-128-ctr", 1344 | cipherparams: { 1345 | iv: "6087dab2f9fdbbfaddc31a909735c1e6" 1346 | }, 1347 | ciphertext: "5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46", 1348 | kdf: "pbkdf2", 1349 | kdfparams: { 1350 | c: 262144, 1351 | dklen: 32, 1352 | prf: "hmac-sha256", 1353 | salt: "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd" 1354 | }, 1355 | mac: "517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2" 1356 | }, 1357 | id: "e13b209c-3b2f-4327-bab0-3bef2e51630d", 1358 | version: 3 1359 | } 1360 | }, 1361 | expected: "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d" 1362 | }); 1363 | test({ 1364 | input: { 1365 | password: "testpassword", 1366 | keyObject: { 1367 | address: "008aeeda4d805471df9b2a5b0f38a0c3bcba786b", 1368 | Crypto: { 1369 | cipher: "aes-128-ctr", 1370 | cipherparams: { 1371 | iv: "83dbcc02d8ccb40e466191a123791e0e" 1372 | }, 1373 | ciphertext: "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c", 1374 | kdf: "scrypt", 1375 | kdfparams: { 1376 | dklen: 32, 1377 | n: 262144, 1378 | r: 1, 1379 | p: 8, 1380 | salt: "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19" 1381 | }, 1382 | mac: "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097" 1383 | }, 1384 | version: 3 1385 | } 1386 | }, 1387 | expected: "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d" 1388 | }); 1389 | test({ 1390 | input: { 1391 | password: "testpassword", 1392 | keyObject: { 1393 | address: "008aeeda4d805471df9b2a5b0f38a0c3bcba786b", 1394 | crypto: { 1395 | cipher: "aes-128-ctr", 1396 | cipherparams: { 1397 | iv: "83dbcc02d8ccb40e466191a123791e0e" 1398 | }, 1399 | ciphertext: "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c", 1400 | kdf: "scrypt", 1401 | kdfparams: { 1402 | dklen: 32, 1403 | n: 262144, 1404 | r: 1, 1405 | p: 8, 1406 | salt: "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19" 1407 | }, 1408 | mac: "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097" 1409 | }, 1410 | version: 3 1411 | } 1412 | }, 1413 | expected: "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d" 1414 | }); 1415 | test({ 1416 | input: { 1417 | password: "testpass", 1418 | keyObject: { 1419 | address: "e1e212c353f7a682693c198ba5ff85849f8300cc", 1420 | Crypto: { 1421 | cipher: "aes-128-ctr", 1422 | ciphertext: "008baf806bb0f855fbc35fcf22cab732315a368e6e6d529b50dcbc60c955d349", 1423 | cipherparams: {iv: "92a01f397d5c2ce4c2964c36a9754f69"}, 1424 | kdf: "scrypt", 1425 | kdfparams: { 1426 | dklen: 32, 1427 | n: 262144, 1428 | p: 1, 1429 | r: 8, 1430 | salt: "7deda03653eb9d767a7feb7ab7ae82a17559954f7ae62fef93f7bc25813c3ccf" 1431 | }, 1432 | mac: "2ff9d7b27b57b856f92b5396819ba18144e434665f945295d2ea3e354c4f6093" 1433 | }, 1434 | id: "64c495d9-05ca-4d3b-8c95-94060df83544", 1435 | version: 3 1436 | } 1437 | }, 1438 | expected: "6445042b8e8cc121fb6a8985606a84b4cb07dac6dfb3633e769ec27dd2370984" 1439 | }); 1440 | test({ 1441 | input: { 1442 | password: "testpassword", 1443 | keyObject: { 1444 | address: "f0c4ee355432a7c7da12bdef04543723d110d591", 1445 | Crypto: { 1446 | cipher: "aes-128-cbc", 1447 | cipherparams: {iv: "bda427191686ac4455142bc449543129"}, 1448 | ciphertext: "097cc168892c41872ba92af7a359708f2e9f2f420465684cf84bb2d1a7351e37a7746607d3845ab91ce82cbf9ba54c69", 1449 | kdf: "scrypt", 1450 | kdfparams: { 1451 | n: 262144, 1452 | r: 8, 1453 | p: 1, 1454 | dklen: 32, 1455 | salt: "98e3f47b814f5a55a2298cf92a2572a047c31d30c6b8bb4d1e5f60cc4a437653" 1456 | }, 1457 | mac: "4c5d82b039d482b51d2f6ca09f1ff9b44f6e4a35f5bf0155cb1a163c75742278", 1458 | version: "1" 1459 | }, 1460 | id: "efe9ba02-56a3-42f5-9fb3-10059629c7bf", 1461 | version: "1" 1462 | } 1463 | }, 1464 | expected: "490127c2782fb55943beeb31943ec26f48a9a5121cd7e91799eb354d30d46529" 1465 | }); 1466 | test({ 1467 | input: { 1468 | password: "correcthorsebatterystaple", 1469 | keyObject: { 1470 | address: "f0c4ee355432a7c7da12bdef04543723d110d591", 1471 | Crypto: { 1472 | cipher: "aes-128-cbc", 1473 | cipherparams: {iv: "bda427191686ac4455142bc449543129"}, 1474 | ciphertext: "fc221520b157d08bd51e1b220a188e36b2f53a783ed5777e4438951349dd80b33089a18f493a84f279f376edc42a370d", 1475 | kdf: "scrypt", 1476 | kdfparams: { 1477 | n: 262144, 1478 | r: 8, 1479 | p: 1, 1480 | dklen: 32, 1481 | salt: "98e3f47b814f5a55a2298cf92a2572a047c31d30c6b8bb4d1e5f60cc4a437653" 1482 | }, 1483 | mac: "f4f15a66f99a87923cc8d8fcbf2fd5d3c2f2de238d87b024113f97a37778210a", 1484 | version: "1" 1485 | }, 1486 | id: "efe9ba02-56a3-42f5-9fb3-10059629c7bf", 1487 | version: "1" 1488 | } 1489 | }, 1490 | expected: "490127c2782fb55943beeb31943ec26f48a9a5121cd7e91799eb354d30d46529" 1491 | }); 1492 | }); 1493 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --timeout 7000 2 | --------------------------------------------------------------------------------