Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,12 @@ Library used by [Hathor Wallet](https://github.com/HathorNetwork/hathor-wallet).

## Install

`npm install @hathor/wallet-lib`
`npm install @hathor/wallet-lib`

## Setting storage

This lib requires a storage to be set so it can persist data. Take a look at `src/storage.js` for the methods this storage object should implement.
```
const hathorLib = require('@hathor/wallet-lib');
hathorLib.storage.setStorage(storageFactory);
```
76 changes: 39 additions & 37 deletions __tests__/address.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import wallet from '../src/wallet';
import { GAP_LIMIT } from '../src/constants';
import WebSocketHandler from '../src/WebSocketHandler';

const storage = require('../src/storage').default;

beforeEach(() => {
WebSocketHandler.started = false;
wallet.cleanLocalStorage();
Expand All @@ -18,107 +20,106 @@ test('Update address', () => {
let address1 = '1zEETJWa3U6fBm8eUXbG7ddj6k4KjoR7j';
let index1 = 10;
wallet.updateAddress(address1, index1, false);
expect(localStorage.getItem('wallet:address')).toBe(address1);
expect(parseInt(localStorage.getItem('wallet:lastSharedIndex'), 10)).toBe(index1);
expect(storage.getItem('wallet:address')).toBe(address1);
expect(parseInt(storage.getItem('wallet:lastSharedIndex'), 10)).toBe(index1);

let address2 = '171hK8MaRpG2SqQMMQ34EdTharUmP1Qk4r';
let index2 = 20;
wallet.updateAddress(address2, index2, false);
expect(localStorage.getItem('wallet:address')).toBe(address2);
expect(parseInt(localStorage.getItem('wallet:lastSharedIndex'), 10)).toBe(index2);
expect(storage.getItem('wallet:address')).toBe(address2);
expect(parseInt(storage.getItem('wallet:lastSharedIndex'), 10)).toBe(index2);
})

test('Has a new address already generated', () => {
localStorage.setItem('wallet:lastGeneratedIndex', 10);
localStorage.setItem('wallet:lastSharedIndex', 9);
storage.setItem('wallet:lastGeneratedIndex', 10);
storage.setItem('wallet:lastSharedIndex', 9);

expect(wallet.hasNewAddress()).toBe(true);

localStorage.setItem('wallet:lastSharedIndex', 10);
storage.setItem('wallet:lastSharedIndex', 10);
expect(wallet.hasNewAddress()).toBe(false);

localStorage.setItem('wallet:lastSharedIndex', 11);
storage.setItem('wallet:lastSharedIndex', 11);
expect(wallet.hasNewAddress()).toBe(false);
});

test('Get next address already generated', () => {
localStorage.setItem('wallet:lastSharedIndex', 9);
localStorage.setItem('wallet:data', JSON.stringify({keys: {'1zEETJWa3U6fBm8eUXbG7ddj6k4KjoR7j': {index: 9}, '171hK8MaRpG2SqQMMQ34EdTharUmP1Qk4r': {index: 10}}}));
storage.setItem('wallet:lastSharedIndex', 9);
storage.setItem('wallet:data', {keys: {'1zEETJWa3U6fBm8eUXbG7ddj6k4KjoR7j': {index: 9}, '171hK8MaRpG2SqQMMQ34EdTharUmP1Qk4r': {index: 10}}});

wallet.getNextAddress()

expect(localStorage.getItem('wallet:address')).toBe('171hK8MaRpG2SqQMMQ34EdTharUmP1Qk4r');
expect(parseInt(localStorage.getItem('wallet:lastSharedIndex'), 10)).toBe(10);
expect(storage.getItem('wallet:address')).toBe('171hK8MaRpG2SqQMMQ34EdTharUmP1Qk4r');
expect(parseInt(storage.getItem('wallet:lastSharedIndex'), 10)).toBe(10);
});

test('Can generate new address', () => {
localStorage.setItem('wallet:lastUsedIndex', 2);
localStorage.setItem('wallet:lastGeneratedIndex', 30);
storage.setItem('wallet:lastUsedIndex', 2);
storage.setItem('wallet:lastGeneratedIndex', 30);

expect(wallet.canGenerateNewAddress()).toBe(false);

localStorage.setItem('wallet:lastUsedIndex', 10);
storage.setItem('wallet:lastUsedIndex', 10);
expect(wallet.canGenerateNewAddress()).toBe(false);

localStorage.setItem('wallet:lastUsedIndex', 11);
storage.setItem('wallet:lastUsedIndex', 11);
expect(wallet.canGenerateNewAddress()).toBe(true);

localStorage.setItem('wallet:lastUsedIndex', 17);
storage.setItem('wallet:lastUsedIndex', 17);
expect(wallet.canGenerateNewAddress()).toBe(true);
});

test('Generate new address', () => {
test('Generate new address', async () => {
WebSocketHandler.started = true;
let words = 'purse orchard camera cloud piece joke hospital mechanic timber horror shoulder rebuild you decrease garlic derive rebuild random naive elbow depart okay parrot cliff';
let pin = '123456';
wallet.executeGenerateWallet(words, '', pin, 'password', true);

let data = JSON.parse(localStorage.getItem('wallet:data'));
expect(Object.keys(data.keys).length).toBe(GAP_LIMIT);
expect(parseInt(localStorage.getItem('wallet:lastGeneratedIndex'), 10)).toBe(GAP_LIMIT - 1);
expect(parseInt(localStorage.getItem('wallet:lastSharedIndex'), 10)).toBe(0);
await wallet.executeGenerateWallet(words, '', pin, 'password', true);
let data = storage.getItem('wallet:data');
expect(Object.keys(data.keys).length).toBe(GAP_LIMIT + 1);
expect(parseInt(storage.getItem('wallet:lastGeneratedIndex'), 10)).toBe(GAP_LIMIT);
expect(storage.store.getItem('wallet:lastSharedIndex')).toBe(0);

for (let address in data.keys) {
if (data.keys[address].index === 0) {
expect(localStorage.getItem('wallet:address')).toBe(address);
expect(storage.getItem('wallet:address')).toBe(address);
break;
}
}

// Set last shared index as last generated also
localStorage.setItem('wallet:lastSharedIndex', GAP_LIMIT - 1);
storage.setItem('wallet:lastSharedIndex', GAP_LIMIT - 1);

wallet.generateNewAddress();

let newData = JSON.parse(localStorage.getItem('wallet:data'));
let newData = storage.getItem('wallet:data');
expect(Object.keys(newData.keys).length).toBe(GAP_LIMIT + 1);
expect(parseInt(localStorage.getItem('wallet:lastSharedIndex'), 10)).toBe(GAP_LIMIT);
expect(parseInt(storage.getItem('wallet:lastSharedIndex'), 10)).toBe(GAP_LIMIT);
for (let address in newData.keys) {
if (newData.keys[address].index === GAP_LIMIT) {
expect(localStorage.getItem('wallet:address')).toBe(address);
expect(storage.getItem('wallet:address')).toBe(address);
break;
}
}
});

test('Last used index', () => {
test('Last used index', async () => {
WebSocketHandler.started = true;
let words = 'purse orchard camera cloud piece joke hospital mechanic timber horror shoulder rebuild you decrease garlic derive rebuild random naive elbow depart okay parrot cliff';
let pin = '123456';
wallet.executeGenerateWallet(words, '', pin, 'password', true);
await wallet.executeGenerateWallet(words, '', pin, 'password', true);

let data = JSON.parse(localStorage.getItem('wallet:data'));
let data = storage.getItem('wallet:data');
for (let address in data.keys) {
if (data.keys[address].index === 12) {
wallet.setLastUsedIndex(address);
expect(parseInt(localStorage.getItem('wallet:lastUsedIndex'), 10)).toBe(12);
expect(localStorage.getItem('wallet:lastUsedAddress')).toBe(address);
expect(parseInt(storage.getItem('wallet:lastUsedIndex'), 10)).toBe(12);
expect(storage.getItem('wallet:lastUsedAddress')).toBe(address);
break;
}
}
});

test('Subscribe address to websocket', (done) => {
test('Subscribe address to websocket', done => {
let address = '171hK8MaRpG2SqQMMQ34EdTharUmP1Qk4r';
WebSocketHandler.started = true;
WebSocketHandler.on('subscribe_success', (wsData) => {
Expand Down Expand Up @@ -147,9 +148,9 @@ test('Subscribe all addresses to websocket', (done) => {
for (let address of addresses) {
keys[address] = {};
}
localStorage.setItem('wallet:data', JSON.stringify({keys: keys}));
storage.setItem('wallet:data', {keys: keys});
WebSocketHandler.started = true;
WebSocketHandler.on('subscribe_success', (wsData) => {
WebSocketHandler.on('subscribe_success', function handler(wsData) {
let foundIndex = -1;
for (let [idx, address] of addresses.entries()) {
if (address === wsData.address) {
Expand All @@ -164,6 +165,7 @@ test('Subscribe all addresses to websocket', (done) => {

if (addresses.length === 0) {
// If got here test was successful, so ending it
WebSocketHandler.removeListener('subscribe_success', handler);
done();
}
});
Expand Down
4 changes: 3 additions & 1 deletion __tests__/encryption.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

import wallet from '../src/wallet';

const storage = require('../src/storage').default;

test('Private key encryption/decryption', () => {
const pin = '123456';
const password = 'password';
Expand All @@ -20,7 +22,7 @@ test('Private key encryption/decryption', () => {
expect(wallet.isPasswordCorrect(password)).toBeTruthy();
expect(wallet.isPasswordCorrect(pin)).toBeFalsy();

let accessData = JSON.parse(localStorage.getItem('wallet:accessData'));
let accessData = storage.getItem('wallet:accessData');
let decrypted = wallet.decryptData(accessData.mainKey, pin);
let decryptedWords = wallet.decryptData(accessData.words, password);

Expand Down
24 changes: 13 additions & 11 deletions __tests__/new_wallet.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { HDPrivateKey } from 'bitcore-lib';
import CryptoJS from 'crypto-js';
import WebSocketHandler from '../src/WebSocketHandler';

const storage = require('../src/storage').default;

var addressUsed = '';
var addressShared = '';
var txId = '00034a15973117852c45520af9e4296c68adb9d39dc99a0342e23cd6686b295e';
Expand Down Expand Up @@ -53,22 +55,22 @@ mock.onGet('thin_wallet/address_history').reply((config) => {
});

const checkData = () => {
check(localStorage.getItem('wallet:address'), addressShared, doneCb);
check(localStorage.getItem('wallet:lastUsedAddress'), addressUsed, doneCb);
check(parseInt(localStorage.getItem('wallet:lastSharedIndex'), 10), 1, doneCb);
check(parseInt(localStorage.getItem('wallet:lastUsedIndex'), 10), 0, doneCb);
check(parseInt(localStorage.getItem('wallet:lastGeneratedIndex'), 10), 20, doneCb);
let accessData = localStorage.getItem('wallet:accessData');
check(storage.getItem('wallet:address'), addressShared, doneCb);
check(storage.getItem('wallet:lastUsedAddress'), addressUsed, doneCb);
check(parseInt(storage.getItem('wallet:lastSharedIndex'), 10), 1, doneCb);
check(parseInt(storage.getItem('wallet:lastUsedIndex'), 10), 0, doneCb);
check(parseInt(storage.getItem('wallet:lastGeneratedIndex'), 10), 20, doneCb);
let accessData = storage.getItem('wallet:accessData');
checkNot(accessData, null, doneCb);
let accessDataJson = JSON.parse(accessData);
let accessDataJson = accessData;
check('mainKey' in accessDataJson, true, doneCb);
check(typeof accessDataJson['mainKey'], 'string', doneCb);
check('hash' in accessDataJson, true, doneCb);
check(accessDataJson['hash'], CryptoJS.SHA256(CryptoJS.SHA256(pin)).toString(), doneCb);

let walletData = localStorage.getItem('wallet:data');
let walletData = storage.getItem('wallet:data');
checkNot(walletData, null, doneCb);
let walletDataJson = JSON.parse(walletData);
let walletDataJson = walletData;
check('historyTransactions' in walletDataJson, true, doneCb);
check(typeof walletDataJson['historyTransactions'], 'object', doneCb);
check('00034a15973117852c45520af9e4296c68adb9d39dc99a0342e23cd6686b295e' in walletDataJson['historyTransactions'], true, doneCb);
Expand All @@ -86,7 +88,7 @@ beforeEach(() => {
test('Generate new HD wallet', (done) => {
doneCb = done;

// Generate new wallet and save data in localStorage
// Generate new wallet and save data in storage
const words = wallet.generateWalletWords(256);
check(wallet.wordsValid(words).valid, true, done);
const promise = wallet.executeGenerateWallet(words, '', pin, 'password', true);
Expand All @@ -105,7 +107,7 @@ test('Generate HD wallet from predefined words', (done) => {
addressShared = 'WgSpcCwYAbtt31S2cqU7hHJkUHdac2EPWG';


// Generate new wallet and save data in localStorage
// Generate new wallet and save data in storage
const promise = wallet.executeGenerateWallet(words, '', pin, 'password', true);

promise.then(() => {
Expand Down
14 changes: 8 additions & 6 deletions __tests__/tokens_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import wallet from '../src/wallet';
import { util } from 'bitcore-lib';
import WebSocketHandler from '../src/WebSocketHandler';

const storage = require('../src/storage').default;

const createdTxHash = '00034a15973117852c45520af9e4296c68adb9d39dc99a0342e23cd6686b295e';
const createdToken = util.buffer.bufferToHex(tokens.getTokenUID(createdTxHash, 0));

Expand Down Expand Up @@ -46,7 +48,7 @@ test('Token UID', () => {
});

const readyLoadHistory = (pin) => {
const encrypted = JSON.parse(localStorage.getItem('wallet:accessData')).mainKey;
const encrypted = storage.getItem('wallet:accessData').mainKey;
const privKeyStr = wallet.decryptData(encrypted, pin);
const privKey = HDPrivateKey(privKeyStr)
return wallet.loadAddressHistory(0, GAP_LIMIT, privKey, pin);
Expand All @@ -55,13 +57,13 @@ const readyLoadHistory = (pin) => {
test('New token', (done) => {
const words = 'connect sunny silent cabin leopard start turtle tortoise dial timber woman genre pave tuna rice indicate gown draft palm collect retreat meadow assume spray';
const pin = '123456';
// Generate new wallet and save data in localStorage
// Generate new wallet and save data in storage
wallet.executeGenerateWallet(words, '', pin, 'password', false);
const promise = readyLoadHistory(pin);
const address = localStorage.getItem('wallet:address');
const address = storage.getItem('wallet:address');
promise.then(() => {
// Adding data to localStorage to be used in the signing process
const savedData = JSON.parse(localStorage.getItem('wallet:data'));
// Adding data to storage to be used in the signing process
const savedData = storage.getItem('wallet:data');
const createdKey = `${createdTxHash},0`;
savedData['historyTransactions'] = {
'00034a15973117852c45520af9e4296c68adb9d39dc99a0342e23cd6686b295e': {
Expand All @@ -76,7 +78,7 @@ test('New token', (done) => {
]
}
};
localStorage.setItem('wallet:data', JSON.stringify(savedData));
storage.setItem('wallet:data', savedData);
const input = {'tx_id': '00034a15973117852c45520af9e4296c68adb9d39dc99a0342e23cd6686b295e', 'index': '0', 'token': '00', 'address': address};
const output = {'address': address, 'value': 100, 'tokenData': 0};
const tokenName = 'TestCoin';
Expand Down
28 changes: 15 additions & 13 deletions __tests__/transaction_utils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,18 @@ import buffer from 'buffer';
import { OP_PUSHDATA1 } from '../src/opcodes';
import { DEFAULT_TX_VERSION } from '../src/constants';

const storage = require('../src/storage').default;

test('Tx weight constants', () => {
transaction.updateTransactionWeightConstants(10, 1.5, 8);
expect(parseFloat(localStorage.getItem('wallet:txMinWeight'))).toBe(10);
expect(parseFloat(localStorage.getItem('wallet:txWeightCoefficient'))).toBe(1.5);
expect(parseFloat(localStorage.getItem('wallet:txMinWeightK'))).toBe(8);
expect(parseFloat(storage.getItem('wallet:txMinWeight'))).toBe(10);
expect(parseFloat(storage.getItem('wallet:txWeightCoefficient'))).toBe(1.5);
expect(parseFloat(storage.getItem('wallet:txMinWeightK'))).toBe(8);

transaction.updateTransactionWeightConstants(15, 1.2, 10);
expect(parseFloat(localStorage.getItem('wallet:txMinWeight'))).toBe(15);
expect(parseFloat(localStorage.getItem('wallet:txWeightCoefficient'))).toBe(1.2);
expect(parseFloat(localStorage.getItem('wallet:txMinWeightK'))).toBe(10);
expect(parseFloat(storage.getItem('wallet:txMinWeight'))).toBe(15);
expect(parseFloat(storage.getItem('wallet:txWeightCoefficient'))).toBe(1.2);
expect(parseFloat(storage.getItem('wallet:txMinWeightK'))).toBe(10);
});

test('Unsigned int to bytes', () => {
Expand Down Expand Up @@ -164,14 +166,14 @@ test('Create input data', () => {
expect(transaction.createInputData(signature, pubkeyBytes2).length).toBe(123);
});

test('Prepare data to send tokens', (done) => {
test('Prepare data to send tokens', async (done) => {
// Now we will update the data in the inputs
let words = 'purse orchard camera cloud piece joke hospital mechanic timber horror shoulder rebuild you decrease garlic derive rebuild random naive elbow depart okay parrot cliff';
// Generate new wallet and save data in localStorage
wallet.executeGenerateWallet(words, '', '123456', 'password', true);
// Adding data to localStorage to be used in the signing process
let savedData = JSON.parse(localStorage.getItem('wallet:data'));
let addr = localStorage.getItem('wallet:address');
// Generate new wallet and save data in storage
await wallet.executeGenerateWallet(words, '', '123456', 'password', true);
// Adding data to storage to be used in the signing process
let savedData = storage.getItem('wallet:data');
let addr = storage.getItem('wallet:address');
savedData['historyTransactions'] = {
'00034a15973117852c45520af9e4296c68adb9d39dc99a0342e23cd6686b295e': {
'tx_id': '00034a15973117852c45520af9e4296c68adb9d39dc99a0342e23cd6686b295e',
Expand All @@ -185,7 +187,7 @@ test('Prepare data to send tokens', (done) => {
]
}
};
localStorage.setItem('wallet:data', JSON.stringify(savedData));
storage.setItem('wallet:data', savedData);

// First get data to sign
let tx_id = '00034a15973117852c45520af9e4296c68adb9d39dc99a0342e23cd6686b295e';
Expand Down
6 changes: 4 additions & 2 deletions __tests__/wallet.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import wallet from '../src/wallet';
import dateFormatter from '../src/date';
import { HATHOR_TOKEN_CONFIG } from '../src/constants';

const storage = require('../src/storage').default;

test('Wallet operations for transaction', () => {
const words = wallet.generateWalletWords(256);
wallet.executeGenerateWallet(words, '', '123456', 'password', false);
Expand Down Expand Up @@ -129,9 +131,9 @@ test('Wallet operations for transaction', () => {
'171hK8MaRpG2SqQMMQ34EdTharUmP1Qk4r': {},
}

const xpubkey = JSON.parse(localStorage.getItem('wallet:data')).xpubkey;
const xpubkey = storage.getItem('wallet:data').xpubkey;

localStorage.setItem('wallet:data', JSON.stringify({keys, historyTransactions, xpubkey}));
storage.setItem('wallet:data', {keys, historyTransactions, xpubkey});

const expectedBalance = {
'00': {
Expand Down
Loading