├── .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 | [](https://travis-ci.org/ethereumjs/keythereum) [](https://coveralls.io/github/ethereumjs/keythereum?branch=master) [](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 |
--------------------------------------------------------------------------------