├── .gitignore
├── lib
├── assert.js
├── signing.js
├── txutils.js
├── upgrade.js
├── encryption.js
└── keystore.js
├── index.js
├── test
├── fixtures
│ ├── generatekeyaddrvector.py
│ ├── lightwallet.json
│ ├── lightwalletv2.json
│ ├── txutils.json
│ ├── keystore.json
│ └── addrprivkey100.json
├── txutils.js
├── encryption.js
├── signing.js
└── keystore.js
├── LICENSE.txt
├── example
├── example_demo_video.html
├── example_usage.js
└── webwallet.html
├── package.json
├── RELEASE-NOTES.md
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | coverage/
3 | .idea/
4 |
5 | package-lock.json
6 |
--------------------------------------------------------------------------------
/lib/assert.js:
--------------------------------------------------------------------------------
1 | function derivedKey(keystore, pwDerivedKey) {
2 | if (!keystore.isDerivedKeyCorrect(pwDerivedKey)) {
3 | throw new Error('Incorrect derived key!');
4 | }
5 | }
6 |
7 | module.exports = {
8 | derivedKey,
9 | };
10 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | txutils: require('./lib/txutils.js'),
3 | encryption: require('./lib/encryption.js'),
4 | signing: require('./lib/signing.js'),
5 | keystore: require('./lib/keystore.js'),
6 | upgrade: require('./lib/upgrade.js'),
7 | };
8 |
--------------------------------------------------------------------------------
/test/fixtures/generatekeyaddrvector.py:
--------------------------------------------------------------------------------
1 | from ethereum import tester
2 | from ethereum import utils
3 | from bitcoin import ecdsa_sign, ecdsa_raw_sign, ecdsa_raw_recover, decode_sig
4 | import json
5 |
6 | s = tester.state()
7 |
8 | init_seed = 'some_random_initial_seed_'
9 |
10 | indices = range(10000)
11 |
12 | result_vector = []
13 |
14 | for i in indices:
15 | seed = init_seed + str(i)
16 | key = utils.sha3(seed)
17 | addr = utils.privtoaddr(key)
18 | s.send(to=addr, sender=tester.k0, value=10**18)
19 | assert (s.block.get_balance(addr) == 10**18)
20 | s.send(to=tester.a0, sender=key, value=6*10**17)
21 | assert (s.block.get_balance(addr) < 4*10**17 and s.block.get_balance > 3*10**17)
22 | result_vector.append({'seed': seed,
23 | 'key' : utils.encode_hex(key),
24 | 'addr' : utils.encode_hex(addr)})
25 |
26 | output = json.dumps(result_vector)
27 | outfile = file('testvector.json', 'w')
28 | outfile.write(output)
29 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Christian Lundkvist
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/test/fixtures/lightwallet.json:
--------------------------------------------------------------------------------
1 | {
2 | "encSeed": {
3 | "encStr": "U2FsdGVkX1+1jjqvZCyyCih+ZeyAURhL/pJg8sRmK+bUN7w77KCMOEpeOg0IC3PYEJH582DsR79KIcITSs9UeXqATQr+3KZUSHVdkA//xRtycN9GiqUnZ3QAV6qz3lHvTM4VFoikPqS/QvD9bBuIAw==",
4 | "iv": "77c76242f2ea7fbc531ec3aea64d83ec",
5 | "salt": "b58e3aaf642cb20a"
6 | },
7 | "encHdRootPriv": {
8 | "encStr": "U2FsdGVkX1+Sk9xTky65xJ6vzDxtbhOFxXmsHpfdu1KuJpaOurxAkW1P+Byq9zfnPzUqhpcDMAJ7GRMTPOSTSkhAeQi2+9KIkFNsLUXHxfiAdoNrve25hl+BpJhnX8buzFD3Yvnpec7B1vOv+5Ya3UQB+BkZJH/Z2Q4ZTov1eTc=",
9 | "iv": "3e60076cfb8c1b70093d62278671482f",
10 | "salt": "9293dc53932eb9c4"
11 | },
12 | "hdIndex": 1,
13 | "encPrivKeys": {
14 | "bc4061a92334dbec978e7c60d631d40ac9f2366d": {
15 | "key": "U2FsdGVkX1/FmWPEQcQ+a919oSJdAyyFhBVyeFgSUSInkk+aUEixHtaH5xEMPIUPdB4OFXNCtorjMoCIfbC7SQ==",
16 | "iv": "1f57166d473738233468681bf2c9be17",
17 | "salt": "c59963c441c43e6b"
18 | }
19 | },
20 | "addresses": [
21 | "bc4061a92334dbec978e7c60d631d40ac9f2366d"
22 | ],
23 | "keyHash": "f53849beb3235d13462edb88a7713bc3e73ec80bb31da72bb27dfe4f8849a6d48bc55f314c708d7e4b1eb379171ace089939b014d65910048c94a9d970c9fbfc",
24 | "salt": {
25 | "words": [
26 | 1524120912,
27 | 1274320379,
28 | 1151371309,
29 | 1817999902
30 | ],
31 | "sigBytes": 16
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/test/fixtures/lightwalletv2.json:
--------------------------------------------------------------------------------
1 | {
2 | "encSeed": {
3 | "encStr": "KuKqgDa4D64OCwOZlPFZ0mtB5vl908Z1PjMpG+5u/4/1NAUn7KjHL/GmTndIGuY/rTB+J0q1yod13JXmPg/9idECXpTmCeYcvYZqt3m0jRGDd1NeBfWwomDIroUKLqsdDAo/ZrlA50WY1xzjBNb7EGYAdbbpz78WsPUQDeCfMdef/UnYJWkKOg==",
4 | "nonce": "VWZ+aXzfjxwwPH1itFo6wxRCkzi2CRUE"
5 | },
6 | "ksData": {
7 | "m/44'/60'/0'/0": {
8 | "info": {
9 | "curve": "secp256k1",
10 | "purpose": "sign"
11 | },
12 | "encHdPathPriv": {
13 | "encStr": "LMT14e/lJze9gszFaaNhJmh95+5wyt3Ser8zrPUThqo+6bHFOTl55jhgEmDBtb/vWhwGjqLkc0Bc5jplz9Ii9DzCtUAbHtSKblPJJ9J/6246QhG9mEG69MGLOGTPIZE1Cj1aI1BuUkO4BbsW7fx4HdaNzAzNDHH4RK1sykz2ww==",
14 | "nonce": "VsOwvC8iCF/EOcso43C5MsghQrVky0zb"
15 | },
16 | "hdIndex": 1,
17 | "encPrivKeys": {
18 | "be159be50befb5fe233871cf0e2ebe4d3005fd6f": {
19 | "key": "uiM3YxqLFU2IJ0+ipc+ZIMrOEZqjoziZfuLp0tOKB/FtjhMLPoPJKb7gzvBIgwR0",
20 | "nonce": "X9bGjJ8IQZ8CYh1jhZpZWKH0iRgO3D3T"
21 | }
22 | },
23 | "addresses": [
24 | "be159be50befb5fe233871cf0e2ebe4d3005fd6f"
25 | ]
26 | }
27 | },
28 | "encHdRootPriv": {
29 | "encStr": "938nq9/t1GwwPQ0eds8VjDvxsfJp9DEnhOcH0Lr69eYR5ENCgnnR8uatSZ0la4OQfE8rl20rXUMjm2b73sy/ighipWKAbDVk7zXjazh7fI1KmURj+7ajSppAKpehZRQYBC150NbX/Xn/PtyAUEN7ALgdtkejOX2NQOnLV+5leA==",
30 | "nonce": "6qoLQN/AMs4qtAiCU2shmihruk8/zogQ"
31 | },
32 | "salt": "ErQWgVARkXHhsK8fk4UuXZ/DmyGkIgUsqqwn0UGUU30=",
33 | "version": 2
34 | }
35 |
--------------------------------------------------------------------------------
/test/txutils.js:
--------------------------------------------------------------------------------
1 | const { expect } = require('chai');
2 | const TxUtils = require('../lib/txutils');
3 | const fixtures = require('./fixtures/txutils');
4 |
5 | describe('Utils', function () {
6 | describe('_getTypesFromAbi', function () {
7 | fixtures.valid.forEach(function (f) {
8 | it('returns valid types of function ' + '"' + f.func + '"', function () {
9 | const types = TxUtils._getTypesFromAbi(f.abi, f.func);
10 |
11 | expect(types).to.deep.equal(f.types);
12 | });
13 | });
14 | });
15 |
16 | describe('functionTx', function () {
17 | fixtures.valid.forEach(function (f) {
18 | it('correct transaction generated', function () {
19 | const tx = TxUtils.functionTx(f.abi, f.func, f.args, f.txObject);
20 |
21 | expect(tx).to.equal(f.funcTx);
22 | });
23 | });
24 | });
25 |
26 | describe('createdContractAddress', function () {
27 | fixtures.valid.forEach(function (f) {
28 | it('correct contract address is generated', function () {
29 | const address = TxUtils.createdContractAddress(f.fromAddress, f.txObject.nonce);
30 |
31 | expect(address).to.equal(f.contractAddress);
32 | });
33 | });
34 | });
35 |
36 | describe('createContractTx valueTx', function () {
37 | fixtures.valid.forEach(function (f) {
38 | it('createContractTx returns the same as valueTx and contractAddress', function () {
39 | const contractTxData = TxUtils.createContractTx(f.fromAddress, f.txObject);
40 | const txData = TxUtils.valueTx(f.txObject);
41 | const address = TxUtils.createdContractAddress(f.fromAddress, f.txObject.nonce);
42 |
43 | expect(address).to.equal(contractTxData.addr);
44 | expect(txData).to.equal(contractTxData.tx);
45 | });
46 | });
47 | });
48 | });
49 |
--------------------------------------------------------------------------------
/lib/signing.js:
--------------------------------------------------------------------------------
1 | const Transaction = require('ethereumjs-tx');
2 | const Util = require('ethereumjs-util');
3 | const Assert = require('./assert');
4 |
5 | function getPrivateKeyBuff(keystore, pwDerivedKey, address) {
6 | const privateKey = keystore.exportPrivateKey(Util.stripHexPrefix(address), pwDerivedKey);
7 |
8 | return new Buffer(privateKey, 'hex');
9 | }
10 |
11 | function signTx(keystore, pwDerivedKey, rawTx, signingAddress) {
12 | Assert.derivedKey(keystore, pwDerivedKey);
13 |
14 | const tx = new Transaction(new Buffer(Util.stripHexPrefix(rawTx), 'hex'));
15 | const privateKeyBuff = getPrivateKeyBuff(keystore, pwDerivedKey, signingAddress);
16 |
17 | tx.sign(privateKeyBuff);
18 |
19 | return tx.serialize().toString('hex');
20 | }
21 |
22 | function signMsg(keystore, pwDerivedKey, rawMsg, signingAddress) {
23 | Assert.derivedKey(keystore, pwDerivedKey);
24 |
25 | const msgHash = Util.addHexPrefix(Util.keccak(rawMsg).toString('hex'));
26 |
27 | return this.signMsgHash(keystore, pwDerivedKey, msgHash, signingAddress);
28 | }
29 |
30 | function signMsgHash(keystore, pwDerivedKey, msgHash, signingAddress) {
31 | Assert.derivedKey(keystore, pwDerivedKey);
32 |
33 | const msgBuff = new Buffer(Util.stripHexPrefix(msgHash), 'hex');
34 | const privateKeyBuff = getPrivateKeyBuff(keystore, pwDerivedKey, signingAddress);
35 |
36 | return Util.ecsign(msgBuff, privateKeyBuff);
37 | }
38 |
39 | function concatSig(signature) {
40 | let v = signature.v;
41 | let r = signature.r;
42 | let s = signature.s;
43 |
44 | r = Util.fromSigned(r);
45 | s = Util.fromSigned(s);
46 | v = Util.bufferToInt(v);
47 |
48 | r = Util.setLengthLeft(Util.toUnsigned(r), 32).toString('hex');
49 | s = Util.setLengthLeft(Util.toUnsigned(s), 32).toString('hex');
50 | v = Util.stripHexPrefix(Util.intToHex(v));
51 |
52 | return Util.addHexPrefix(r.concat(s, v).toString('hex'));
53 | }
54 |
55 | function recoverAddress(rawMsg, v, r, s) {
56 | const msgHash = Util.keccak(rawMsg);
57 |
58 | return Util.pubToAddress(Util.ecrecover(msgHash, v, r, s));
59 | }
60 |
61 | module.exports = {
62 | signTx,
63 | signMsg,
64 | signMsgHash,
65 | concatSig,
66 | recoverAddress,
67 | };
68 |
--------------------------------------------------------------------------------
/example/example_demo_video.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
46 | LightWallet Demo
47 | Seed
48 |
49 |
50 |
51 |
52 | Address
53 |
54 | Transfer ether
55 | To:
56 | Ether:
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "eth-lightwallet",
3 | "version": "4.0.0",
4 | "description": "A lightweight ethereum javascript wallet.",
5 | "main": "index.js",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/ConsenSys/eth-lightwallet.git"
9 | },
10 | "scripts": {
11 | "build-js": "browserify index.js --s lightwallet -g [ babelify --presets [ @babel/preset-env @babel/preset-react ] ] | uglifyjs -c > dist/lightwallet.min.js",
12 | "build-dev": "browserify index.js -o dist/lightwallet.js --s lightwallet -g [ babelify --presets [ @babel/preset-env @babel/preset-react ] ]",
13 | "test": "./node_modules/.bin/mocha --reporter spec",
14 | "coverage": "istanbul cover _mocha -- -R spec; open coverage/lcov-report/index.html",
15 | "prepublish": "mkdir -p dist && npm run build-dev && npm run build-js"
16 | },
17 | "keywords": [
18 | "ethereum",
19 | "blockchain",
20 | "transactions",
21 | "contracts",
22 | "wallet"
23 | ],
24 | "contributors": [
25 | {
26 | "name": "Christian Lundkvist",
27 | "email": "christian.lundkvist@gmail.com"
28 | },
29 | {
30 | "name": "Tyler Clark",
31 | "email": "tysclark@gmail.com"
32 | },
33 | {
34 | "name": "Joel Torstensson",
35 | "email": "me@joeltorstensson.se"
36 | },
37 | {
38 | "name": "Zach Ferland",
39 | "email": "zachferland@gmail.com"
40 | },
41 | {
42 | "name": "Kevin Jiao",
43 | "email": "kevin.jiao@berkeley.edu"
44 | },
45 | {
46 | "name": "Marian Oancea",
47 | "email": "marian.oancea@gmail.com"
48 | },
49 | {
50 | "name": "John McDowall",
51 | "email": "john@kantan.io"
52 | },
53 | {
54 | "name": "Milad Mostavi",
55 | "email": "milad.mostavi@gmail.com"
56 | },
57 | {
58 | "name": "Slava Matvienco",
59 | "email": "slava.matvienco@gmail.com"
60 | }
61 | ],
62 | "license": "MIT",
63 | "dependencies": {
64 | "bitcore-lib": "8.1.1",
65 | "bitcore-mnemonic": "8.1.1",
66 | "crypto-js": "3.1.8",
67 | "elliptic": "6.4.1",
68 | "ethereumjs-tx": "1.3.7",
69 | "ethereumjs-util": "6.1.0",
70 | "rlp": "2.2.3",
71 | "scrypt-async": "2.0.1",
72 | "tweetnacl": "1.0.1",
73 | "tweetnacl-util": "0.15.0",
74 | "web3": "0.20.7"
75 | },
76 | "devDependencies": {
77 | "@babel/core": "7.4.0",
78 | "@babel/preset-env": "7.4.2",
79 | "@babel/preset-react": "7.0.0",
80 | "async": "2.6.2",
81 | "babelify": "10.0.0",
82 | "bluebird": "3.5.3",
83 | "browserify": "16.2.3",
84 | "chai": "4.2.0",
85 | "hooked-web3-provider": "christianlundkvist/hooked-web3-provider#updates_web3_14",
86 | "istanbul": "0.4.5",
87 | "mocha": "6.0.2",
88 | "uglify-js": "3.5.2"
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/lib/txutils.js:
--------------------------------------------------------------------------------
1 | const Transaction = require('ethereumjs-tx');
2 | const Util = require('ethereumjs-util');
3 | const Coder = require('web3/lib/solidity/coder');
4 | const Rlp = require('rlp');
5 | const CryptoJS = require('crypto-js');
6 |
7 | function createTx(txObject) {
8 | return new Transaction({
9 | ...txObject.from && { from: Util.addHexPrefix(txObject.from) },
10 | ...txObject.to && { to: Util.addHexPrefix(txObject.to) },
11 | ...txObject.gasPrice && { gasPrice: Util.addHexPrefix(txObject.gasPrice) },
12 | ...txObject.gasLimit && { gasLimit: Util.addHexPrefix(txObject.gasLimit) },
13 | ...txObject.nonce && { nonce: Util.addHexPrefix(txObject.nonce) },
14 | ...txObject.value && { value: Util.addHexPrefix(txObject.value) },
15 | ...txObject.data && { data: Util.addHexPrefix(txObject.data) },
16 | });
17 | }
18 |
19 | function txToHexString(tx) {
20 | return Util.addHexPrefix(tx.serialize().toString('hex'));
21 | }
22 |
23 | function _getTypesFromAbi(abi, functionName) {
24 | const funcJson = abi.filter(json => json.type === 'function' && json.name === functionName)[0];
25 |
26 | return (funcJson.inputs).map(json => json.type);
27 | }
28 |
29 | function _encodeFunctionTxData(functionName, types, args) {
30 | const fullName = `${functionName}(${types.join()})`;
31 | const signature = CryptoJS.SHA3(fullName, { outputLength: 256 }).toString(CryptoJS.enc.Hex).slice(0, 8);
32 | const encodeParams = Coder.encodeParams(types, args);
33 | const dataHex = Util.addHexPrefix(`${signature}${encodeParams}`);
34 |
35 | return dataHex;
36 | }
37 |
38 | function functionTx(abi, functionName, args, txObject) {
39 | const types = _getTypesFromAbi(abi, functionName);
40 | const txData = _encodeFunctionTxData(functionName, types, args);
41 | const tx = createTx({
42 | ...txObject,
43 | data: txData,
44 | });
45 |
46 | return txToHexString(tx);
47 | }
48 |
49 | function valueTx(txObject) {
50 | const tx = createTx(txObject);
51 |
52 | return txToHexString(tx);
53 | }
54 |
55 | function createdContractAddress(fromAddress, nonce) {
56 | const addressBuf = new Buffer(Util.stripHexPrefix(fromAddress), 'hex');
57 | const rlpEncodedHex = Rlp.encode([addressBuf, nonce]).toString('hex');
58 | const rlpEncodedWordArray = CryptoJS.enc.Hex.parse(rlpEncodedHex);
59 | const hash = CryptoJS.SHA3(rlpEncodedWordArray, { outputLength: 256 }).toString(CryptoJS.enc.Hex);
60 |
61 | return Util.addHexPrefix(hash.slice(24));
62 | }
63 |
64 | function createContractTx(fromAddress, txObject) {
65 | const tx = createTx(txObject);
66 | const contractAddress = createdContractAddress(fromAddress, txObject.nonce);
67 |
68 | return {
69 | tx: txToHexString(tx),
70 | addr: contractAddress,
71 | };
72 | }
73 |
74 | module.exports = {
75 | _encodeFunctionTxData,
76 | _getTypesFromAbi,
77 | createTx,
78 | txToHexString,
79 | functionTx,
80 | createdContractAddress,
81 | createContractTx,
82 | valueTx,
83 | };
84 |
--------------------------------------------------------------------------------
/test/encryption.js:
--------------------------------------------------------------------------------
1 | const { expect } = require('chai');
2 | const KeyStore = require('../lib/keystore');
3 | const Encryption = require('../lib/encryption');
4 | const fixtures = require('./fixtures/keystore');
5 |
6 | describe('Encryption', function () {
7 | describe('Asymmetric Encryption', function () {
8 | it('encrypts and decrypts a string', function (done) {
9 | const fixture = fixtures.valid[0];
10 | const pw = Uint8Array.from(fixture.pwDerivedKey);
11 |
12 | KeyStore.createVault({
13 | password: fixture.password,
14 | seedPhrase: fixture.mnSeed,
15 | salt: fixture.salt,
16 | hdPathString: 'm/0\'/0\'/2\''
17 | }, function (err, ks) {
18 | ks.generateNewAddress(pw, 2);
19 | const addresses = ks.getAddresses();
20 | const pubKey0 = Encryption.addressToPublicEncKey(ks, pw, addresses[0]);
21 | const pubKey1 = Encryption.addressToPublicEncKey(ks, pw, addresses[1]);
22 |
23 | const msg = 'Hello World!';
24 | const encrypted = Encryption.asymEncryptString(ks, pw, msg, addresses[0], pubKey1);
25 | const clearText = Encryption.asymDecryptString(ks, pw, encrypted, pubKey0, addresses[1]);
26 | expect(clearText).to.equal(msg);
27 | done();
28 | });
29 | });
30 | });
31 |
32 | describe('Multi-recipient Encryption', function () {
33 | this.timeout(10000);
34 |
35 | it('encrypts and decrypts a string to multiple parties', function (done) {
36 | const fixture = fixtures.valid[0];
37 | const pw = Uint8Array.from(fixture.pwDerivedKey);
38 |
39 | KeyStore.createVault({
40 | password: fixture.password,
41 | seedPhrase: fixture.mnSeed,
42 | salt: fixture.salt,
43 | hdPathString: 'm/0\'/0\'/2\''
44 | }, function (err, ks) {
45 |
46 | ks.generateNewAddress(pw, 6);
47 |
48 | const addresses = ks.getAddresses();
49 | const pubKeys = [];
50 | addresses.map(function (addr) {
51 | pubKeys.push(Encryption.addressToPublicEncKey(ks, pw, addr));
52 | });
53 |
54 | const msg = 'Hello World to multiple people!';
55 | const encrypted = Encryption.multiEncryptString(ks, pw, msg, addresses[0], pubKeys.slice(0, 4));
56 |
57 | let clearText = Encryption.multiDecryptString(ks, pw, encrypted, pubKeys[0], addresses[0]);
58 | expect(clearText).to.equal(msg);
59 | clearText = Encryption.multiDecryptString(ks, pw, encrypted, pubKeys[0], addresses[1]);
60 | expect(clearText).to.equal(msg);
61 | clearText = Encryption.multiDecryptString(ks, pw, encrypted, pubKeys[0], addresses[2]);
62 | expect(clearText).to.equal(msg);
63 | clearText = Encryption.multiDecryptString(ks, pw, encrypted, pubKeys[0], addresses[3]);
64 | expect(clearText).to.equal(msg);
65 | clearText = Encryption.multiDecryptString(ks, pw, encrypted, pubKeys[0], addresses[4]);
66 | expect(clearText).to.equal(false);
67 | done();
68 | });
69 | });
70 | });
71 | });
72 |
--------------------------------------------------------------------------------
/lib/upgrade.js:
--------------------------------------------------------------------------------
1 | const CryptoJS = require('crypto-js');
2 | const Keystore = require('./keystore');
3 |
4 | const HD_PATH_STRING = 'm/0\'/0\'/0\'';
5 |
6 | function legacyGenerateEncKey(password, salt) {
7 | return CryptoJS.PBKDF2(password, salt, {
8 | keySize: 512 / 32,
9 | iterations: 150,
10 | }).toString();
11 | }
12 |
13 | function legacyDecryptString(encryptedStr, password) {
14 | const { encStr, iv, salt } = encryptedStr;
15 | const decryptedStr = CryptoJS.AES.decrypt(encStr, password, { iv, salt });
16 |
17 | return decryptedStr.toString(CryptoJS.enc.Latin1);
18 | }
19 |
20 | function upgradeVersion1(oldKS, password, callback) {
21 | const { salt, keyHash, encSeed, hdIndex } = oldKS;
22 | const derivedKey = legacyGenerateEncKey(password, salt);
23 |
24 | const hash = CryptoJS.SHA3(derivedKey).toString();
25 |
26 | if (keyHash !== hash) {
27 | callback(new Error('Keystore Upgrade: Invalid Password!'));
28 | return;
29 | }
30 |
31 | const seedPhrase = legacyDecryptString(encSeed, derivedKey);
32 |
33 | Keystore.createVault({
34 | password,
35 | seedPhrase,
36 | salt: Keystore.DEFAULT_SALT,
37 | hdPathString: HD_PATH_STRING
38 | }, (err, newKeyStore) => {
39 | if (err) {
40 | callback(err);
41 | return;
42 | }
43 |
44 | newKeyStore.keyFromPassword(password, (err, pwDerivedKey) => {
45 | if (err) {
46 | callback(err);
47 | return;
48 | }
49 |
50 | newKeyStore.generateNewAddress(pwDerivedKey, hdIndex);
51 |
52 | callback(null, newKeyStore.serialize());
53 | });
54 | });
55 | }
56 |
57 | function upgradeVersion2(oldKS, password, callback) {
58 | const { salt = Keystore.DEFAULT_SALT, encSeed, ksData } = oldKS;
59 |
60 | Keystore.deriveKeyFromPasswordAndSalt(password, salt, (err, pwKey) => {
61 | if (err) {
62 | callback(err);
63 | return;
64 | }
65 |
66 | let seedPhrase = Keystore._decryptString(encSeed, pwKey);
67 |
68 | if (seedPhrase) {
69 | seedPhrase = seedPhrase.trim();
70 | }
71 |
72 | if (!seedPhrase || !Keystore.isSeedValid(seedPhrase)) {
73 | callback(new Error('Keystore Upgrade: Invalid provided password.'));
74 | return;
75 | }
76 |
77 | const hdPaths = Object.keys(ksData);
78 |
79 | let hdPathString = HD_PATH_STRING;
80 |
81 | if (hdPaths.length > 0) {
82 | hdPathString = hdPaths[0];
83 | }
84 |
85 | Keystore.createVault({
86 | password,
87 | seedPhrase,
88 | salt,
89 | hdPathString
90 | }, (err, newKeyStore) => {
91 | if (err) {
92 | callback(err);
93 | return;
94 | }
95 |
96 | newKeyStore.keyFromPassword(password, (err, pwDerivedKey) => {
97 | if (err) {
98 | callback(err);
99 | return;
100 | }
101 |
102 | const hdIndex = ksData[hdPathString].hdIndex;
103 | newKeyStore.generateNewAddress(pwDerivedKey, hdIndex);
104 |
105 | callback(null, newKeyStore.serialize());
106 | });
107 | });
108 | });
109 | }
110 |
111 | function upgradeOldSerialized(oldSerialized, password, callback) {
112 | const oldKS = JSON.parse(oldSerialized);
113 | const { version } = oldKS;
114 |
115 | if (version === undefined || version === 1) {
116 | upgradeVersion1(oldKS, password, callback);
117 | } else if (version === 2) {
118 | upgradeVersion2(oldKS, password, callback);
119 | } else if (version === 3) {
120 | callback(null, oldSerialized);
121 | } else {
122 | throw new Error('Keystore is not of correct version.');
123 | }
124 | }
125 |
126 | module.exports = {
127 | upgradeOldSerialized,
128 | };
129 |
--------------------------------------------------------------------------------
/test/signing.js:
--------------------------------------------------------------------------------
1 | const { expect } = require('chai');
2 | const Transaction = require('ethereumjs-tx');
3 | const Util = require('ethereumjs-util');
4 | const KeyStore = require('../lib/keystore');
5 | const Signing = require('../lib/signing');
6 | const fixtures = require('./fixtures/keystore');
7 |
8 | describe('Signing', function () {
9 | describe('signTx', function () {
10 | it('signs a transaction deterministically', function (done) {
11 | const pw = Uint8Array.from(fixtures.valid[0].pwDerivedKey);
12 | const fixture = fixtures.valid[0];
13 |
14 | KeyStore.createVault({
15 | password: fixture.password,
16 | seedPhrase: fixture.mnSeed,
17 | salt: fixture.salt,
18 | hdPathString: fixture.hdPathString
19 | }, function (err, ks) {
20 | ks.generateNewAddress(pw);
21 |
22 | const addr = ks.getAddresses()[0];
23 | expect(addr).to.equal(fixtures.valid[0].ethjsTxParams.from);
24 |
25 | const tx = new Transaction(fixtures.valid[0].ethjsTxParams);
26 | const rawTx = tx.serialize().toString('hex');
27 | expect(rawTx).to.equal(fixtures.valid[0].rawUnsignedTx);
28 |
29 | const signedTx0 = Signing.signTx(ks, pw, rawTx, addr);
30 | expect(signedTx0).to.equal(fixtures.valid[0].rawSignedTx);
31 | done();
32 | });
33 | });
34 |
35 | it('Correctly handles a 31 byte key from bitcore', function (done) {
36 | const secretSeed = 'erupt consider beyond twist bike enroll you salute weasel emerge divert hundred';
37 | const hdPath = 'm/44\'/60\'/0\''; //as defined in SLIP44
38 | const password = 'test';
39 |
40 | KeyStore.createVault({
41 | password: password,
42 | seedPhrase: secretSeed,
43 | salt: 'someSalt',
44 | hdPathString: hdPath
45 | }, function (err, keystore) {
46 | keystore.keyFromPassword(password, function (err, pwDerivedKey) {
47 | keystore.generateNewAddress(pwDerivedKey, 1); //Generate a new address
48 |
49 | const address = keystore.getAddresses()[0];
50 | const hexSeedETH = keystore.exportPrivateKey(address, pwDerivedKey);
51 | const addr0 = KeyStore._computeAddressFromPrivKey(hexSeedETH);
52 | expect(address).to.equal('0x' + addr0);
53 |
54 | const tx = new Transaction({
55 | from: address,
56 | to: address,
57 | value: 100000000
58 | });
59 | const rawTx = tx.serialize().toString('hex');
60 | const signedTx = Signing.signTx(keystore, pwDerivedKey, rawTx, address, hdPath);
61 | const expectedTx = 'f861808080945e2abe3de708923e8425348005ee7fdd77e203cb8405f5e100801ca00a9a2486f65cab6c7819c82ee741f72d1acaab005642eef32f303696909fa64ea04e5d5e0e8d5f38704ac04faa1f91a9ee15a3ffcf158de342324d242b6acba819';
62 |
63 | expect(signedTx).to.equal(expectedTx);
64 | done();
65 | });
66 | });
67 | });
68 |
69 | describe('signMsg', function () {
70 | it('signs a message deterministically', function (done) {
71 | const pw = Uint8Array.from(fixtures.valid[0].pwDerivedKey);
72 | const fixture = fixtures.valid[0];
73 |
74 | KeyStore.createVault({
75 | password: fixture.password,
76 | seedPhrase: fixture.mnSeed,
77 | salt: fixture.salt,
78 | hdPathString: fixture.hdPathString
79 | }, function (err, ks) {
80 | ks.generateNewAddress(pw);
81 |
82 | const addr = ks.getAddresses()[0];
83 | expect(addr).to.equal(fixtures.valid[0].ethjsTxParams.from);
84 |
85 | const msg = 'this is a message';
86 | const signedMsg = Signing.signMsg(ks, pw, msg, addr);
87 | const msgHash = Util.addHexPrefix(Util.keccak(msg).toString('hex'));
88 | const signedMsgHash = Signing.signMsgHash(ks, pw, msgHash, addr);
89 |
90 | // signedMsg and signedMsgHash have the same signature
91 | expect(signedMsg.v).to.equal(signedMsgHash.v);
92 | expect(signedMsg.r.toString()).to.equal(signedMsgHash.r.toString());
93 | expect(signedMsg.s.toString()).to.equal(signedMsgHash.s.toString());
94 |
95 | const recoveredAddress = Signing.recoverAddress(msg, signedMsg.v, signedMsg.r, signedMsg.s);
96 |
97 | expect(addr).to.equal('0x' + recoveredAddress.toString('hex'));
98 | const concatSig = Signing.concatSig(signedMsg);
99 | const expectedConcatSig = '0x7b518ee144b8facf3f21b1f97a6d1f8aea448934d89cf5570e92bcca4d375ab6080f17400eafad3c5808e064ee56cd45321382040fb299fa028ea3cddf3488151c';
100 |
101 | expect(concatSig).to.equal(expectedConcatSig);
102 | done();
103 | });
104 | });
105 | });
106 | });
107 | });
108 |
--------------------------------------------------------------------------------
/lib/encryption.js:
--------------------------------------------------------------------------------
1 | const Nacl = require('tweetnacl');
2 | const NaclUtil = require('tweetnacl-util');
3 | const Assert = require('./assert');
4 |
5 | function encodeHex(msgUInt8Arr) {
6 | const msgBase64 = NaclUtil.encodeBase64(msgUInt8Arr);
7 |
8 | return (new Buffer(msgBase64, 'base64')).toString('hex');
9 | }
10 |
11 | function decodeHex(msgHex) {
12 | const msgBase64 = (new Buffer(msgHex, 'hex')).toString('base64');
13 |
14 | return NaclUtil.decodeBase64(msgBase64);
15 | }
16 |
17 | function asymEncryptRaw(keystore, pwDerivedKey, msgUint8Array, myAddress, theirPubKey) {
18 | Assert.derivedKey(keystore, pwDerivedKey);
19 |
20 | const privateKey = keystore.exportPrivateKey(myAddress, pwDerivedKey);
21 | const privateKeyUInt8Array = decodeHex(privateKey);
22 | const pubKeyUInt8Array = decodeHex(theirPubKey);
23 | const nonce = Nacl.randomBytes(Nacl.box.nonceLength);
24 | const encryptedMessage = Nacl.box(msgUint8Array, nonce, pubKeyUInt8Array, privateKeyUInt8Array);
25 |
26 | return {
27 | alg: 'curve25519-xsalsa20-poly1305',
28 | nonce: NaclUtil.encodeBase64(nonce),
29 | ciphertext: NaclUtil.encodeBase64(encryptedMessage)
30 | };
31 | }
32 |
33 | function asymDecryptRaw(keystore, pwDerivedKey, encMsg, theirPubKey, myAddress) {
34 | Assert.derivedKey(keystore, pwDerivedKey);
35 |
36 | const privateKey = keystore.exportPrivateKey(myAddress, pwDerivedKey);
37 | const privateKeyUInt8Array = decodeHex(privateKey);
38 | const pubKeyUInt8Array = decodeHex(theirPubKey);
39 |
40 | const nonce = NaclUtil.decodeBase64(encMsg.nonce);
41 | const cipherText = NaclUtil.decodeBase64(encMsg.ciphertext);
42 | const clearText = Nacl.box.open(cipherText, nonce, pubKeyUInt8Array, privateKeyUInt8Array);
43 |
44 | return clearText;
45 | }
46 |
47 | function asymEncryptString(keystore, pwDerivedKey, msg, myAddress, theirPubKey) {
48 | Assert.derivedKey(keystore, pwDerivedKey);
49 |
50 | const messageUInt8Array = NaclUtil.decodeUTF8(msg);
51 |
52 | return asymEncryptRaw(keystore, pwDerivedKey, messageUInt8Array, myAddress, theirPubKey);
53 | }
54 |
55 | function asymDecryptString(keystore, pwDerivedKey, encMsg, theirPubKey, myAddress) {
56 | Assert.derivedKey(keystore, pwDerivedKey);
57 |
58 | const clearText = asymDecryptRaw(keystore, pwDerivedKey, encMsg, theirPubKey, myAddress);
59 |
60 | if (clearText === null) {
61 | return false;
62 | }
63 |
64 | return NaclUtil.encodeUTF8(clearText);
65 | }
66 |
67 | function multiEncryptString(keystore, pwDerivedKey, msg, myAddress, theirPubKeyArray) {
68 | Assert.derivedKey(keystore, pwDerivedKey);
69 |
70 | const messageUInt8Array = NaclUtil.decodeUTF8(msg);
71 | const symEncryptionKey = Nacl.randomBytes(Nacl.secretbox.keyLength);
72 | const symNonce = Nacl.randomBytes(Nacl.secretbox.nonceLength);
73 |
74 | const symEncMessage = Nacl.secretbox(messageUInt8Array, symNonce, symEncryptionKey);
75 |
76 | if (theirPubKeyArray.length < 1) {
77 | throw new Error('Found no pubkeys to encrypt to.');
78 | }
79 |
80 | const encryptedSymKey = theirPubKeyArray.map(theirPubKey => {
81 | const { alg, ...props } = asymEncryptRaw(keystore, pwDerivedKey, symEncryptionKey, myAddress, theirPubKey);
82 |
83 | return {
84 | ...props,
85 | };
86 | });
87 |
88 | return {
89 | version: 1,
90 | asymAlg: 'curve25519-xsalsa20-poly1305',
91 | symAlg: 'xsalsa20-poly1305',
92 | symNonce: NaclUtil.encodeBase64(symNonce),
93 | symEncMessage: NaclUtil.encodeBase64(symEncMessage),
94 | encryptedSymKey,
95 | };
96 | }
97 |
98 | function multiDecryptString(keystore, pwDerivedKey, encMsg, theirPubKey, myAddress) {
99 | Assert.derivedKey(keystore, pwDerivedKey);
100 |
101 | let symKey = null;
102 |
103 | for (let i = 0; i < encMsg.encryptedSymKey.length; i++) {
104 | const result = asymDecryptRaw(keystore, pwDerivedKey, encMsg.encryptedSymKey[i], theirPubKey, myAddress);
105 |
106 | if (result !== null) {
107 | symKey = result;
108 | break;
109 | }
110 | }
111 |
112 | if (symKey === null) {
113 | return false;
114 | }
115 |
116 | const symNonce = NaclUtil.decodeBase64(encMsg.symNonce);
117 | const symEncMessage = NaclUtil.decodeBase64(encMsg.symEncMessage);
118 | const msg = Nacl.secretbox.open(symEncMessage, symNonce, symKey);
119 |
120 | if (msg === null) {
121 | return false;
122 | }
123 |
124 | return NaclUtil.encodeUTF8(msg);
125 | }
126 |
127 | function addressToPublicEncKey(keystore, pwDerivedKey, address) {
128 | Assert.derivedKey(keystore, pwDerivedKey);
129 |
130 | const privateKey = keystore.exportPrivateKey(address, pwDerivedKey);
131 | const privateKeyUInt8Array = decodeHex(privateKey);
132 | const pubKeyUInt8Array = Nacl.box.keyPair.fromSecretKey(privateKeyUInt8Array).publicKey;
133 |
134 | return encodeHex(pubKeyUInt8Array);
135 | }
136 |
137 | module.exports = {
138 | encodeHex,
139 | decodeHex,
140 | asymEncryptString,
141 | asymDecryptString,
142 | multiEncryptString,
143 | multiDecryptString,
144 | addressToPublicEncKey,
145 | };
146 |
--------------------------------------------------------------------------------
/test/fixtures/txutils.json:
--------------------------------------------------------------------------------
1 | {
2 | "valid": [
3 | {
4 | "abi": [
5 | {
6 | "constant": true,
7 | "inputs": [
8 | {
9 | "name": "key",
10 | "type": "uint256"
11 | }
12 | ],
13 | "name": "getValue",
14 | "outputs": [
15 | {
16 | "name": "value",
17 | "type": "uint256"
18 | }
19 | ],
20 | "type": "function"
21 | },
22 | {
23 | "constant": false,
24 | "inputs": [
25 | {
26 | "name": "key",
27 | "type": "uint256"
28 | },
29 | {
30 | "name": "newOwner",
31 | "type": "address"
32 | }
33 | ],
34 | "name": "transferOwnership",
35 | "outputs": [],
36 | "type": "function"
37 | },
38 | {
39 | "constant": false,
40 | "inputs": [
41 | {
42 | "name": "key",
43 | "type": "uint256"
44 | },
45 | {
46 | "name": "newValue",
47 | "type": "uint256"
48 | }
49 | ],
50 | "name": "setValue",
51 | "outputs": [],
52 | "type": "function"
53 | },
54 | {
55 | "constant": true,
56 | "inputs": [
57 | {
58 | "name": "key",
59 | "type": "uint256"
60 | }
61 | ],
62 | "name": "getOwner",
63 | "outputs": [
64 | {
65 | "name": "owner",
66 | "type": "address"
67 | }
68 | ],
69 | "type": "function"
70 | },
71 | {
72 | "constant": false,
73 | "inputs": [
74 | {
75 | "name": "key",
76 | "type": "uint256"
77 | }
78 | ],
79 | "name": "register",
80 | "outputs": [],
81 | "type": "function"
82 | }
83 | ],
84 | "func": "getValue",
85 | "types": [
86 | "uint256"
87 | ],
88 | "args": [
89 | 234
90 | ],
91 | "funcTx": "0xf84a018609184e72a000832dc6c0946c19d6af83d0d335a006ae424a426ef4d139d82780a40ff4c91600000000000000000000000000000000000000000000000000000000000000ea1c8080",
92 | "fromAddress": "0xfd888462ba01400a37768e53791df4617c23cd08",
93 | "contractAddress": "0x501593b1087f3a542500e19e644268187e5a591c",
94 | "txObject": {
95 | "gasPrice": 10000000000000,
96 | "gasLimit": 3000000,
97 | "value": 0,
98 | "nonce": 1,
99 | "to": "0x6c19d6af83d0d335a006ae424a426ef4d139d827"
100 | }
101 | },
102 | {
103 | "abi": [
104 | {
105 | "constant": true,
106 | "inputs": [
107 | {
108 | "name": "key",
109 | "type": "uint256"
110 | }
111 | ],
112 | "name": "getValue",
113 | "outputs": [
114 | {
115 | "name": "value",
116 | "type": "uint256"
117 | }
118 | ],
119 | "type": "function"
120 | },
121 | {
122 | "constant": false,
123 | "inputs": [
124 | {
125 | "name": "key",
126 | "type": "uint256"
127 | },
128 | {
129 | "name": "newOwner",
130 | "type": "address"
131 | }
132 | ],
133 | "name": "transferOwnership",
134 | "outputs": [],
135 | "type": "function"
136 | },
137 | {
138 | "constant": false,
139 | "inputs": [
140 | {
141 | "name": "key",
142 | "type": "uint256"
143 | },
144 | {
145 | "name": "newValue",
146 | "type": "uint256"
147 | }
148 | ],
149 | "name": "setValue",
150 | "outputs": [],
151 | "type": "function"
152 | },
153 | {
154 | "constant": true,
155 | "inputs": [
156 | {
157 | "name": "key",
158 | "type": "uint256"
159 | }
160 | ],
161 | "name": "getOwner",
162 | "outputs": [
163 | {
164 | "name": "owner",
165 | "type": "address"
166 | }
167 | ],
168 | "type": "function"
169 | },
170 | {
171 | "constant": false,
172 | "inputs": [
173 | {
174 | "name": "key",
175 | "type": "uint256"
176 | }
177 | ],
178 | "name": "register",
179 | "outputs": [],
180 | "type": "function"
181 | }
182 | ],
183 | "func": "transferOwnership",
184 | "types": [
185 | "uint256",
186 | "address"
187 | ],
188 | "args": [
189 | 234,
190 | "0x34bc4a62ba01400a37768e342a1df4617c77cd03"
191 | ],
192 | "funcTx": "0xf86c0985174876e8008333e140946c19d6af83d0d335a006ae424a426ef4d139d827821756b84429507f7300000000000000000000000000000000000000000000000000000000000000ea00000000000000000000000034bc4a62ba01400a37768e342a1df4617c77cd031c8080",
193 | "fromAddress": "0xfd888462ba01400a37768e53791df4617c77cd08",
194 | "contractAddress": "0xddfca315a51a2e058f10353f484594da3e2703b8",
195 | "txObject": {
196 | "gasPrice": 100000000000,
197 | "gasLimit": 3400000,
198 | "value": 5974,
199 | "nonce": 9,
200 | "to": "0x6c19d6af83d0d335a006ae424a426ef4d139d827"
201 | }
202 | }
203 | ],
204 | "invalid": [
205 | ]
206 | }
207 |
208 |
--------------------------------------------------------------------------------
/example/example_usage.js:
--------------------------------------------------------------------------------
1 | // Example usage: Name Registry
2 | // Create the contract, register the key 123, set the value 456
3 |
4 | var lightwallet = require('../index.js');
5 | var txutils = lightwallet.txutils;
6 | var signing = lightwallet.signing;
7 | var encryption = lightwallet.encryption;
8 |
9 | var source = '\ncontract NameCoin {\n\n struct Item {\n\taddress owner;\n\tuint value;\n }\n\n mapping (uint => Item) registry;\n\n function register(uint key) {\n\tif (registry[key].owner == 0) {\n\t registry[key].owner = msg.sender;\n\t}\n }\n\n function transferOwnership(uint key, address newOwner) {\n\tif (registry[key].owner == msg.sender) {\n\t registry[key].owner = newOwner;\n\t}\n }\n\n function setValue(uint key, uint newValue) {\n\tif (registry[key].owner == msg.sender) {\n\t registry[key].value = newValue;\n\t}\n }\n\n function getValue(uint key) constant returns (uint value) {\n\treturn registry[key].value;\n }\n\n function getOwner(uint key) constant returns (address owner) {\n\treturn registry[key].owner;\n }\n}\n';
10 |
11 | // contract json abi, this is autogenerated using solc CLI
12 | var abi = [{
13 | 'constant': true,
14 | 'inputs': [{ 'name': 'key', 'type': 'uint256' }],
15 | 'name': 'getValue',
16 | 'outputs': [{ 'name': 'value', 'type': 'uint256' }],
17 | 'type': 'function'
18 | }, {
19 | 'constant': false,
20 | 'inputs': [{ 'name': 'key', 'type': 'uint256' }, { 'name': 'newOwner', 'type': 'address' }],
21 | 'name': 'transferOwnership',
22 | 'outputs': [],
23 | 'type': 'function'
24 | }, {
25 | 'constant': false,
26 | 'inputs': [{ 'name': 'key', 'type': 'uint256' }, { 'name': 'newValue', 'type': 'uint256' }],
27 | 'name': 'setValue',
28 | 'outputs': [],
29 | 'type': 'function'
30 | }, {
31 | 'constant': true,
32 | 'inputs': [{ 'name': 'key', 'type': 'uint256' }],
33 | 'name': 'getOwner',
34 | 'outputs': [{ 'name': 'owner', 'type': 'address' }],
35 | 'type': 'function'
36 | }, {
37 | 'constant': false,
38 | 'inputs': [{ 'name': 'key', 'type': 'uint256' }],
39 | 'name': 'register',
40 | 'outputs': [],
41 | 'type': 'function'
42 | }];
43 |
44 | var code = '6060604052610381806100136000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900480630ff4c9161461006557806329507f731461008c5780637b8d56e3146100a5578063c41a360a146100be578063f207564e146100fb57610063565b005b610076600480359060200150610308565b6040518082815260200191505060405180910390f35b6100a36004803590602001803590602001506101b3565b005b6100bc60048035906020018035906020015061026e565b005b6100cf600480359060200150610336565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b61010c60048035906020015061010e565b005b60006000600050600083815260200190815260200160002060005060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614156101af57336000600050600083815260200190815260200160002060005060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908302179055505b5b50565b3373ffffffffffffffffffffffffffffffffffffffff166000600050600084815260200190815260200160002060005060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141561026957806000600050600084815260200190815260200160002060005060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908302179055505b5b5050565b3373ffffffffffffffffffffffffffffffffffffffff166000600050600084815260200190815260200160002060005060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161415610303578060006000506000848152602001908152602001600020600050600101600050819055505b5b5050565b600060006000506000838152602001908152602001600020600050600101600050549050610331565b919050565b60006000600050600083815260200190815260200160002060005060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905061037c565b91905056';
45 |
46 | // You can change this to your seed
47 | // and the nonce of the first address
48 | var seed = 'unhappy nerve cancel reject october fix vital pulse cash behind curious bicycle';
49 | var nonce = 2;
50 |
51 | lightwallet.keystore.deriveKeyFromPassword('mypassword', function (err, pwDerivedKey) {
52 | var keystore = new lightwallet.keystore(seed, pwDerivedKey);
53 | keystore.generateNewAddress(pwDerivedKey);
54 |
55 | var sendingAddr = keystore.getAddresses()[0];
56 |
57 | // The transaction data follows the format of ethereumjs-tx
58 | txOptions = {
59 | gasPrice: 10000000000000,
60 | gasLimit: 3000000,
61 | value: 10000000,
62 | nonce: nonce,
63 | data: code
64 | };
65 |
66 | // sendingAddr is needed to compute the contract address
67 | var contractData = txutils.createContractTx(sendingAddr, txOptions);
68 | var signedTx = signing.signTx(keystore, pwDerivedKey, contractData.tx, sendingAddr);
69 |
70 | console.log('Signed Contract creation TX: ' + signedTx);
71 | console.log('');
72 | console.log('Contract Address: ' + contractData.addr);
73 | console.log('');
74 |
75 | // TX to register the key 123
76 | txOptions.to = contractData.addr;
77 | txOptions.nonce += 1;
78 | var registerTx = txutils.functionTx(abi, 'register', [123], txOptions);
79 | var signedRegisterTx = signing.signTx(keystore, pwDerivedKey, registerTx, sendingAddr);
80 |
81 | // inject signedRegisterTx into the network...
82 | console.log('Signed register key TX: ' + signedRegisterTx);
83 | console.log('');
84 |
85 | // TX to set the value corresponding to key 123 to 456
86 | txOptions.nonce += 1;
87 | var setValueTx = txutils.functionTx(abi, 'setValue', [123, 456], txOptions);
88 | var signedSetValueTx = signing.signTx(keystore, pwDerivedKey, setValueTx, sendingAddr);
89 |
90 | // inject signedSetValueTx into the network...
91 | console.log('Signed setValueTx: ' + signedSetValueTx);
92 | console.log('');
93 |
94 | // Send a value transaction
95 | txOptions.nonce += 1;
96 | txOptions.value = 1500000000000000000;
97 | txOptions.data = undefined;
98 | txOptions.to = 'eba8cdda5058cd20acbe5d1af35a71cfc442450e';
99 | var valueTx = txutils.valueTx(txOptions);
100 |
101 | var signedValueTx = signing.signTx(keystore, pwDerivedKey, valueTx, sendingAddr);
102 | console.log('Signed value TX: ' + signedValueTx);
103 | console.log('');
104 | });
105 |
--------------------------------------------------------------------------------
/RELEASE-NOTES.md:
--------------------------------------------------------------------------------
1 | # Release Notes #
2 |
3 | ## Version 4.0.0 - 2019-03-26 ##
4 |
5 | * Major cleanup - backwards compatible!
6 | * Updated all dependencies
7 | * Code optimizations
8 | * Migrated to ES6
9 |
10 | ## Version 3.0.1 - 2017-11-14 ##
11 |
12 | * Fixed an issue with `signing.concatSig` where the `r` and `s` were not left padded, and the resulting signature was invalid.
13 | * Removed dist files from git.
14 |
15 | ## Version 3.0.0 - 2017-11-01 ##
16 |
17 | * Major cleanup - not backwards compatible!
18 |
19 | * Remove legacy constructor - now only `createVault` is supported. Also `createVault` now require seed and hd path as inputs.
20 |
21 | * Remove `deriveKeyFromPassword` function in favor of `keyFromPassword` function.
22 |
23 | * Remove special handling of encryption keys in the keystore. You can still use keystore keys to encrypt, but they no longer use pubkeys to index. To get the pubkey corresponding to an address, please use the function `encryption.addressToPublicEncKey`.
24 |
25 | * Make the keystore and interfaces simpler by only allowing one `hdPathString`. If you need to derive from more HD paths you need to create more keystores.
26 |
27 | * Add `0x` prefix for all addresses and transaction hex data.
28 |
29 | * Remove unneeded `bitcore-lib` package dependency. Thanks to [Srirangan](https://github.com/Srirangan).
30 |
31 | ## Version 2.5.6 - 2017-06-24 ##
32 |
33 | * Switch back to using npm version of `web3.js`, since version `0.19.1` is now fixed.
34 |
35 | ## Version 2.5.5 - 2017-06-23 ##
36 |
37 | * Remove redundant dependency on `bignumber.js` library which has stopped working. Temporarily use a fork of `web3.js` since this library also breaks because of the issues with the `bignumber.js` library.
38 |
39 | ## Version 2.5.4 - 2017-03-16 ##
40 |
41 | * Upgrade bitcore-lib and explicitly increase version of bitcore-mnemonic. By [roderik](https://github.com/roderik).
42 |
43 | * Upgrade ethereumjs-util and add needed babel plugins.
44 |
45 | * Update dist files
46 |
47 | ## Version 2.5.3 - 2016-11-05 ##
48 |
49 | * Make sure the deprecation warning doesn't show up when we are using the new constructor. By [flyswatter](https://github.com/flyswatter).
50 |
51 | ## Version 2.5.2 - 2016-09-08 ##
52 |
53 | * Fixed a bug that caused the dist files to fail in browsers.
54 |
55 | * Update dist files.
56 |
57 | ## Version 2.5.1 - 2016-09-07 ##
58 |
59 | * Update dist files.
60 |
61 | ## Version 2.5.0 - 2016-09-07 ##
62 |
63 | * Introduced a new constructor function that introduces a random seed in key derivation, which protects against rainbow attacks. By [flyswatter](https://github.com/flyswatter).
64 |
65 | ## Version 2.4.4 - 2016-08-17 ##
66 |
67 | * Fixed an issue that caused lightwallet to fail in Firefox 48+. By [miladmostavi](https://github.com/miladmostavi).
68 |
69 | ## Version 2.4.3 - 2016-06-20 ##
70 |
71 | * Update README.
72 |
73 | ## Version 2.4.2 - 2016-06-20 ##
74 |
75 | * Add more safety checks for valid password derived keys.
76 |
77 | ## Version 2.4.1 - 2016-06-08 ##
78 |
79 | * Add correct deserialization of default HD path. By [johnmcdowall](https://github.com/johnmcdowall).
80 |
81 | * Fix a bug where the default HD path was not set correctly in the constructor.
82 |
83 | ## Version 2.4.0 - 2016-06-08 ##
84 |
85 | * Add new message signing function `signMsgHash()`. By [Georgi87](https://github.com/Georgi87).
86 |
87 | ## Version 2.3.3 - 2016-05-26 ##
88 |
89 | * Fixed a bug which would create random addresses if the wrong
90 | pwDerivedKey was used.
91 |
92 | ## Version 2.3.2 - 2016-04-06 ##
93 |
94 | * Add "var" statements for function declarations. Thanks to [dalexj](https://github.com/dalexj) and [pipermerriam](https://github.com/pipermerriam).
95 |
96 | ## Version 2.3.1 - 2016-04-06 ##
97 |
98 | * Add missing built files. Thanks to [area](https://github.com/area).
99 |
100 | ## Version 2.3.0 - 2016-03-31 ##
101 |
102 | * Add functions `signMsg` and `recoverAddress` for signing messages and recovering the signing address. Thanks to [ckeenan](https://github.com/ckeenan) and [Georgi87](https://github.com/Georgi87).
103 |
104 | ## Version 2.2.5 - 2016-03-16 ##
105 |
106 | * Fixed a bug where there uglify would cause an infinite loop in the elliptic library. Thanks to [pelle](https://github.com/pelle) for the fix.
107 |
108 | ## Version 2.2.4 - 2016-03-16 ##
109 |
110 | * Update dependencies
111 |
112 | ## Version 2.2.3 - 2016-03-03 ##
113 |
114 | * Fixed bug in serialization
115 | * Add non-minified distributable
116 |
117 | ## Version 2.2.2 - 2016-02-26 ##
118 |
119 | * Update distributable.
120 |
121 | ## Version 2.2.1 - 2016-02-25 ##
122 |
123 | * Handle bug from bitcore where leading zeros are stripped. We do this by padding the private key to 32 bytes in the `keystore._generatePrivKeys()` function.
124 |
125 | * Remove unsupported `string.repeat()` function. H/T [chrisforrester](https://github.com/chrisforrester).
126 |
127 | * Change `Uint8Array.from()` to `new Uint8Array` in key derivation. H/T [chrisforrester](https://github.com/chrisforrester).
128 |
129 | * Update `ethereumjs-tx` library dependency.
130 |
131 | * Hardened dependency on `bignumber.js` to specific commit.
132 |
133 | ## Version 2.2.0 - 2016-02-14 ##
134 |
135 | * Change order of parameters in `encryption` module.
136 |
137 | * Add function `keystore.isDerivedKeyCorrect()`.
138 |
139 | * Removed redundant data members `keyHash, salt` of the keystore.
140 |
141 | ## Version 2.1.0 - 2016-02-14 ##
142 |
143 | * Refactoring by [cubedro](https://github.com/cubedro) - move functions out to separate modules (signing, encryption) in order to make the core keystore object less cluttered.
144 |
145 | ## Version 2.0.0 - 2016-02-09 ##
146 |
147 | * Big refactoring of password handling. Key derivation is now moved out into an asyncronous function allowing for more secure password-based key derivation or user-supplied keys. A helper function is provided with Scrypt key derivation.
148 |
149 | * Change from using AES for keystore encryption to using xsalsa20 (in the form of `nacl.secretbox`). This provides a simpler interface for the symmetric encryption.
150 |
151 | * Updated tests with the new password handling and correct usage of `done()`.
152 |
153 | ## Version 1.0.1 - 2016-01-19 ##
154 |
155 | * Formatting changes in documentation.
156 |
157 | ## Version 1.0.0 - 2015-12-09 ##
158 |
159 | * Ability to have multiple HD derivation paths - allowing multiple Personas from one wallet seed
160 |
161 | * The ability to designate that keys from a derivation path should be used for asymmetric encryption using Curve25519
162 |
163 | * Ability to encrypt messages using keys in the lightwallet: messages can be encrypted to multiple recipients, allowing selective disclosure of Persona attributes as well as things like encrypted group chats between Personas
164 |
165 | * A massive test of the private key —> address functionality using a file with 10000 pseudorandomly generated private keys.
166 |
167 | * Fixed issues with nested `bitcore-lib` packages that would cause the build to fail with NPM3
168 |
--------------------------------------------------------------------------------
/example/webwallet.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
155 | LightWallet
156 | New Wallet
157 |
158 |
159 |
160 | Restore Wallet
161 |
162 |
163 |
164 | Show Addresses
165 | Show more address(es)
166 |
167 |
168 |
169 |
170 |
171 |
172 | Send Ether
173 | From:
174 | To:
175 | Ether:
176 |
177 |
178 |
179 | Show Seed
180 |
181 | Function Call
182 | Caller:
183 | Contract Address:
184 | Contract ABI:
185 | Function Name:
186 | Function Arguments:
187 | Value (Ether):
188 |
189 |
190 |
191 |
192 |
193 |
--------------------------------------------------------------------------------
/test/fixtures/keystore.json:
--------------------------------------------------------------------------------
1 | {
2 | "valid": [
3 | {
4 | "password": "password",
5 | "salt": "strangeSalt",
6 | "pwDerivedKey": [
7 | 205,
8 | 127,
9 | 41,
10 | 55,
11 | 40,
12 | 243,
13 | 95,
14 | 138,
15 | 187,
16 | 239,
17 | 244,
18 | 242,
19 | 33,
20 | 239,
21 | 174,
22 | 4,
23 | 146,
24 | 184,
25 | 75,
26 | 185,
27 | 221,
28 | 160,
29 | 223,
30 | 207,
31 | 124,
32 | 49,
33 | 16,
34 | 208,
35 | 237,
36 | 141,
37 | 15,
38 | 120
39 | ],
40 | "hdPathString": "m/0'/0'/0'",
41 | "seed": "77c2b00716cec7213839159e404db50d",
42 | "mnSeed": "jelly better achieve collect unaware mountain thought cargo oxygen act hood bridge",
43 | "hdIndex": 0,
44 | "privKeyHex": "7627f5655d3f103f0be5c90064bd3557995604e6208590986de4e1230425c1ae",
45 | "address": "0x07ed7f762488ffa46298afeba33f05a04c796833",
46 | "ethjsTxParams": {
47 | "from": "0x07ed7f762488ffa46298afeba33f05a04c796833",
48 | "to": "0x9e2068cce22de4e1e80f15cb71ef435a20a3b37c",
49 | "nonce": "0x00",
50 | "value": "0xde0b6b3a7640000",
51 | "gasLimit": "0x2fefd8",
52 | "gasPrice": "0xba43b7400",
53 | "data": "0xabcdef01234567890"
54 | },
55 | "web3TxParams": {
56 | "from": "0x07ed7f762488ffa46298afeba33f05a04c796833",
57 | "to": "0x9e2068cce22de4e1e80f15cb71ef435a20a3b37c",
58 | "nonce": "0x00",
59 | "value": "0xde0b6b3a7640000",
60 | "gas": "0x2fefd8",
61 | "gasPrice": "0xba43b7400",
62 | "data": "0xabcdef01234567890"
63 | },
64 | "rawUnsignedTx": "f680850ba43b7400832fefd8949e2068cce22de4e1e80f15cb71ef435a20a3b37c880de0b6b3a7640000890abcdef012345678901c8080",
65 | "rawSignedTx": "f87680850ba43b7400832fefd8949e2068cce22de4e1e80f15cb71ef435a20a3b37c880de0b6b3a7640000890abcdef012345678901ca08de63b51b4b9cc5bc09f7f9610d707f43c5840b37dffe655d0073b270032511ba01c31647c5cb6d32676e73bd0ea618fa9f3fa2a7c0132e11a593b2e44cc500323",
66 | "m/0'/0'/1'": {
67 | "addresses": [
68 | "0xd540ec8e4de76484e31cdce612f1d4da196e6987",
69 | "0xf65f355fd6c0771a9fa9dc31191dcafe1cc79934",
70 | "0x1fb2a56e077aa64236623baea6fae20c6209d885",
71 | "0x3c2b55c8019eec2ceea5154cc6d150e877a68a69",
72 | "0xee77f5a7f61ff11b8734c043db0ed5bc7afd8447"
73 | ]
74 | },
75 | "m/0'/0'/5'": {
76 | "addresses": [
77 | "0x427bfa1ebd65dd5a136ed3112fe5f72e7cebe43a",
78 | "0xa4c2f35340307e50b453ccd6574db054d2ef357a",
79 | "0x618c683268c1c2a75f28e158943977e81b7568a1",
80 | "0x060e9bb7211af94a13f2d09cec767d5d2ca4acc1",
81 | "0x558e33b4272da4b004470a4ada4e673cc972379a",
82 | "0x6b85cc8f612d5457d49775439335f83e12b8cfde",
83 | "0xcbd22ff1ded1423fbc24a7af2148745878800024"
84 | ]
85 | },
86 | "m/0'/0'/2'": {
87 | "pubKeys": [
88 | "9b2b6699ad63eaf4f08c5a16d29eaf9db40dfb47b6c41d16bb00368564b76f10",
89 | "eaa70a8727385e6e0af7ce718874065a3cba0b994555543288b79901e320be20",
90 | "70d505a34e1ff438a6e0fca3ba37ba8e3c7d7b9843a5762b45d3be9e4d38633f",
91 | "7c2864ef868146251c881d2cb4e92cab3a1868bb3fc49b233a4471dcb1bc050e",
92 | "9c493f840b54cb4d48c1dbc8dacf4b7fa6a471e4f462eae3f64ebf10fcd9430c",
93 | "170023f3a4ef99ae36f97bfedb8e55be277755a2b1b8caa35067cfe93e467b1d"
94 | ]
95 | }
96 | },
97 | {
98 | "password": "password",
99 | "salt": "lightwalletSalt",
100 | "pwDerivedKey": [
101 | 12,
102 | 37,
103 | 184,
104 | 176,
105 | 141,
106 | 255,
107 | 15,
108 | 70,
109 | 189,
110 | 195,
111 | 206,
112 | 218,
113 | 109,
114 | 172,
115 | 141,
116 | 233,
117 | 117,
118 | 114,
119 | 2,
120 | 10,
121 | 156,
122 | 57,
123 | 255,
124 | 102,
125 | 37,
126 | 66,
127 | 2,
128 | 177,
129 | 82,
130 | 70,
131 | 1,
132 | 246
133 | ],
134 | "hdPathString": "m/0'/0'/0'",
135 | "seed": "77c2b00716cec7213839159e404db50d",
136 | "mnSeed": "jelly better achieve collect unaware mountain thought cargo oxygen act hood bridge",
137 | "hdIndex": 0,
138 | "privKeyHex": "7627f5655d3f103f0be5c90064bd3557995604e6208590986de4e1230425c1ae",
139 | "address": "0x07ed7f762488ffa46298afeba33f05a04c796833",
140 | "ethjsTxParams": {
141 | "from": "0x07ed7f762488ffa46298afeba33f05a04c796833",
142 | "to": "0x9e2068cce22de4e1e80f15cb71ef435a20a3b37c",
143 | "nonce": "0x00",
144 | "value": "0xde0b6b3a7640000",
145 | "gasLimit": "0x2fefd8",
146 | "gasPrice": "0xba43b7400",
147 | "data": "0xabcdef01234567890"
148 | },
149 | "web3TxParams": {
150 | "from": "0x07ed7f762488ffa46298afeba33f05a04c796833",
151 | "to": "0x9e2068cce22de4e1e80f15cb71ef435a20a3b37c",
152 | "nonce": "0x00",
153 | "value": "0xde0b6b3a7640000",
154 | "gas": "0x2fefd8",
155 | "gasPrice": "0xba43b7400",
156 | "data": "0xabcdef01234567890"
157 | },
158 | "rawUnsignedTx": "f680850ba43b7400832fefd8949e2068cce22de4e1e80f15cb71ef435a20a3b37c880de0b6b3a7640000890abcdef012345678901c8080",
159 | "rawSignedTx": "f87680850ba43b7400832fefd8949e2068cce22de4e1e80f15cb71ef435a20a3b37c880de0b6b3a7640000890abcdef012345678901ca08de63b51b4b9cc5bc09f7f9610d707f43c5840b37dffe655d0073b270032511ba01c31647c5cb6d32676e73bd0ea618fa9f3fa2a7c0132e11a593b2e44cc500323",
160 | "m/0'/0'/1'": {
161 | "addresses": [
162 | "0xd540ec8e4de76484e31cdce612f1d4da196e6987",
163 | "0xf65f355fd6c0771a9fa9dc31191dcafe1cc79934",
164 | "0x1fb2a56e077aa64236623baea6fae20c6209d885",
165 | "0x3c2b55c8019eec2ceea5154cc6d150e877a68a69",
166 | "0xee77f5a7f61ff11b8734c043db0ed5bc7afd8447"
167 | ]
168 | },
169 | "m/0'/0'/5'": {
170 | "addresses": [
171 | "0x427bfa1ebd65dd5a136ed3112fe5f72e7cebe43a",
172 | "0xa4c2f35340307e50b453ccd6574db054d2ef357a",
173 | "0x618c683268c1c2a75f28e158943977e81b7568a1",
174 | "0x060e9bb7211af94a13f2d09cec767d5d2ca4acc1",
175 | "0x558e33b4272da4b004470a4ada4e673cc972379a",
176 | "0x6b85cc8f612d5457d49775439335f83e12b8cfde",
177 | "0xcbd22ff1ded1423fbc24a7af2148745878800024"
178 | ]
179 | },
180 | "m/0'/0'/2'": {
181 | "pubKeys": [
182 | "9b2b6699ad63eaf4f08c5a16d29eaf9db40dfb47b6c41d16bb00368564b76f10",
183 | "eaa70a8727385e6e0af7ce718874065a3cba0b994555543288b79901e320be20",
184 | "70d505a34e1ff438a6e0fca3ba37ba8e3c7d7b9843a5762b45d3be9e4d38633f",
185 | "7c2864ef868146251c881d2cb4e92cab3a1868bb3fc49b233a4471dcb1bc050e",
186 | "9c493f840b54cb4d48c1dbc8dacf4b7fa6a471e4f462eae3f64ebf10fcd9430c",
187 | "170023f3a4ef99ae36f97bfedb8e55be277755a2b1b8caa35067cfe93e467b1d"
188 | ]
189 | }
190 | },
191 | {
192 | "password": "asdflknwqeroilasdflkjnzvmnwmet",
193 | "salt": "lightwalletSalt",
194 | "pwDerivedKey": [
195 | 24,
196 | 100,
197 | 147,
198 | 66,
199 | 23,
200 | 117,
201 | 206,
202 | 186,
203 | 27,
204 | 54,
205 | 254,
206 | 137,
207 | 187,
208 | 174,
209 | 103,
210 | 5,
211 | 38,
212 | 18,
213 | 139,
214 | 128,
215 | 139,
216 | 74,
217 | 217,
218 | 188,
219 | 166,
220 | 34,
221 | 0,
222 | 30,
223 | 54,
224 | 133,
225 | 158,
226 | 208
227 | ],
228 | "seed": "0460ef47585604c5660618db2e6a7e7f",
229 | "mnSeed": "afford alter spike radar gate glance object seek swamp infant panel yellow",
230 | "hdIndex": 1,
231 | "hdPathString": "m/0'/0'/0'",
232 | "privKeyHex": "8846068358cda43b5f00a0898ccb381402062b7cd668c0f9233845e4c0c9bc73",
233 | "address": "0x50cca5d6fd56696eaa085ca7f401c1b42c747642"
234 | },
235 | {
236 | "password": "!@*#()",
237 | "salt": "lightwalletSalt",
238 | "pwDerivedKey": [
239 | 51,
240 | 238,
241 | 218,
242 | 224,
243 | 61,
244 | 216,
245 | 29,
246 | 19,
247 | 249,
248 | 245,
249 | 254,
250 | 125,
251 | 54,
252 | 245,
253 | 160,
254 | 253,
255 | 1,
256 | 73,
257 | 137,
258 | 234,
259 | 137,
260 | 138,
261 | 109,
262 | 226,
263 | 61,
264 | 11,
265 | 128,
266 | 41,
267 | 76,
268 | 115,
269 | 80,
270 | 43
271 | ],
272 | "seed": "eaebabb2383351fd31d703840b32e9e2",
273 | "mnSeed": "turtle front uncle idea crush write shrug there lottery flower risk shell",
274 | "hdIndex": 2,
275 | "hdPathString": "m/0'/0'/0'",
276 | "privKeyHex": "c2e25d4acaa7ff2161aeff1063227c8a6df62fc74b2f62fa218642bfb21e1072",
277 | "address": "0x252192e13218f6897fb8434c7f93f319a2a0efe9"
278 | },
279 | {
280 | "password": "PassHello",
281 | "salt": "lightwalletSalt",
282 | "pwDerivedKey": [
283 | 113,
284 | 91,
285 | 117,
286 | 133,
287 | 102,
288 | 129,
289 | 128,
290 | 16,
291 | 251,
292 | 116,
293 | 91,
294 | 122,
295 | 215,
296 | 255,
297 | 183,
298 | 202,
299 | 72,
300 | 108,
301 | 222,
302 | 129,
303 | 238,
304 | 46,
305 | 143,
306 | 236,
307 | 132,
308 | 13,
309 | 127,
310 | 227,
311 | 110,
312 | 202,
313 | 254,
314 | 112
315 | ],
316 | "seed": "18ab19a9f54a9274f03e5209a2ac8a91",
317 | "mnSeed": "board flee heavy tunnel powder denial science ski answer betray cargo cat",
318 | "depth": 1,
319 | "hdIndex": 100,
320 | "hdPathString": "m/0'/0'/0'",
321 | "privKeyHex": "8cf46f325252f13471a902f026abaf4fa70a7c2d38b6e23a040543cd2c8100a2",
322 | "address": "0x1ef45f7d20b7d9bd2985644968f476db184d3498"
323 | }
324 | ],
325 | "invalid": [
326 | {
327 | "mnSeed": "board turtle heavy tunnel powder denial science ski answer betray cargo cat"
328 | }
329 | ],
330 | "sha256Test": [
331 | {
332 | "ent0": "abcdefghbcdefghicdefghijdefghijke",
333 | "ent1": "fghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
334 | "targetHash": "cf5b16a778af8380036ce59e7b0492370b249b11e8f07a51afac45037afee9d1"
335 | },
336 | {
337 | "ent0": "abcdbcdecdefdefgefghfghighijhijkijkljklmklm",
338 | "ent1": "nlmnomnopnopq",
339 | "targetHash": "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"
340 | },
341 | {
342 | "ent0": "ab",
343 | "ent1": "c",
344 | "targetHash": "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
345 | }
346 | ]
347 | }
348 |
--------------------------------------------------------------------------------
/lib/keystore.js:
--------------------------------------------------------------------------------
1 | const CryptoJS = require('crypto-js');
2 | const Util = require('ethereumjs-util');
3 | const EC = require('elliptic').ec;
4 | const BitCore = require('bitcore-lib');
5 | const Random = BitCore.crypto.Random;
6 | const Hash = BitCore.crypto.Hash;
7 | const Mnemonic = require('bitcore-mnemonic');
8 | const Nacl = require('tweetnacl');
9 | const NaclUtil = require('tweetnacl-util');
10 | const ScryptAsync = require('scrypt-async');
11 |
12 | const Assert = require('./assert');
13 | const Encryption = require('./encryption');
14 | const Signing = require('./signing');
15 | const TxUtils = require('./txutils');
16 |
17 | const ec = new EC('secp256k1');
18 |
19 | function leftPadString(stringToPad, padChar, length) {
20 | let repeatedPadChar = '';
21 |
22 | for (let i = 0; i < length; i++) {
23 | repeatedPadChar += padChar;
24 | }
25 |
26 | return ((repeatedPadChar + stringToPad).slice(-length));
27 | }
28 |
29 | const KeyStore = function () {
30 | };
31 |
32 | KeyStore.prototype.init = function (mnemonic, pwDerivedKey, hdPathString, salt) {
33 | this.salt = salt;
34 | this.hdPathString = hdPathString;
35 | this.encSeed = undefined;
36 | this.encHdRootPriv = undefined;
37 | this.version = 3;
38 | this.hdIndex = 0;
39 | this.encPrivKeys = {};
40 | this.addresses = [];
41 |
42 | if ((typeof pwDerivedKey !== 'undefined') && (typeof mnemonic !== 'undefined')) {
43 | const words = mnemonic.split(' ');
44 |
45 | if (!KeyStore.isSeedValid(mnemonic) || words.length !== 12) {
46 | throw new Error('KeyStore: Invalid mnemonic');
47 | }
48 |
49 | // Pad the seed to length 120 before encrypting
50 | const paddedSeed = leftPadString(mnemonic, ' ', 120);
51 | this.encSeed = KeyStore._encryptString(paddedSeed, pwDerivedKey);
52 |
53 | // hdRoot is the relative root from which we derive the keys using generateNewAddress().
54 | // The derived keys are then `hdRoot/hdIndex`.
55 |
56 | const hdRoot = new Mnemonic(mnemonic).toHDPrivateKey().xprivkey;
57 | const hdRootKey = new BitCore.HDPrivateKey(hdRoot);
58 | const hdPathKey = hdRootKey.derive(hdPathString).xprivkey;
59 |
60 | this.encHdRootPriv = KeyStore._encryptString(hdPathKey, pwDerivedKey);
61 | }
62 | };
63 |
64 | KeyStore.prototype.isDerivedKeyCorrect = function (pwDerivedKey) {
65 | const paddedSeed = KeyStore._decryptString(this.encSeed, pwDerivedKey);
66 |
67 | return paddedSeed && paddedSeed.length > 0;
68 | };
69 |
70 | KeyStore.prototype.serialize = function () {
71 | return JSON.stringify({
72 | encSeed: this.encSeed,
73 | encHdRootPriv: this.encHdRootPriv,
74 | addresses: this.addresses,
75 | encPrivKeys: this.encPrivKeys,
76 | hdPathString: this.hdPathString,
77 | salt: this.salt,
78 | hdIndex: this.hdIndex,
79 | version: this.version,
80 | });
81 | };
82 |
83 | KeyStore.prototype.getAddresses = function () {
84 | return this.addresses.map((addr) => Util.addHexPrefix(addr));
85 | };
86 |
87 | KeyStore.prototype.getSeed = function (pwDerivedKey) {
88 | Assert.derivedKey(this, pwDerivedKey);
89 |
90 | const paddedSeed = KeyStore._decryptString(this.encSeed, pwDerivedKey);
91 |
92 | if (!paddedSeed || paddedSeed.length === 0) {
93 | throw new Error('Provided password derived key is wrong');
94 | }
95 |
96 | return paddedSeed.trim();
97 | };
98 |
99 | KeyStore.prototype.exportPrivateKey = function (address, pwDerivedKey) {
100 | Assert.derivedKey(this, pwDerivedKey);
101 |
102 | const addr = Util.stripHexPrefix(address).toLowerCase();
103 |
104 | if (this.encPrivKeys[addr] === undefined) {
105 | throw new Error('KeyStore.exportPrivateKey: Address not found in KeyStore');
106 | }
107 |
108 | const encPrivateKey = this.encPrivKeys[addr];
109 |
110 | return KeyStore._decryptKey(encPrivateKey, pwDerivedKey);
111 | };
112 |
113 | KeyStore.prototype.generateNewAddress = function (pwDerivedKey, n) {
114 | Assert.derivedKey(this, pwDerivedKey);
115 |
116 | if (!this.encSeed) {
117 | throw new Error('KeyStore.generateNewAddress: No seed set');
118 | }
119 |
120 | n = n || 1;
121 |
122 | const keys = this._generatePrivKeys(pwDerivedKey, n);
123 |
124 | for (let i = 0; i < n; i++) {
125 | const keyObj = keys[i];
126 | const address = KeyStore._computeAddressFromPrivKey(keyObj.privKey);
127 |
128 | this.encPrivKeys[address] = keyObj.encPrivKey;
129 | this.addresses.push(address);
130 | }
131 | };
132 |
133 | KeyStore.prototype.keyFromPassword = function (password, callback) {
134 | KeyStore.deriveKeyFromPasswordAndSalt(password, this.salt, callback);
135 | };
136 |
137 | KeyStore.prototype.passwordProvider = function (callback) {
138 | const password = prompt('Enter password to continue', 'Enter password');
139 |
140 | callback(null, password);
141 | };
142 |
143 | KeyStore.prototype.hasAddress = function (address, callback) {
144 | const addrToCheck = Util.stripHexPrefix(address);
145 |
146 | if (this.encPrivKeys[addrToCheck] === undefined) {
147 | const err = new Error('Address not found!');
148 | callback(err, false);
149 | return;
150 | }
151 |
152 | callback(null, true);
153 | };
154 |
155 | KeyStore.prototype.signTransaction = function (txParams, callback) {
156 | const { gas, ...params } = txParams;
157 | const txObj = {
158 | ...params,
159 | gasLimit: gas,
160 | };
161 |
162 | const tx = TxUtils.createTx(txObj);
163 | const rawTx = TxUtils.txToHexString(tx);
164 | const signingAddress = Util.stripHexPrefix(txParams.from);
165 |
166 | this.passwordProvider((err, password) => {
167 | if (err) {
168 | callback(err);
169 | return;
170 | }
171 |
172 | this.keyFromPassword(password, (err, pwDerivedKey) => {
173 | if (err) {
174 | callback(err);
175 | return;
176 | }
177 |
178 | const signedTx = Signing.signTx(this, pwDerivedKey, rawTx, signingAddress);
179 |
180 | callback(null, Util.addHexPrefix(signedTx));
181 | });
182 | });
183 | };
184 |
185 | KeyStore.prototype._generatePrivKeys = function (pwDerivedKey, n) {
186 | Assert.derivedKey(this, pwDerivedKey);
187 |
188 | const hdRoot = KeyStore._decryptString(this.encHdRootPriv, pwDerivedKey);
189 |
190 | if (!hdRoot || hdRoot.length === 0) {
191 | throw new Error('Provided password derived key is wrong');
192 | }
193 |
194 | const keys = [];
195 |
196 | for (let i = 0; i < n; i++) {
197 | const hdPrivateKey = new BitCore.HDPrivateKey(hdRoot).derive(this.hdIndex++);
198 | const privateKeyBuf = hdPrivateKey.privateKey.toBuffer();
199 | let privateKeyHex = privateKeyBuf.toString('hex');
200 |
201 | if (privateKeyBuf.length < 16) {
202 | // Way too small key, something must have gone wrong
203 | // Halt and catch fire
204 | throw new Error('Private key suspiciously small: < 16 bytes. Aborting!');
205 | } else if (privateKeyBuf.length > 32) {
206 | throw new Error('Private key larger than 32 bytes. Aborting!');
207 | } else if (privateKeyBuf.length < 32) {
208 | // Pad private key if too short
209 | // bitcore has a bug where it sometimes returns
210 | // truncated keys
211 | privateKeyHex = leftPadString(privateKeyBuf.toString('hex'), '0', 64);
212 | }
213 |
214 | const encPrivateKey = KeyStore._encryptKey(privateKeyHex, pwDerivedKey);
215 |
216 | keys[i] = {
217 | privKey: privateKeyHex,
218 | encPrivKey: encPrivateKey
219 | };
220 | }
221 |
222 | return keys;
223 | };
224 |
225 | KeyStore.createVault = function (opts, cb) {
226 | const { hdPathString, seedPhrase, password } = opts;
227 | let salt = opts.salt;
228 |
229 | // Default hdPathString
230 | if (!hdPathString) {
231 | const err = new Error('Keystore: Must include hdPathString in createVault inputs. Suggested alternatives are m/0\'/0\'/0\' for previous lightwallet default, or m/44\'/60\'/0\'/0 for BIP44 (used by Jaxx & MetaMask)');
232 | return cb(err);
233 | }
234 |
235 | if (!seedPhrase) {
236 | const err = new Error('Keystore: Must include seedPhrase in createVault inputs.');
237 | return cb(err);
238 | }
239 |
240 | if (!salt) {
241 | salt = KeyStore.generateSalt(32);
242 | }
243 |
244 | KeyStore.deriveKeyFromPasswordAndSalt(password, salt, (err, pwDerivedKey) => {
245 | if (err) {
246 | cb(err);
247 | return;
248 | }
249 |
250 | const ks = new KeyStore();
251 |
252 | ks.init(seedPhrase, pwDerivedKey, hdPathString, salt);
253 |
254 | cb(null, ks);
255 | });
256 | };
257 |
258 | KeyStore.generateSalt = function (byteCount) {
259 | return BitCore.crypto.Random.getRandomBuffer(byteCount || 32).toString('base64');
260 | };
261 |
262 | // Generates a random seed. If the optional string extraEntropy is set,
263 | // a random set of entropy is created, then concatenated with extraEntropy
264 | // and hashed to produce the entropy that gives the seed.
265 | // Thus if extraEntropy comes from a high-entropy source (like dice)
266 | // it can give some protection from a bad RNG.
267 | // If extraEntropy is not set, the random number generator is used directly.
268 | KeyStore.generateRandomSeed = function (extraEntropy) {
269 | let seed = '';
270 |
271 | if (extraEntropy === undefined) {
272 | seed = new Mnemonic(Mnemonic.Words.ENGLISH);
273 | } else if (typeof extraEntropy === 'string') {
274 | const entBuf = new Buffer(extraEntropy);
275 | const randBuf = Random.getRandomBuffer(256 / 8);
276 | const hashedEnt = this._concatAndSha256(randBuf, entBuf).slice(0, 128 / 8);
277 |
278 | seed = new Mnemonic(hashedEnt, Mnemonic.Words.ENGLISH);
279 | } else {
280 | throw new Error('generateRandomSeed: extraEntropy is set but not a string.');
281 | }
282 |
283 | return seed.toString();
284 | };
285 |
286 | KeyStore.isSeedValid = function (seed) {
287 | return Mnemonic.isValid(seed, Mnemonic.Words.ENGLISH);
288 | };
289 |
290 | KeyStore.deserialize = function (keystore) {
291 | const dataKS = JSON.parse(keystore);
292 | const { version, salt, encSeed, encHdRootPriv, encPrivKeys, hdIndex, hdPathString, addresses } = dataKS;
293 |
294 | if (version === undefined || version < 3) {
295 | throw new Error('Old version of serialized keystore. Please use KeyStore.upgradeOldSerialized() to convert it to the latest version.');
296 | }
297 |
298 | const ks = new KeyStore();
299 |
300 | ks.salt = salt;
301 | ks.hdPathString = hdPathString;
302 | ks.encSeed = encSeed;
303 | ks.encHdRootPriv = encHdRootPriv;
304 | ks.version = version;
305 | ks.hdIndex = hdIndex;
306 | ks.encPrivKeys = encPrivKeys;
307 | ks.addresses = addresses;
308 |
309 | return ks;
310 | };
311 |
312 | KeyStore.deriveKeyFromPasswordAndSalt = function (password, salt, callback) {
313 | // Do not require salt, and default it to 'lightwalletSalt'
314 | // (for backwards compatibility)
315 | if (!callback && typeof salt === 'function') {
316 | callback = salt;
317 | salt = KeyStore.DEFAULT_SALT;
318 | } else if (!salt && typeof callback === 'function') {
319 | salt = KeyStore.DEFAULT_SALT;
320 | }
321 |
322 | const logN = 14;
323 | const r = 8;
324 | const dkLen = 32;
325 | const interruptStep = 200;
326 |
327 | const cb = function (derKey) {
328 | let err = null;
329 | let ui8arr = null;
330 |
331 | try {
332 | ui8arr = (new Uint8Array(derKey));
333 | } catch (e) {
334 | err = e;
335 | }
336 |
337 | callback(err, ui8arr);
338 | };
339 |
340 | ScryptAsync(password, salt, logN, r, dkLen, interruptStep, cb, null);
341 | };
342 |
343 | KeyStore._encryptString = function (string, pwDerivedKey) {
344 | const nonce = Nacl.randomBytes(Nacl.secretbox.nonceLength);
345 | const encStr = Nacl.secretbox(NaclUtil.decodeUTF8(string), nonce, pwDerivedKey);
346 |
347 | return {
348 | encStr: NaclUtil.encodeBase64(encStr),
349 | nonce: NaclUtil.encodeBase64(nonce),
350 | };
351 | };
352 |
353 | KeyStore._decryptString = function (encryptedStr, pwDerivedKey) {
354 | const decStr = NaclUtil.decodeBase64(encryptedStr.encStr);
355 | const nonce = NaclUtil.decodeBase64(encryptedStr.nonce);
356 |
357 | const decryptedStr = Nacl.secretbox.open(decStr, nonce, pwDerivedKey);
358 |
359 | if (decryptedStr === null) {
360 | return false;
361 | }
362 |
363 | return NaclUtil.encodeUTF8(decryptedStr);
364 | };
365 |
366 | KeyStore._encryptKey = function (privateKey, pwDerivedKey) {
367 | const nonce = Nacl.randomBytes(Nacl.secretbox.nonceLength);
368 | const privateKeyArray = Encryption.decodeHex(privateKey);
369 | const encKey = Nacl.secretbox(privateKeyArray, nonce, pwDerivedKey);
370 |
371 | return {
372 | key: NaclUtil.encodeBase64(encKey),
373 | nonce: NaclUtil.encodeBase64(nonce),
374 | };
375 | };
376 |
377 | KeyStore._decryptKey = function (encryptedKey, pwDerivedKey) {
378 | const decKey = NaclUtil.decodeBase64(encryptedKey.key);
379 | const nonce = NaclUtil.decodeBase64(encryptedKey.nonce);
380 | const decryptedKey = Nacl.secretbox.open(decKey, nonce, pwDerivedKey);
381 |
382 | if (decryptedKey === null) {
383 | throw new Error('Decryption failed!');
384 | }
385 |
386 | return Encryption.encodeHex(decryptedKey);
387 | };
388 |
389 | KeyStore._computeAddressFromPrivKey = function (privateKey) {
390 | const keyPair = ec.genKeyPair();
391 | keyPair._importPrivate(privateKey, 'hex');
392 |
393 | const pubKey = keyPair.getPublic(false, 'hex').slice(2);
394 | const pubKeyWordArray = CryptoJS.enc.Hex.parse(pubKey);
395 | const hash = CryptoJS.SHA3(pubKeyWordArray, { outputLength: 256 });
396 | const address = hash.toString(CryptoJS.enc.Hex).slice(24);
397 |
398 | return address;
399 | };
400 |
401 | KeyStore._computePubkeyFromPrivKey = function (privKey, curve) {
402 | if (curve !== 'curve25519') {
403 | throw new Error('KeyStore._computePubkeyFromPrivKey: Only "curve25519" supported.');
404 | }
405 |
406 | const privateKeyBase64 = (new Buffer(privKey, 'hex')).toString('base64');
407 | const privateKeyUInt8Array = NaclUtil.decodeBase64(privateKeyBase64);
408 | const pubKey = Nacl.box.keyPair.fromSecretKey(privateKeyUInt8Array).publicKey;
409 | const pubKeyBase64 = NaclUtil.encodeBase64(pubKey);
410 | const pubKeyHex = (new Buffer(pubKeyBase64, 'base64')).toString('hex');
411 |
412 | return pubKeyHex;
413 | };
414 |
415 | // This function is tested using the test vectors here:
416 | // http://www.di-mgt.com.au/sha_testvectors.html
417 | KeyStore._concatAndSha256 = function (entropyBuf0, entropyBuf1) {
418 | const totalEnt = Buffer.concat([entropyBuf0, entropyBuf1]);
419 |
420 | if (totalEnt.length !== entropyBuf0.length + entropyBuf1.length) {
421 | throw new Error('generateRandomSeed: Logic error! Concatenation of entropy sources failed.');
422 | }
423 |
424 | return Hash.sha256(totalEnt);
425 | };
426 |
427 | KeyStore.DEFAULT_SALT = 'lightwalletSalt';
428 |
429 | module.exports = KeyStore;
430 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # LightWallet
2 |
3 | A minimal ethereum javascript wallet.
4 |
5 | ## About
6 |
7 | LightWallet is a HD wallet that can store your private keys encrypted in the browser to allow you to run Ethereum dapps even if you're not running a local Ethereum node. It uses [BIP32][] and [BIP39][] to generate an HD tree of addresses from a randomly generated 12-word seed.
8 |
9 | LightWallet is primarily intended to be a signing provider for the [Hooked Web3 provider](https://github.com/ConsenSys/hooked-web3-provider) through the `keystore` module. This allows you to have full control over your private keys while still connecting to a remote node to relay signed transactions. Moreover, the `txutils` functions can be used to construct transactions when offline, for use in e.g. air-gapped coldwallet implementations.
10 |
11 | The default BIP32 HD derivation path has been `m/0'/0'/0'/i`, but any HD path can be chosen.
12 |
13 | ## Security
14 |
15 | Please note that LightWallet has not been through a comprehensive security review at this point. It is still experimental software, intended for small amounts of Ether to be used for interacting with smart contracts on the Ethereum blockchain. Do not rely on it to store larger amounts of Ether yet.
16 |
17 | ## Get Started
18 |
19 | ```
20 | npm install eth-lightwallet
21 | ```
22 |
23 | The `eth-lightwallet` package contains `dist/lightwallet.min.js` that can be included in an HTML page:
24 |
25 | ```html
26 |
27 |
28 |
29 |
30 |
31 | ```
32 |
33 | The file `lightwallet.min.js` exposes the global object `lightwallet` to the browser which has the two main modules `lightwallet.keystore` and `lightwallet.txutils`.
34 |
35 | Sample recommended usage with hooked web3 provider:
36 |
37 | ```js
38 | // the seed is stored encrypted by a user-defined password
39 | var password = prompt('Enter password for encryption', 'password');
40 |
41 | keyStore.createVault({
42 | password: password,
43 | // seedPhrase: seedPhrase, // Optionally provide a 12-word seed phrase
44 | // salt: fixture.salt, // Optionally provide a salt.
45 | // A unique salt will be generated otherwise.
46 | // hdPathString: hdPath // Optional custom HD Path String
47 | }, function (err, ks) {
48 |
49 | // Some methods will require providing the `pwDerivedKey`,
50 | // Allowing you to only decrypt private keys on an as-needed basis.
51 | // You can generate that value with this convenient method:
52 | ks.keyFromPassword(password, function (err, pwDerivedKey) {
53 | if (err) throw err;
54 |
55 | // generate five new address/private key pairs
56 | // the corresponding private keys are also encrypted
57 | ks.generateNewAddress(pwDerivedKey, 5);
58 | var addr = ks.getAddresses();
59 |
60 | ks.passwordProvider = function (callback) {
61 | var pw = prompt("Please enter password", "Password");
62 | callback(null, pw);
63 | };
64 |
65 | // Now set ks as transaction_signer in the hooked web3 provider
66 | // and you can start using web3 using the keys/addresses in ks!
67 | });
68 | });
69 | ```
70 |
71 | Sample old-style usage with hooked web3 provider (still works, but less secure because uses fixed salts).
72 |
73 | ```js
74 | // generate a new BIP32 12-word seed
75 | var secretSeed = lightwallet.keystore.generateRandomSeed();
76 |
77 | // the seed is stored encrypted by a user-defined password
78 | var password = prompt('Enter password for encryption', 'password');
79 | lightwallet.keystore.deriveKeyFromPassword(password, function (err, pwDerivedKey) {
80 |
81 | var ks = new lightwallet.keystore(secretSeed, pwDerivedKey);
82 |
83 | // generate five new address/private key pairs
84 | // the corresponding private keys are also encrypted
85 | ks.generateNewAddress(pwDerivedKey, 5);
86 | var addr = ks.getAddresses();
87 |
88 | // Create a custom passwordProvider to prompt the user to enter their
89 | // password whenever the hooked web3 provider issues a sendTransaction
90 | // call.
91 | ks.passwordProvider = function (callback) {
92 | var pw = prompt("Please enter password", "Password");
93 | callback(null, pw);
94 | };
95 |
96 | // Now set ks as transaction_signer in the hooked web3 provider
97 | // and you can start using web3 using the keys/addresses in ks!
98 | });
99 | ```
100 |
101 | ## `keystore` Function definitions
102 |
103 | These are the interface functions for the keystore object. The keystore object holds a 12-word seed according to [BIP39][] spec. From this seed you can generate addresses and private keys, and use the private keys to sign transactions.
104 |
105 | Note: Addresses and RLP encoded data are in the form of hex-strings. Hex-strings start with `0x`.
106 |
107 | ### `keystore.createVault(options, callback)`
108 |
109 | This is the interface to create a new lightwallet keystore.
110 |
111 | #### Options
112 |
113 | * password: (mandatory) A string used to encrypt the vault when serialized.
114 | * seedPhrase: (mandatory) A twelve-word mnemonic used to generate all accounts.
115 | * salt: (optional) The user may supply the salt used to encrypt & decrypt the vault, otherwise a random salt will be generated.
116 | * hdPathString (mandatory): The user must provide a `BIP39` compliant HD Path String. Previously the default has been `m/0'/0'/0'`, another popular one is the BIP44 path string `m/44'/60'/0'/0`.
117 |
118 | ### `keystore.keyFromPassword(password, callback)`
119 |
120 | This instance method uses any internally-configured salt to return the appropriate `pwDerivedKey`.
121 |
122 | Takes the user's password as input and generates a symmetric key of type `Uint8Array` that is used to encrypt/decrypt the keystore.
123 |
124 | ### `keystore.isDerivedKeyCorrect(pwDerivedKey)`
125 |
126 | Returns `true` if the derived key can decrypt the seed, and returns `false` otherwise.
127 |
128 | ### `keystore.generateRandomSeed([extraEntropy])`
129 |
130 | Generates a string consisting of a random 12-word seed and returns it. If the optional argument string `extraEntropy` is present the random data from the Javascript RNG will be concatenated with `extraEntropy` and then hashed to produce the final seed. The string `extraEntropy` can be something like entropy from mouse movements or keyboard presses, or a string representing dice throws.
131 |
132 | ### `keystore.isSeedValid(seed)`
133 |
134 | Checks if `seed` is a valid 12-word seed according to the [BIP39][] specification.
135 |
136 | ### `keystore.generateNewAddress(pwDerivedKey, [num])`
137 |
138 | Allows the vault to generate additional internal address/private key pairs.
139 |
140 | The simplest usage is `ks.generateNewAddress(pwDerivedKey)`.
141 |
142 | Generates `num` new address/private key pairs (defaults to 1) in the keystore from the seed phrase, which will be returned with calls to `ks.getAddresses()`.
143 |
144 | ### `keystore.deserialize(serialized_keystore)`
145 |
146 | Takes a serialized keystore string `serialized_keystore` and returns a new keystore object.
147 |
148 | ### `keystore.serialize()`
149 |
150 | Serializes the current keystore object into a JSON-encoded string and returns that string.
151 |
152 | ### `keystore.getAddresses()`
153 |
154 | Returns a list of hex-string addresses currently stored in the keystore.
155 |
156 | ### `keystore.getSeed(pwDerivedKey)`
157 |
158 | Given the pwDerivedKey, decrypts and returns the users 12-word seed.
159 |
160 | ### `keystore.exportPrivateKey(address, pwDerivedKey)`
161 |
162 | Given the derived key, decrypts and returns the private key corresponding to `address`. This should be done sparingly as the recommended practice is for the `keystore` to sign transactions using `signing.signTx`, so there is normally no need to export private keys.
163 |
164 | ## `upgrade` Function definitions
165 |
166 | ### `keystore.upgradeOldSerialized(oldSerialized, password, callback)`
167 |
168 | Takes a serialized keystore in an old format and a password. The callback takes the upgraded serialized keystore as its second argument.
169 |
170 | ## `signing` Function definitions
171 |
172 | ### `signing.signTx(keystore, pwDerivedKey, rawTx, signingAddress, hdPathString)`
173 |
174 | Signs a transaction with the private key corresponding to `signingAddress`.
175 |
176 | #### Inputs
177 |
178 | * `keystore`: An instance of the keystore with which to sign the TX with.
179 | * `pwDerivedKey`: the users password derived key (Uint8Array)
180 | * `rawTx`: Hex-string defining an RLP-encoded raw transaction.
181 | * `signingAddress`: hex-string defining the address to send the transaction from.
182 | * `hdPathString`: (Optional) A path at which to create the encryption keys.
183 |
184 | #### Return value
185 |
186 | Hex-string corresponding to the RLP-encoded raw transaction.
187 |
188 | ### `signing.signMsg(keystore, pwDerivedKey, rawMsg, signingAddress, hdPathString)`
189 |
190 | Creates and signs a sha3 hash of a message with the private key corresponding to `signingAddress`.
191 |
192 | #### Inputs
193 |
194 | * `keystore`: An instance of the keystore with which to sign the TX with.
195 | * `pwDerivedKey`: the users password derived key (Uint8Array)
196 | * `rawMsg`: Message to be signed
197 | * `signingAddress`: hex-string defining the address corresponding to the signing private key.
198 | * `hdPathString`: (Optional) A path at which to create the encryption keys.
199 |
200 | #### Return value
201 |
202 | Signed hash as signature object with v, r and s values.
203 |
204 | ### `signing.signMsgHash(keystore, pwDerivedKey, msgHash, signingAddress, hdPathString)`
205 |
206 | Signs a sha3 message hash with the private key corresponding to `signingAddress`.
207 |
208 | #### Inputs
209 |
210 | * `keystore`: An instance of the keystore with which to sign the TX with.
211 | * `pwDerivedKey`: the users password derived key (Uint8Array)
212 | * `msgHash`: SHA3 hash to be signed
213 | * `signingAddress`: hex-string defining the address corresponding to the signing private key.
214 | * `hdPathString`: (Optional) A path at which to create the encryption keys.
215 |
216 | #### Return value
217 |
218 | Signed hash as signature object with v, r and s values.
219 |
220 | ### `signing.concatSig(signature)`
221 |
222 | Concatenates signature object to return signature as hex-string in the same format as `eth_sign` does.
223 |
224 | #### Inputs
225 |
226 | * `signature`: Signature object as returned from `signMsg` or ``signMsgHash`.
227 |
228 | #### Return value
229 |
230 | Concatenated signature object as hex-string.
231 |
232 | ### `signing.recoverAddress(rawMsg, v, r, s)`
233 |
234 | Recovers the signing address from the message `rawMsg` and the signature `v, r, s`.
235 |
236 |
237 | ## `encryption` Function definitions
238 |
239 | ### `encryption.multiEncryptString(keystore, pwDerivedKey, msg, myAddress, theirPubKeyArray)`
240 |
241 | **NOTE:** The format of encrypted messages has not been finalized and may change at any time, so only use this for ephemeral messages that do not need to be stored encrypted for a long time.
242 |
243 | Encrypts the string `msg` with a randomly generated symmetric key, then encrypts that symmetric key assymetrically to each of the pubkeys in `theirPubKeyArray`. The encrypted message can then be read only by sender and the holders of the private keys corresponding to the public keys in `theirPubKeyArray`. The returned object has the following form, where nonces and ciphertexts are encoded in base64:
244 |
245 | ```js
246 | { version: 1,
247 | asymAlg: 'curve25519-xsalsa20-poly1305',
248 | symAlg: 'xsalsa20-poly1305',
249 | symNonce: 'SLmxcH3/CPMCCJ7orkI7iSjetRlMmzQH',
250 | symEncMessage: 'iN4+/b5InlsVo5Bc7GTmaBh8SgWV8OBMHKHMVf7aq5O9eqwnIzVXeX4yzUWbw2w=',
251 | encryptedSymKey:
252 | [ { nonce: 'qcNCtKqiooYLlRuIrNlNVtF8zftoT5Cb',
253 | ciphertext: 'L8c12EJsFYM1K7udgHDRrdHhQ7ng+VMkzOdVFTjWu0jmUzpehFeqyoEyg8cROBmm' },
254 | { nonce: 'puD2x3wmQKu3OIyxgJq2kG2Hz01+dxXs',
255 | ciphertext: 'gLYtYpJbeFKXL/WAK0hyyGEelaL5Ddq9BU3249+hdZZ7xgTAZVL8tw+fIVcvpgaZ' },
256 | { nonce: '1g8VbftPnjc+1NG3zCGwZS8KO73yjucu',
257 | ciphertext: 'pftERJOPDV2dfP+C2vOwPWT43Q89V74Nfu1arNQeTMphSHqVuUXItbyCMizISTxG' },
258 | { nonce: 'KAH+cCxbFGSDjHDOBzDhMboQdFWepvBw',
259 | ciphertext: 'XWmmBmxLEyLTUmUBiWy2wDqedubsa0KTcufhKM7YfJn/eHWhDDptMxYDvaKisFmn' } ] }
260 | ```
261 |
262 | Note that no padding is applied to `msg`, so it's possible to deduce the length of the string `msg` from the ciphertext. If you don't want this information to be known, please apply padding to `msg` before calling this function.
263 |
264 | ### `encryption.multiDecryptString(keystore, pwDerivedKey, encMsg, theirPubKey, myAddress)`
265 |
266 | Decrypt a message `encMsg` created with the function
267 | `multiEncryptString()`. If successful, returns the original message
268 | string. If not successful, returns `false`.
269 |
270 | ### `encryption.addressToPublicEncKey(keystore, pwDerivedKey, address)`
271 |
272 | Gets the public encryption key corresponding to the private key of `address` in the `keystore`.
273 |
274 | ## `txutils` Function definitions
275 |
276 | These are the interface functions for the `txutils` module. These functions will create RLP encoded raw unsigned transactions which can be signed using the `keystore.signTx()` command.
277 |
278 | ### `txutils.createContractTx(fromAddress, txObject)`
279 |
280 | Using the data in `txObject`, creates an RLP-encoded transaction that will create the contract with compiled bytecode defined by `txObject.data`. Also computes the address of the created contract.
281 |
282 | #### Inputs
283 |
284 | * `fromAddress`: Address to send the transaction from
285 | * `txObject.gasLimit`: Gas limit
286 | * `txObject.gasPrice`: Gas price
287 | * `txObject.value`: Endowment (optional)
288 | * `txObject.nonce`: Nonce of `fromAddress`
289 | * `txObject.data`: Compiled code of the contract
290 |
291 | #### Output
292 |
293 | Object `obj` with fields
294 |
295 | * `obj.tx`: RLP encoded transaction (hex string)
296 | * `obj.addr`: Address of the created contract
297 |
298 | ### `txutils.functionTx(abi, functionName, args, txObject)`
299 |
300 | Creates a transaction calling a function with name `functionName`, with arguments `args` conforming to `abi`. The function is defined in a contract with address `txObject.to`.
301 |
302 | #### Inputs
303 |
304 | * `abi`: Json-formatted ABI as returned from the `solc` compiler
305 | * `functionName`: string with the function name
306 | * `args`: Array with the arguments to the function
307 | * `txObject.to`: Address of the contract
308 | * `txObject.gasLimit`: Gas limit
309 | * `txObject.gasPrice`: Gas price
310 | * `txObject.value`: Value to send
311 | * `txObject.nonce`: Nonce of sending address
312 |
313 | #### Output
314 |
315 | RLP-encoded hex string defining the transaction.
316 |
317 |
318 | ### `txutils.valueTx(txObject)`
319 |
320 | Creates a transaction sending value to `txObject.to`.
321 |
322 | #### Inputs
323 |
324 | * `txObject.to`: Address to send to
325 | * `txObject.gasLimit`: Gas limit
326 | * `txObject.gasPrice`: Gas price
327 | * `txObject.value`: Value to send
328 | * `txObject.nonce`: Nonce of sending address
329 |
330 | #### Output
331 |
332 | RLP-encoded hex string defining the transaction.
333 |
334 | ## Examples
335 |
336 | See the file `example_usage.js` for usage of `keystore` and `txutils` in node.
337 |
338 | See the file `example_web.html` for an example of how to use the LightWallet keystore together with the Hooked Web3 Provider in the browser.
339 |
340 | ## Tests
341 |
342 | Run all tests:
343 |
344 | ```
345 | npm run test
346 | npm run coverage
347 | ```
348 |
349 | [BIP39]: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
350 | [BIP32]: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
351 |
352 | ## License
353 |
354 | MIT License.
355 |
--------------------------------------------------------------------------------
/test/keystore.js:
--------------------------------------------------------------------------------
1 | const { expect } = require('chai');
2 | const Promise = require('bluebird');
3 | const KeyStore = require('../lib/keystore');
4 | const Upgrade = require('../lib/upgrade');
5 | const fixtures = require('./fixtures/keystore');
6 |
7 | // Test with 100 private keys
8 | const addrPrivKeyVector = require('./fixtures/addrprivkey100.json');
9 | // Test with 10000 private keys - takes about 40 seconds to run
10 | // const addrPrivKeyVector = require('./fixtures/addrprivkey10000.json')
11 |
12 | const createVaultProm = Promise.promisify(KeyStore.createVault);
13 |
14 | describe('Keystore', function () {
15 | describe('createVault constructor', function () {
16 | it('accepts a variety of options', function (done) {
17 | const fixture = fixtures.valid[0];
18 |
19 | KeyStore.createVault({
20 | password: fixture.password,
21 | seedPhrase: fixture.mnSeed,
22 | salt: fixture.salt,
23 | hdPathString: fixture.hdPathString
24 | }, function (err, ks) {
25 | expect(ks.encSeed).to.not.equal(undefined);
26 | const decryptedPaddedSeed = KeyStore._decryptString(ks.encSeed, Uint8Array.from(fixtures.valid[0].pwDerivedKey));
27 | // Check padding
28 | expect(decryptedPaddedSeed.length).to.equal(120);
29 | expect(decryptedPaddedSeed.trim()).to.equal(fixtures.valid[0].mnSeed);
30 | done();
31 | });
32 | });
33 |
34 | it('generates a random salt for key generation', function (done) {
35 | this.timeout(10000);
36 | const fixture = fixtures.valid[0];
37 |
38 | KeyStore.createVault({
39 | password: fixture.password,
40 | seedPhrase: fixture.mnSeed,
41 | hdPathString: fixture.hdPathString
42 | }, function (err, ks) {
43 | const salt0 = ks.salt;
44 | expect(ks.salt).to.not.equal(undefined);
45 | ks.keyFromPassword(fixture.password, function (err, derivedKey) {
46 | const decryptedPaddedSeed = KeyStore._decryptString(ks.encSeed, derivedKey);
47 | // Check padding
48 | expect(decryptedPaddedSeed.length).to.equal(120);
49 | expect(decryptedPaddedSeed.trim()).to.equal(fixtures.valid[0].mnSeed);
50 | KeyStore.createVault({
51 | password: fixture.password,
52 | seedPhrase: fixture.mnSeed,
53 | hdPathString: fixture.hdPathString
54 | }, function (err, ks1) {
55 | const salt1 = ks1.salt;
56 | expect(salt0).to.not.equal(salt1);
57 | done();
58 | });
59 | });
60 | });
61 | });
62 | });
63 |
64 |
65 | // Can't directly test the encrypt/decrypt functions
66 | // since salt and iv is used.
67 | describe('_encryptString _decryptString', function () {
68 | fixtures.valid.forEach(function (f) {
69 | it('encrypts the seed then returns same seed decrypted ' + '"' + f.mnSeed.substring(0, 25) + '..."', function (done) {
70 | const encryptedString = KeyStore._encryptString(f.mnSeed, Uint8Array.from(f.pwDerivedKey));
71 | const decryptedString = KeyStore._decryptString(encryptedString, Uint8Array.from(f.pwDerivedKey));
72 |
73 | expect(decryptedString).to.equal(f.mnSeed);
74 | done();
75 | });
76 | });
77 | });
78 |
79 | describe('_encryptKey _decryptKey', function () {
80 | fixtures.valid.forEach(function (f) {
81 | it('encrypts the key then returns same key decrypted ' + '"' + f.privKeyHex.substring(0, 15) + '..."', function (done) {
82 | const encryptedKey = KeyStore._encryptKey(f.privKeyHex, Uint8Array.from(f.pwDerivedKey));
83 | const decryptedKey = KeyStore._decryptKey(encryptedKey, Uint8Array.from(f.pwDerivedKey));
84 |
85 | expect(decryptedKey).to.equal(f.privKeyHex);
86 | done();
87 | });
88 | });
89 | });
90 |
91 | describe('deriveKeyFromPassword', function () {
92 | it('derives a key correctly from the password', function (done) {
93 | const derKeyProm = Promise.promisify(KeyStore.deriveKeyFromPasswordAndSalt);
94 | const promArray = [];
95 |
96 | fixtures.valid.forEach(function (f) {
97 | promArray.push(derKeyProm(f.password, f.salt));
98 | });
99 |
100 | Promise.all(promArray).then(function (derived) {
101 | for (let i = 0; i < derived.length; i++) {
102 | expect(derived[i]).to.deep.equal(Uint8Array.from(fixtures.valid[i].pwDerivedKey));
103 | }
104 | done();
105 | });
106 | });
107 |
108 | it('Checks if a derived key is correct or not', function (done) {
109 | const derKey = Uint8Array.from(fixtures.valid[0].pwDerivedKey);
110 | const derKey1 = Uint8Array.from(fixtures.valid[1].pwDerivedKey);
111 | const fixture = fixtures.valid[0];
112 |
113 | KeyStore.createVault({
114 | password: fixture.password,
115 | seedPhrase: fixture.mnSeed,
116 | salt: fixture.salt,
117 | hdPathString: fixture.hdPathString
118 | }, function (err, ks) {
119 | const isDerKeyCorrect = ks.isDerivedKeyCorrect(derKey);
120 | expect(isDerKeyCorrect).to.equal(true);
121 | const isDerKey1Correct = ks.isDerivedKeyCorrect(derKey1);
122 | expect(isDerKey1Correct).to.equal(false);
123 | done();
124 | });
125 | });
126 |
127 | });
128 |
129 | describe('_computeAddressFromPrivKey', function () {
130 | fixtures.valid.forEach(function (f) {
131 | it('generates valid address from private key ' + '"' + f.privKeyHex.substring(0, 15) + '..."', function (done) {
132 | const address = '0x' + KeyStore._computeAddressFromPrivKey(f.privKeyHex);
133 | expect(address).to.equal(f.address);
134 | done();
135 | });
136 | });
137 |
138 | addrPrivKeyVector.forEach(function (f) {
139 | it('generates valid address from private key ' + '"' + f.key.substring(0, 15) + '..."', function (done) {
140 | const address = KeyStore._computeAddressFromPrivKey(f.key);
141 | expect(address).to.equal(f.addr);
142 | done();
143 | });
144 | });
145 | });
146 |
147 | describe('serialize deserialize', function () {
148 | it('serializes empty keystore and returns same non-empty keystore when deserialized ', function (done) {
149 | const fixture = fixtures.valid[0];
150 |
151 | KeyStore.createVault({
152 | password: fixture.password,
153 | seedPhrase: fixture.mnSeed,
154 | salt: fixture.salt,
155 | hdPathString: fixture.hdPathString
156 | }, function (err, origKS) {
157 | const serKS = origKS.serialize();
158 | const deserKS = KeyStore.deserialize(serKS);
159 |
160 | // Retains all attributes properly
161 | expect(deserKS).to.deep.equal(origKS);
162 | done();
163 | });
164 | });
165 |
166 | it('serializes non-empty keystore and returns same non-empty keystore when deserialized ', function (done) {
167 | const fixture = fixtures.valid[0];
168 |
169 | KeyStore.createVault({
170 | password: fixture.password,
171 | seedPhrase: fixture.mnSeed,
172 | salt: fixture.salt,
173 | hdPathString: fixture.hdPathString
174 | }, function (err, origKS) {
175 | //Add Keys
176 | origKS.generateNewAddress(Uint8Array.from(fixtures.valid[0].pwDerivedKey), 20);
177 |
178 | const serKS = origKS.serialize();
179 | const deserKS = KeyStore.deserialize(serKS);
180 |
181 | // Retains all attributes properly
182 | expect(deserKS).to.deep.equal(origKS);
183 | done();
184 | });
185 | });
186 | });
187 |
188 |
189 | describe('generateNewAddress', function () {
190 | const N = fixtures.valid.length;
191 |
192 | it('returns a new address, next in hd wallet', function (done) {
193 | this.timeout(10000);
194 |
195 | const promArray = [];
196 | fixtures.valid.forEach(function (fixture) {
197 | promArray.push(createVaultProm({
198 | password: fixture.password,
199 | seedPhrase: fixture.mnSeed,
200 | salt: fixture.salt,
201 | hdPathString: fixture.hdPathString
202 | }));
203 | });
204 |
205 | Promise.all(promArray).then(function (keystores) {
206 | for (let i = 0; i < N; i++) {
207 | const ks = keystores[i];
208 | const numAddresses = fixtures.valid[i].hdIndex + 1;
209 | ks.generateNewAddress(Uint8Array.from(fixtures.valid[i].pwDerivedKey), numAddresses);
210 | const addresses = ks.getAddresses();
211 | const addr = addresses[addresses.length - 1];
212 | const priv = ks.exportPrivateKey(addr, Uint8Array.from(fixtures.valid[i].pwDerivedKey));
213 | expect(addr).to.equal(fixtures.valid[i].address);
214 | expect(priv).to.equal(fixtures.valid[i].privKeyHex);
215 | }
216 | done();
217 | }).catch(done);
218 | });
219 | });
220 |
221 |
222 | describe('getAddresses', function () {
223 | it('returns the object\'s address attribute', function (done) {
224 | const fixture = fixtures.valid[0];
225 | KeyStore.createVault({
226 | password: fixture.password,
227 | seedPhrase: fixture.mnSeed,
228 | salt: fixture.salt,
229 | hdPathString: fixture.hdPathString
230 | }, function (err, ks) {
231 | const add0x = function (addr) {
232 | return ('0x' + addr);
233 | };
234 |
235 | const pwKey = Uint8Array.from(fixture.pwDerivedKey);
236 | expect(ks.addresses.length).to.equal(0);
237 | expect(ks.getAddresses()).to.deep.equal(ks.addresses);
238 | ks.generateNewAddress(pwKey);
239 | expect(ks.addresses.length).to.equal(1);
240 | expect(ks.getAddresses()).to.deep.equal(ks.addresses.map(add0x));
241 | ks.generateNewAddress(pwKey, 5);
242 | expect(ks.addresses.length).to.equal(6);
243 | expect(ks.getAddresses()).to.deep.equal(ks.addresses.map(add0x));
244 | done();
245 | });
246 | });
247 | });
248 |
249 | describe('Seed functions', function () {
250 | it('returns the unencrypted seed', function (done) {
251 | const fixture = fixtures.valid[0];
252 |
253 | KeyStore.createVault({
254 | password: fixture.password,
255 | seedPhrase: fixture.mnSeed,
256 | salt: fixture.salt,
257 | hdPathString: fixture.hdPathString
258 | }, function (err, ks) {
259 | const pwKey = Uint8Array.from(fixtures.valid[0].pwDerivedKey);
260 | expect(ks.getSeed(pwKey)).to.equal(fixture.mnSeed);
261 | done();
262 | });
263 | });
264 |
265 | it('checks if seed is valid', function (done) {
266 | let isValid = KeyStore.isSeedValid(fixtures.valid[0].mnSeed);
267 | expect(isValid).to.equal(true);
268 |
269 | isValid = KeyStore.isSeedValid(fixtures.invalid[0].mnSeed);
270 | expect(isValid).to.equal(false);
271 | done();
272 | });
273 |
274 | it('concatenates and hashes entropy sources', function (done) {
275 | const N = fixtures.sha256Test.length;
276 |
277 | for (let i = 0; i < N; i++) {
278 | const ent0 = new Buffer(fixtures.sha256Test[i].ent0);
279 | const ent1 = new Buffer(fixtures.sha256Test[i].ent1);
280 | const outputString = KeyStore._concatAndSha256(ent0, ent1).toString('hex');
281 | expect(outputString).to.equal(fixtures.sha256Test[i].targetHash);
282 | }
283 |
284 | done();
285 | });
286 | });
287 |
288 | describe('exportPrivateKey', function () {
289 | it('exports the private key corresponding to an address', function (done) {
290 | const fixture = fixtures.valid[0];
291 |
292 | KeyStore.createVault({
293 | password: fixture.password,
294 | seedPhrase: fixture.mnSeed,
295 | salt: fixture.salt,
296 | hdPathString: fixture.hdPathString
297 | }, function (err, ks) {
298 | const pw = Uint8Array.from(fixtures.valid[0].pwDerivedKey);
299 | ks.generateNewAddress(pw, 2);
300 | const addr = ks.getAddresses();
301 |
302 | const exportedPriv0 = ks.exportPrivateKey(addr[0], pw);
303 | const exportedPriv1 = ks.exportPrivateKey(addr[1], pw);
304 |
305 | const addrFromExported0 = '0x' + KeyStore._computeAddressFromPrivKey(exportedPriv0);
306 | const addrFromExported1 = '0x' + KeyStore._computeAddressFromPrivKey(exportedPriv1);
307 |
308 | expect(addrFromExported0).to.equal(addr[0]);
309 | expect(addrFromExported1).to.equal(addr[1]);
310 | done();
311 | });
312 | });
313 | });
314 |
315 | describe('hooked web3-provider', function () {
316 | it('implements hasAddress() correctly', function (done) {
317 | const fixture = fixtures.valid[0];
318 |
319 | KeyStore.createVault({
320 | password: fixture.password,
321 | seedPhrase: fixture.mnSeed,
322 | salt: fixture.salt,
323 | hdPathString: fixture.hdPathString
324 | }, function (err, ks) {
325 | const pw = Uint8Array.from(fixtures.valid[0].pwDerivedKey);
326 | ks.generateNewAddress(pw, 5);
327 | const addr = ks.getAddresses();
328 |
329 | for (let i = 0; i < addr.length; i++) {
330 | ks.hasAddress(addr[i], function (err, hasAddr) {
331 | expect(hasAddr).to.equal(true);
332 | });
333 | ks.hasAddress(addr[i], function (err, hasAddr) {
334 | expect(hasAddr).to.equal(true);
335 | });
336 | }
337 |
338 | ks.hasAddress('abcdef0123456', function (err, hasAddr) {
339 | expect(hasAddr).to.equal(false);
340 | });
341 |
342 | ks.hasAddress('0xabcdef0123456', function (err, hasAddr) {
343 | expect(hasAddr).to.equal(false);
344 | });
345 |
346 | done();
347 | });
348 | });
349 |
350 | it('implements signTransaction correctly', function (done) {
351 | const fixture = fixtures.valid[1];
352 |
353 | KeyStore.createVault({
354 | password: fixture.password,
355 | seedPhrase: fixture.mnSeed,
356 | salt: fixture.salt,
357 | hdPathString: fixture.hdPathString
358 | }, function (err, ks) {
359 | ks.keyFromPassword(fixture.password, function (err, pwDerivedKey) {
360 |
361 | ks.generateNewAddress(pwDerivedKey, 1);
362 | const addr = ks.getAddresses()[0];
363 |
364 | // Trivial passwordProvider
365 | ks.passwordProvider = function (callback) {
366 | callback(null, fixture.password);
367 | };
368 |
369 | const txParams = fixture.web3TxParams;
370 | ks.signTransaction(txParams, function (err, signedTx) {
371 | expect(signedTx.slice(2)).to.equal(fixture.rawSignedTx);
372 | done();
373 | });
374 | });
375 | });
376 | });
377 | });
378 |
379 | describe('upgrade old serialized keystore', function () {
380 | it('upgrades a keystore older than version 2', function (done) {
381 | this.timeout(10000);
382 | const oldKS = require('./fixtures/lightwallet.json');
383 | const oldSerialized = JSON.stringify(oldKS);
384 |
385 | Upgrade.upgradeOldSerialized(oldSerialized, 'test', function (err, upgradedKeystore) {
386 | const newKS = KeyStore.deserialize(upgradedKeystore);
387 | expect(newKS.addresses).to.deep.equal(oldKS.addresses);
388 | done();
389 | });
390 | });
391 |
392 | it('upgrades a version 2 keystore', function (done) {
393 | this.timeout(10000);
394 | const oldKS = require('./fixtures/lightwalletv2.json');
395 | const oldSerialized = JSON.stringify(oldKS);
396 |
397 | Upgrade.upgradeOldSerialized(oldSerialized, 'PHveKjhQ&8dwWEdhu]q6', function (err, upgradedKeystore) {
398 | const newKS = KeyStore.deserialize(upgradedKeystore);
399 | expect(newKS.addresses).to.deep.equal(oldKS.ksData[newKS.hdPathString].addresses);
400 | done();
401 | });
402 | });
403 | });
404 | });
405 |
--------------------------------------------------------------------------------
/test/fixtures/addrprivkey100.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "seed": "some_random_initial_seed_0",
4 | "addr": "80a88ab96bc14bf71261a01481b0039f69e9feb1",
5 | "key": "56adc28ae72e4ff7e705e2d7c17d65e4840c2089f6bfeecc878e5d4d3f30f496"
6 | },
7 | {
8 | "seed": "some_random_initial_seed_1",
9 | "addr": "599a4338d92cedfb663bbfb07763f7aad5c5edf3",
10 | "key": "5f25046c6691ae4df8f14f986921884254b6d633089b004b5f1c0e37cd69fd64"
11 | },
12 | {
13 | "seed": "some_random_initial_seed_2",
14 | "addr": "3fd4427a695c1e7d5001173966efb5b7c7821606",
15 | "key": "aa9d5c6e4c51b67aca066a3b46a4862c12a7b393391b068fdafc9cd3853c986c"
16 | },
17 | {
18 | "seed": "some_random_initial_seed_3",
19 | "addr": "5bc29d019d1e4643a401b4abec345b75c161f892",
20 | "key": "4bc54f3171bebe9d85785a275c41dd532d37063634b39bb987099669d6347bef"
21 | },
22 | {
23 | "seed": "some_random_initial_seed_4",
24 | "addr": "cfa7874f5c8d59afc6b806831ed4ea61aac4a9b6",
25 | "key": "fc6192d0c469689e991a4c1291d3980b7cb3bce970d5b32fe2f27fb8732e9913"
26 | },
27 | {
28 | "seed": "some_random_initial_seed_5",
29 | "addr": "ec04b160fd14ba35ad7384de995e9897bdfbe5bc",
30 | "key": "2037ad15dc3941521f72476fe03e309ebddc1f0d4b75fbb7e62fedd5c1a50170"
31 | },
32 | {
33 | "seed": "some_random_initial_seed_6",
34 | "addr": "b49e0d04ba7eaf3c122bc4db23a415a907a976ed",
35 | "key": "aaa12851b7814367b67c48a6aee3ebff1c2d255977c562e969b36496c547c9e9"
36 | },
37 | {
38 | "seed": "some_random_initial_seed_7",
39 | "addr": "e9ad5b7bbf972980bbaeac37da1bc9f493a7af50",
40 | "key": "43b56a0864711a3f3513143d6d5f9301e93d55c4cf787e85a4157079c1026a68"
41 | },
42 | {
43 | "seed": "some_random_initial_seed_8",
44 | "addr": "0688dfb6dae2027177b995c8600361a0dc4a2be7",
45 | "key": "df4e1e89ae7fcaabcea162b3b8f9de98a05d8688ac83ce1a7e9f09c6e311c7d9"
46 | },
47 | {
48 | "seed": "some_random_initial_seed_9",
49 | "addr": "c28993f7d553a042910a3ffcb11e97e877a2ea41",
50 | "key": "96fd5b5a74cbc6b693c17990cd0dc729835f2e58c40a46327195bbd447cbecfe"
51 | },
52 | {
53 | "seed": "some_random_initial_seed_10",
54 | "addr": "5b05dae4998ebcb3f1ddcfae54c6fe31e74f436b",
55 | "key": "24e8ceb99aa66e727da271dd8e7a9fc2f6a063d678b1458c22bd82c00d6b4243"
56 | },
57 | {
58 | "seed": "some_random_initial_seed_11",
59 | "addr": "8d2f06b93cf1ee20f48b949255e21595c08e75bd",
60 | "key": "74af600f57190617bf04cb2f62e71d3fc026ac7c89bd746fc2d4d34475884ca0"
61 | },
62 | {
63 | "seed": "some_random_initial_seed_12",
64 | "addr": "93871bc44bf274a21481c045b79629ce7ab581bd",
65 | "key": "24bf800f14d3c94b7803ced1b86ac91eb7988b6bd4a89c2880b213ec8fef205a"
66 | },
67 | {
68 | "seed": "some_random_initial_seed_13",
69 | "addr": "95dcc88624ac79b5f1999489ca65a297556abd04",
70 | "key": "9c57849ee21893eb2fb6fff94989abadbb452db9ba6d773774a34153bef7a399"
71 | },
72 | {
73 | "seed": "some_random_initial_seed_14",
74 | "addr": "24af280e0cd0c29af4185ecbe33a14523d426f71",
75 | "key": "87e544481da8d6db0d9490cda4f0d3427d03ac2cc9b6daaaf6b8f75848ddaf72"
76 | },
77 | {
78 | "seed": "some_random_initial_seed_15",
79 | "addr": "b5e97291ffa505a19bb725d208c39f8a2c46025f",
80 | "key": "9eaad11513a8a780d5cc8729eddc1f78742ce508d1b5618e8ce9e11a3ef955d0"
81 | },
82 | {
83 | "seed": "some_random_initial_seed_16",
84 | "addr": "65bd17db18158486b5f0037799dd0417f743e827",
85 | "key": "80e66bc2e6651944c71d12d2502bdd8ad145c6210e20437394faeb2e5f7194da"
86 | },
87 | {
88 | "seed": "some_random_initial_seed_17",
89 | "addr": "81207292bdbbfba422d89c6daaa888ae8f511ac7",
90 | "key": "e84f739e3a5c7f522beb43269ee3f7cae14a666b613d2681a84e754f54fb57a0"
91 | },
92 | {
93 | "seed": "some_random_initial_seed_18",
94 | "addr": "920a5f00ea2aeac10a08a80911bd03b2269ebf6d",
95 | "key": "5e804399c264c7a5f2ea9c40ee4be2989912d44cedf2395f77750abbc18d8d79"
96 | },
97 | {
98 | "seed": "some_random_initial_seed_19",
99 | "addr": "9ea3a6c0706ab705e5eb7a900587bcf29a706a03",
100 | "key": "28b311bd5af1d82e2989741541f34e7baaa4054db2d20f7673de711c4599f513"
101 | },
102 | {
103 | "seed": "some_random_initial_seed_20",
104 | "addr": "fece124100254fc202409ff56630ee57c9f7fbcd",
105 | "key": "c4344518d534a680732c1008181ede7879797c5c6b2c6da38b646f1539d5488a"
106 | },
107 | {
108 | "seed": "some_random_initial_seed_21",
109 | "addr": "f1dd81900157b5159682aacc8bd1826e076a9fcf",
110 | "key": "d60c94165a89fb32a638d3e777692932deefe86a837bdf85b6e41c3b0fd86503"
111 | },
112 | {
113 | "seed": "some_random_initial_seed_22",
114 | "addr": "cd0c3b0f51db278611e666b9453a0c71aac72bcb",
115 | "key": "00ce3fda508444c7afeef35097226a5af243a9c41ac92f299aa46657cea4b1fa"
116 | },
117 | {
118 | "seed": "some_random_initial_seed_23",
119 | "addr": "33b9ddf4e3aba7ad415ebcc129673d521b4fafb8",
120 | "key": "743bba31af4de73dd201a3b9ed24fb7d1fb0d71c160338d0dcf4476fb656e6fa"
121 | },
122 | {
123 | "seed": "some_random_initial_seed_24",
124 | "addr": "5f1b31b2bef66477f8cbfcac21e3be34db0a1006",
125 | "key": "683aa272c7de40770517a48d8bce6cf141dbe84185829054847744ef7e2800a7"
126 | },
127 | {
128 | "seed": "some_random_initial_seed_25",
129 | "addr": "4971b21f8cf1568d426785a0a776c30825f7c18e",
130 | "key": "d0f0ef8bf98469ae86d9167eb9b42df7a08187d0b8ee5648d11e91d9492ddfe2"
131 | },
132 | {
133 | "seed": "some_random_initial_seed_26",
134 | "addr": "0dfcf091f886c22135cd9c85addc261104502365",
135 | "key": "9b438abcf1fcb31ea5f3e79f77657cf4db19535bf54421542f9ff504f66db4b2"
136 | },
137 | {
138 | "seed": "some_random_initial_seed_27",
139 | "addr": "630ce468ae85d1c451ef22e5a495968aa6d72eef",
140 | "key": "5aa4b64b2bc9f41602840d6e678c3aee1e94c070c1742057f84d711587ca6027"
141 | },
142 | {
143 | "seed": "some_random_initial_seed_28",
144 | "addr": "06a38c3bbff434e4cdca02c1eb297c34a6e3b341",
145 | "key": "2c7db3a652fe55080d4e86ffcce9705844cae4630873b5fd67da87e64620053e"
146 | },
147 | {
148 | "seed": "some_random_initial_seed_29",
149 | "addr": "2d71f5aee73dc8109bbdfbd4ff25cfd6e863fd4a",
150 | "key": "1fc04d1bca8b256e501dfab94b67e2c59eb76f1f6b3ac033b069f165dda4c3fa"
151 | },
152 | {
153 | "seed": "some_random_initial_seed_30",
154 | "addr": "fa4b674f4385d5f0e2635d5a682df6c26c1fefdb",
155 | "key": "7ac4044e9aba623cdd31ff1433ab8f981662b691287dbf91d70b8bfabd548224"
156 | },
157 | {
158 | "seed": "some_random_initial_seed_31",
159 | "addr": "84a7b8c82d363eb381408fd731abf4b454d392a8",
160 | "key": "cdfcdf0864146b4f6e9b3b72eaeafa9166459dcd9407180089846fa8ec14156b"
161 | },
162 | {
163 | "seed": "some_random_initial_seed_32",
164 | "addr": "19663ed0a0776062072714c3a026297b78f78b24",
165 | "key": "f934fa4f6525c7daba5e0c66e9164972ed792d1f483072529a59dc379b9a3bac"
166 | },
167 | {
168 | "seed": "some_random_initial_seed_33",
169 | "addr": "2be1344001ffdb6dcb36e0e57114ec36b22b7de3",
170 | "key": "fe05c672b99231555b440b24b7da5d3acee8c43215c856206ce253fc367f62c2"
171 | },
172 | {
173 | "seed": "some_random_initial_seed_34",
174 | "addr": "61035748247495655c8d264d5c7e041a1a9fe223",
175 | "key": "3e139af943674cc58118266ce3484ea14a402a2980684a052ee148e26e07fba6"
176 | },
177 | {
178 | "seed": "some_random_initial_seed_35",
179 | "addr": "1f39e3893648743bf7de99a208a061412a375183",
180 | "key": "e8b97e953e777ddc9d01e9191508a0a73de50ca98dc8e2067ddb0a9ee069eec9"
181 | },
182 | {
183 | "seed": "some_random_initial_seed_36",
184 | "addr": "22a140da8bf5ac4f49f7cf0fc0d80c30ebbee426",
185 | "key": "096e8e8ccdddb250df59f7900a1e6c9eac9af30534b18b5e23ad7b9ed1979d54"
186 | },
187 | {
188 | "seed": "some_random_initial_seed_37",
189 | "addr": "ee24070841e42803b0b0c8d74b24f8600c3b9d28",
190 | "key": "2bc903f4520ee81cc61a5e5661bc719976087e116c41a98fa3aa957552f970ad"
191 | },
192 | {
193 | "seed": "some_random_initial_seed_38",
194 | "addr": "ea0d9a25bbf7caf4366b2de3a97493c494108ba3",
195 | "key": "81e7a17c4e671b461c37aa34fcbedde5b2fd422a9d936129fa00ad872879736f"
196 | },
197 | {
198 | "seed": "some_random_initial_seed_39",
199 | "addr": "65a6768978231c75cbc3b785fc23fd82fe7ff7e0",
200 | "key": "2f38fca5b693ec6daa526aced01b54a87fbcad8963e97297fc3bb4dfbe1bec50"
201 | },
202 | {
203 | "seed": "some_random_initial_seed_40",
204 | "addr": "841b66e64fe91a807f8c8685358c1f3f941413cc",
205 | "key": "5c33e4bac8fea9a5ce2ad4b5e9629b1bbadab1b8d5e8cd1ecc65592c4d9b6a5a"
206 | },
207 | {
208 | "seed": "some_random_initial_seed_41",
209 | "addr": "7f3f1e90387184e0a74d215ef5d33faaf84678ec",
210 | "key": "e9cb9fddc2d9170a7e6870e33ff74575a1441302d1b5ecb7355aeda526e88f86"
211 | },
212 | {
213 | "seed": "some_random_initial_seed_42",
214 | "addr": "7eec903acb8001403b342d8a405b34e55825032e",
215 | "key": "5e204505c93024748f968c5f4aa9320a54535df9397b3d8fddafe3f9e3f73d58"
216 | },
217 | {
218 | "seed": "some_random_initial_seed_43",
219 | "addr": "3eb40f89ced2e07fbd10cc943d4143be6cce8955",
220 | "key": "f0fea5a9cb8e35b071c50b95d12988246bb09e245235cfd42d017dc9e898e9bf"
221 | },
222 | {
223 | "seed": "some_random_initial_seed_44",
224 | "addr": "dd34bfe452f22e12566fc106812ccb4e2130c160",
225 | "key": "e9f9c99a25ae856b85ec0c7da7df66921b6f752709734f0bda4627a368b5068c"
226 | },
227 | {
228 | "seed": "some_random_initial_seed_45",
229 | "addr": "75d5956f39d46cdfbd96739e2e6a0f79f1249e47",
230 | "key": "2ad4231490bfd995d9e1e9da3c189a93a6b5f639af293ae69d3b96ae5fbb3204"
231 | },
232 | {
233 | "seed": "some_random_initial_seed_46",
234 | "addr": "1469e1b8c6559434f4aa8fc0c30baa6c6892a410",
235 | "key": "a7fbe5ce273c5e057c81c0687ae8098f21fce880a74dcd0a06190d041fa0ea5d"
236 | },
237 | {
238 | "seed": "some_random_initial_seed_47",
239 | "addr": "8baecd67be41ee1272d70b1d0b843b24db1d1c90",
240 | "key": "0b73032ca57706aa41b812654fdf5fbedf5b225b05412d0e617856b301134620"
241 | },
242 | {
243 | "seed": "some_random_initial_seed_48",
244 | "addr": "dc7def275b77f0e0193e5cdf0b284dcb01f4b5cb",
245 | "key": "2d252edef6e319c1ba86e7bfe17e6a36f76786b1af067d7bbc3c5216c970f588"
246 | },
247 | {
248 | "seed": "some_random_initial_seed_49",
249 | "addr": "4923811f36f121574a0082f070f4706e5af4ec65",
250 | "key": "3653940e388d52ee12dd7392d765453bf1e973a9c96d6724df11866c6293f9e6"
251 | },
252 | {
253 | "seed": "some_random_initial_seed_50",
254 | "addr": "fc3b30491aeee00f1c1f73ebd38c8bce138fd274",
255 | "key": "73d31969e97229b737994a512b8a898820fa01ada7d94a9b72025e70ddfb03a2"
256 | },
257 | {
258 | "seed": "some_random_initial_seed_51",
259 | "addr": "0e49b221a84356d32c949e258b0166e743ce4206",
260 | "key": "2a315bad3c43aed4182fe7b9dfbf004fb285f48a967202c19bf8cb2b00d99a21"
261 | },
262 | {
263 | "seed": "some_random_initial_seed_52",
264 | "addr": "533a32609a99ebfadc9d064fc12398a462a67046",
265 | "key": "2c85caa5034e41def3a95cf68d975b94867d9e505b095703153a2438b6ac7d58"
266 | },
267 | {
268 | "seed": "some_random_initial_seed_53",
269 | "addr": "6c37fc4ab83743478b840c337cb7e8d9aac96a55",
270 | "key": "423f8a10e3f80f4174eb69f9afd34ad15ac57d9be536b9105caddfae3e258a61"
271 | },
272 | {
273 | "seed": "some_random_initial_seed_54",
274 | "addr": "02dcc7dea59f2dab4d39a76df537a859c4e0c6d6",
275 | "key": "916d3e2b0e8ae2f636f8eb8fbe8c1788d53b7a4b1fd8347d7dd487d605af341a"
276 | },
277 | {
278 | "seed": "some_random_initial_seed_55",
279 | "addr": "12716de65e9bfab050794fbab53070aa3dcdf303",
280 | "key": "35c6f4635392cb69a12d3517c6b36894f5ff222834b887bc7884432ea9f65e0a"
281 | },
282 | {
283 | "seed": "some_random_initial_seed_56",
284 | "addr": "921fc445b204138ebce64dae55bd9867a6090785",
285 | "key": "2a3fb81910590c8979fabd61bffdfe1923ac08343d0e1685476b4d384a25aeb1"
286 | },
287 | {
288 | "seed": "some_random_initial_seed_57",
289 | "addr": "0bb1323b9b31df02e07d8e0ddf198da1bf00d74b",
290 | "key": "c1bf58c15d6a6953350e9ba0f8b6977f27f0d81f5af5cebd522963b7b8c0f7fb"
291 | },
292 | {
293 | "seed": "some_random_initial_seed_58",
294 | "addr": "0ff12c8e0821b3cdf5a7e3dcb0c7302e5ede4a21",
295 | "key": "1878ee09baad8b1b2ade402923db6dac085e82d4fc95d91fb1a708696f7df6a4"
296 | },
297 | {
298 | "seed": "some_random_initial_seed_59",
299 | "addr": "7b565843b2c0414650580004eb18ea2b95f4f9a4",
300 | "key": "6b609798fce83df03224092a1643d99ad1819f1898a55ebe8570a94948e941d8"
301 | },
302 | {
303 | "seed": "some_random_initial_seed_60",
304 | "addr": "131d9c86e8f2dad21cf00a8d34ca2cb2a31fc74c",
305 | "key": "942631f6a7abbc56a22878286cc719bb6147fbb72ca70b5be1c6a09f319b905e"
306 | },
307 | {
308 | "seed": "some_random_initial_seed_61",
309 | "addr": "6a436d7edcdeddfe627caadbed0affde5e3d11d7",
310 | "key": "f81e7d072c69b7b44676b9acf1375179165f255ef3d9d6443724469a1c05d415"
311 | },
312 | {
313 | "seed": "some_random_initial_seed_62",
314 | "addr": "387d4166fdede8b63c20f4f5aa5b729077451356",
315 | "key": "a9ac4460569b991cf9d9049c68bb58395497be59811232b702b05d39d0d4b078"
316 | },
317 | {
318 | "seed": "some_random_initial_seed_63",
319 | "addr": "9f413a429d7ab40388ffa829e62b002867799406",
320 | "key": "60f8ef356561dd3d9a91c436b5b33d3dd29ae404f91e1855da25bc5ea10235f1"
321 | },
322 | {
323 | "seed": "some_random_initial_seed_64",
324 | "addr": "a89404828c1ec4a01682f7e545137c725250bf41",
325 | "key": "1968cf9d5070ba836ad9775bc441acef5abb88308b870b9705ccdf94a259a283"
326 | },
327 | {
328 | "seed": "some_random_initial_seed_65",
329 | "addr": "3ff29cf526d7b37cf638a77ec390d599914d150b",
330 | "key": "28b28423bd72c5569d1c45db622303a8420de13272cfe4f61c88e432550662fb"
331 | },
332 | {
333 | "seed": "some_random_initial_seed_66",
334 | "addr": "186ecc4ffd058acf075a6f7b6da70bd6da8c7d02",
335 | "key": "6e28d70e858f1d90d58dd40ffa69fc5e447b8c13b093970c778c44b32c122399"
336 | },
337 | {
338 | "seed": "some_random_initial_seed_67",
339 | "addr": "649402a60d9723144930b2c35bb4b669fc6d0d49",
340 | "key": "a3a6ddccd3e9a317ff2957d3ad41704cc9f986b4611b9a9391032fef2dbc6cce"
341 | },
342 | {
343 | "seed": "some_random_initial_seed_68",
344 | "addr": "683c3e909d0f09a68b3ad1e42c792113dd030e37",
345 | "key": "70980b509c934a2364c6c92fe03aa9aedbd2ea2919a0879dfc709a37a1ec2760"
346 | },
347 | {
348 | "seed": "some_random_initial_seed_69",
349 | "addr": "a33183beb9096a7b42cdbf3459df1e4262e60bd8",
350 | "key": "1328d0b737d43096bf2ca3ad86f8944071434360f983bd1d6a5fa545d943d5da"
351 | },
352 | {
353 | "seed": "some_random_initial_seed_70",
354 | "addr": "2b224255bed20e07ba7db50435694c290bd8d8c4",
355 | "key": "154d60b39d53c2062b324b81acdd5e1878676ab01697caab1c866df183361aa3"
356 | },
357 | {
358 | "seed": "some_random_initial_seed_71",
359 | "addr": "d2e80a39e698cd9a558a6f953d9cacb570fbec81",
360 | "key": "6ed291f8f1d1f97ffe798b3199c619275661e2f6386f60b484719b4f4795c2bd"
361 | },
362 | {
363 | "seed": "some_random_initial_seed_72",
364 | "addr": "d5333d851705baee139cf38711f8997c0dfcf4b7",
365 | "key": "3ae00c8c648841561e60084e0d6438003fa78ba8152b113a5df942946e6b9204"
366 | },
367 | {
368 | "seed": "some_random_initial_seed_73",
369 | "addr": "176b904bdaae1665a7911ecb5895a889dcc6a7e2",
370 | "key": "ec1401f4d41d369c0c27236ce6c655802ece39a94270468cb89d57ea05b9446c"
371 | },
372 | {
373 | "seed": "some_random_initial_seed_74",
374 | "addr": "a21972b8ec5f96380994c325782d7816079ee0c4",
375 | "key": "bbadfea858b5d29a2137dd96b82f955d772a820c55b7f22e6e2ffe59782aa64b"
376 | },
377 | {
378 | "seed": "some_random_initial_seed_75",
379 | "addr": "7e4bed507283da8e09b062a61a4c10549961dc61",
380 | "key": "e0d89955540dedbd4c2fe01476bf3d11dac2cff19453310d4e3d55d387c91f70"
381 | },
382 | {
383 | "seed": "some_random_initial_seed_76",
384 | "addr": "0ed8cf017871748fe92b29548114c0b31c69189d",
385 | "key": "107db7710c5dc5c013ec4dbe8c3e606e4450d6f92e95e3689df10e18b3260782"
386 | },
387 | {
388 | "seed": "some_random_initial_seed_77",
389 | "addr": "842c4d882adbd08184def5026fe49ec97fef89f6",
390 | "key": "5f932cfc56b31d58788bc77f0a42a454606142e97dd2717b7f022d9c55fd1aa0"
391 | },
392 | {
393 | "seed": "some_random_initial_seed_78",
394 | "addr": "a7681624754a5edb04fed038ecbd9db694cdbe6b",
395 | "key": "2fae51987b8f147a494b7101505defe254b09c6bc22916b1fe35b7dbb545d1a8"
396 | },
397 | {
398 | "seed": "some_random_initial_seed_79",
399 | "addr": "bc69262a753345cadc38269c0aa80a13b5cba8f9",
400 | "key": "d761392fa24f09b61d98d515994cc4ab0f1d66c85252404d5297da50bbb11091"
401 | },
402 | {
403 | "seed": "some_random_initial_seed_80",
404 | "addr": "d87a6a47c2de8dcdc65d1f7a2b8d666c21513bb0",
405 | "key": "83be15c00d26a5f3a3c88e2e1f3d13a9ee3646fb7141b928e208a06a013ed5b8"
406 | },
407 | {
408 | "seed": "some_random_initial_seed_81",
409 | "addr": "06d099c4f1d2854f6201fca0f5d29f09a838e404",
410 | "key": "98a28e0556a98277a13ca3df5218d220bac88cf41bb560e2c22d924514164c1b"
411 | },
412 | {
413 | "seed": "some_random_initial_seed_82",
414 | "addr": "a489839992aba1173bfaec602e88548ca931846d",
415 | "key": "c03f0d3073e5c7f38c6dc71ddafd697010125d370998ef27b1d03cecdb248d5b"
416 | },
417 | {
418 | "seed": "some_random_initial_seed_83",
419 | "addr": "2a47929676faa23932428b2454b9cfdf02f744f4",
420 | "key": "75efddedf0bdbf005d2859087b19306fa53970dda6525bcf539b1f64e79a3c27"
421 | },
422 | {
423 | "seed": "some_random_initial_seed_84",
424 | "addr": "f08fca4bc8a8df9fc1d8922ad351443d19727877",
425 | "key": "ee310ba454e54422d89383052303ed6a7945804eaee39a6c84b1529c88b94069"
426 | },
427 | {
428 | "seed": "some_random_initial_seed_85",
429 | "addr": "1c51db294d5b58565432af258a660ba21e7c014f",
430 | "key": "4b7a1f09b3a2625814c8e5de79914b2f80009d5996a68a447fdce43ccbab7daa"
431 | },
432 | {
433 | "seed": "some_random_initial_seed_86",
434 | "addr": "d9e679f7a25ddb60ba33f5ef6377afcd92ffb8c6",
435 | "key": "d9abcf1b6663a22307f394ea8b8517a8e51a92259099b3ac947d39d832076a7f"
436 | },
437 | {
438 | "seed": "some_random_initial_seed_87",
439 | "addr": "53a469dfd3fff8903314b51cfdf64a393395d596",
440 | "key": "cb8e21d88b7d16eefab625db574e939067eb555bb8313c4650d8810e8d18ee1b"
441 | },
442 | {
443 | "seed": "some_random_initial_seed_88",
444 | "addr": "a57b6eea12555b0f70a92c6fc03d0d731f9c5e43",
445 | "key": "71546e7e822802030161206deecd1d99206bb23fba907260fa4cbb37b522b992"
446 | },
447 | {
448 | "seed": "some_random_initial_seed_89",
449 | "addr": "467493435535b660528954ffa9314fdea166ea0b",
450 | "key": "a3c396a3e5f8d4da3666d0653bbffc08cda415b4035f0035cc1005849190470a"
451 | },
452 | {
453 | "seed": "some_random_initial_seed_90",
454 | "addr": "d923acf4fdf06e29cfdca47e62fe03f3854a26cb",
455 | "key": "7884f49f04fa3cb95ea584cb1ad33c2e96a0eb5f725f4514a7311e14fd15876d"
456 | },
457 | {
458 | "seed": "some_random_initial_seed_91",
459 | "addr": "7f8ecb59ac8f8c9f74d1391328c026e79b6ae987",
460 | "key": "2fcbe031721d821044cab7efdd35cad3a1445f3838fa42870cda41c205984c01"
461 | },
462 | {
463 | "seed": "some_random_initial_seed_92",
464 | "addr": "156b2158112ec21a58f166d4715850be12f77ac2",
465 | "key": "a69dbbf7a2c3c325e3913288431536169f0f795674ce85c2e30b006e3ce5e942"
466 | },
467 | {
468 | "seed": "some_random_initial_seed_93",
469 | "addr": "96e4ececadddd4fa3f8df7e625953ca1a4173388",
470 | "key": "c62f62ab873a66cdbc2be665de12ea1a447014d48f07d93e96dfbf7600a1b891"
471 | },
472 | {
473 | "seed": "some_random_initial_seed_94",
474 | "addr": "914f0811c79c9289378d32cf672c364c5620f9b2",
475 | "key": "4c06f7d1408e4cef67d22eef2df104238a62b8ac9d47cd00c5d5e6d0082b7eef"
476 | },
477 | {
478 | "seed": "some_random_initial_seed_95",
479 | "addr": "d1aebadd6c71783d6f9eaab6a1c4c14522aa6d8c",
480 | "key": "fde88a1bf7e3ebdc2af8381ded84779b869b3071ded170a5c89ffdeeba9c1159"
481 | },
482 | {
483 | "seed": "some_random_initial_seed_96",
484 | "addr": "c4a3136c5d8cc1840186005d8fd8a840cc617de0",
485 | "key": "3b037a739ae32ca4750cc6d8877b978f6ad69a022be06d4aa162e7c87ae16a7e"
486 | },
487 | {
488 | "seed": "some_random_initial_seed_97",
489 | "addr": "e0db75ea656de3890f2bdb97e56632eabcb2c46a",
490 | "key": "2d2e78044bb95ba939dffed0e12e8ac3da964da9959e94ae4af2845c7caee836"
491 | },
492 | {
493 | "seed": "some_random_initial_seed_98",
494 | "addr": "a0277ced6546bba415ccaeff079b0332cdb7e355",
495 | "key": "389bb1f637552705ec9ecb73e9f841b324e4e27856e4952285a556980d5f49a0"
496 | },
497 | {
498 | "seed": "some_random_initial_seed_99",
499 | "addr": "05ab84e98d77927851eb646b2fcb0a2932ecff7a",
500 | "key": "4c6e6159344ff940ff6f2d2b0a6aacdf510b5de7a81f646be8bdedfb9d767193"
501 | }
502 | ]
503 |
--------------------------------------------------------------------------------