Merge pull request #547 from maraoz/optimize/txp-protocol

Optimize txp protocol, send only indexes after wallet creation
This commit is contained in:
Gustavo Maximiliano Cortez 2014-06-04 15:30:44 -03:00
commit 660462ddce
9 changed files with 210 additions and 63 deletions

View file

@ -5,6 +5,7 @@ module.exports.TxProposals = require('./js/models/core/TxProposals');
module.exports.PrivateKey = require('./js/models/core/PrivateKey');
module.exports.Passphrase = require('./js/models/core/Passphrase');
module.exports.Structure = require('./js/models/core/Structure');
module.exports.AddressIndex = require('./js/models/core/AddressIndex');
// components

View file

@ -0,0 +1,69 @@
'use strict';
var imports = require('soop').imports();
function AddressIndex(opts) {
opts = opts || {};
this.walletId = opts.walletId;
this.changeIndex = opts.changeIndex || 0;
this.receiveIndex = opts.receiveIndex || 0;
}
AddressIndex.fromObj = function(data) {
if (data instanceof AddressIndex) {
throw new Error('bad data format: Did you use .toObj()?');
}
var ret = new AddressIndex(data);
return ret;
};
AddressIndex.prototype.toObj = function() {
return {
walletId: this.walletId,
changeIndex: this.changeIndex,
receiveIndex: this.receiveIndex,
};
};
AddressIndex.prototype.checkRange = function(index, isChange) {
if ((isChange && index > this.changeIndex) ||
(!isChange && index > this.receiveIndex)) {
throw new Error('Out of bounds at index %d isChange: %d', index, isChange);
}
};
AddressIndex.prototype.getChangeIndex = function() {
return this.changeIndex;
};
AddressIndex.prototype.getReceiveIndex = function() {
return this.receiveIndex;
};
AddressIndex.prototype.increment = function(isChange) {
if (isChange) {
this.changeIndex++;
} else {
this.receiveIndex++;
}
};
AddressIndex.prototype.merge = function(inAddressIndex) {
var hasChanged = false;
// Indexes
if (inAddressIndex.changeIndex > this.changeIndex) {
this.changeIndex = inAddressIndex.changeIndex;
hasChanged = true;
}
if (inAddressIndex.receiveIndex > this.receiveIndex) {
this.receiveIndex = inAddressIndex.receiveIndex;
hasChanged = true;
}
return hasChanged;
};
module.exports = require('soop')(AddressIndex);

View file

@ -81,12 +81,15 @@ PrivateKey.prototype.get = function(index,isChange) {
return this.getForPath(path);
};
PrivateKey.prototype.getAll = function(addressIndex, changeAddressIndex) {
PrivateKey.prototype.getAll = function(receiveIndex, changeIndex) {
if (typeof receiveIndex === 'undefined' || typeof changeIndex === 'undefined')
throw new Error('Invalid parameters');
var ret = [];
for(var i=0;i<addressIndex; i++) {
for(var i=0;i<receiveIndex; i++) {
ret.push(this.get(i,false));
}
for(var i=0; i<changeAddressIndex; i++) {
for(var i=0; i<changeIndex; i++) {
ret.push(this.get(i,true));
}
return ret;

View file

@ -6,12 +6,10 @@ var imports = require('soop').imports();
var bitcore = require('bitcore');
var HK = bitcore.HierarchicalKey;
var PrivateKey = require('./PrivateKey');
var Structure = require('./Structure');
var Structure = require('./Structure');
var AddressIndex= require('./AddressIndex');
var Address = bitcore.Address;
var Script = bitcore.Script;
var coinUtil = bitcore.util;
var Transaction = bitcore.Transaction
var util = bitcore.util;
function PublicKeyRing(opts) {
opts = opts || {};
@ -26,8 +24,7 @@ function PublicKeyRing(opts) {
this.copayersHK = opts.copayersHK || [];
this.changeAddressIndex= opts.changeAddressIndex || 0;
this.addressIndex= opts.addressIndex || 0;
this.indexes = AddressIndex.fromObj(opts.indexes) || new AddressIndex(opts);
this.publicKeysCache = opts.publicKeysCache || {};
this.nicknameFor = opts.nicknameFor || {};
@ -54,9 +51,8 @@ PublicKeyRing.prototype.toObj = function() {
networkName: this.network.name,
requiredCopayers: this.requiredCopayers,
totalCopayers: this.totalCopayers,
indexes: this.indexes.toObj(),
changeAddressIndex: this.changeAddressIndex,
addressIndex: this.addressIndex,
copayersExtPubKeys: this.copayersHK.map( function (b) {
return b.extendedPublicKeyString();
}),
@ -158,16 +154,9 @@ PublicKeyRing.prototype.getPubKeys = function(index, isChange) {
return pubKeys;
};
PublicKeyRing.prototype._checkIndexRange = function (index, isChange) {
if ( (isChange && index > this.changeAddressIndex) ||
(!isChange && index > this.addressIndex)) {
throw new Error('Out of bounds at getAddress: Index %d isChange: %d', index, isChange);
}
};
// TODO this could be cached
PublicKeyRing.prototype.getRedeemScript = function (index, isChange) {
this._checkIndexRange(index, isChange);
this.indexes.checkRange(index, isChange);
var pubKeys = this.getPubKeys(index, isChange);
var script = Script.createMultisig(this.requiredCopayers, pubKeys);
@ -197,14 +186,9 @@ PublicKeyRing.prototype.getScriptPubKeyHex = function (index, isChange) {
//generate a new address, update index.
PublicKeyRing.prototype.generateAddress = function(isChange) {
var ret =
this.getAddress(isChange ? this.changeAddressIndex : this.addressIndex, isChange);
if (isChange) {
this.changeAddressIndex++;
} else {
this.addressIndex++;
}
var index = isChange ? this.indexes.getChangeIndex() : this.indexes.getReceiveIndex();
var ret = this.getAddress(index, isChange);
this.indexes.increment(isChange);
return ret;
};
@ -219,7 +203,7 @@ PublicKeyRing.prototype.getAddressesInfo = function(opts) {
var ret = [];
if (!opts.excludeChange) {
for (var i=0; i<this.changeAddressIndex; i++) {
for (var i=0; i<this.indexes.getChangeIndex(); i++) {
ret.unshift({
address: this.getAddress(i,true),
isChange: true
@ -228,7 +212,7 @@ PublicKeyRing.prototype.getAddressesInfo = function(opts) {
}
if (!opts.excludeMain) {
for (var i=0; i<this.addressIndex; i++) {
for (var i=0; i<this.indexes.getReceiveIndex(); i++) {
ret.unshift({
address: this.getAddress(i,false),
isChange: false
@ -248,10 +232,10 @@ PublicKeyRing.prototype._addScriptMap = function (map, index, isChange) {
PublicKeyRing.prototype.getRedeemScriptMap = function () {
var ret = {};
for (var i=0; i<this.changeAddressIndex; i++) {
for (var i=0; i<this.indexes.getChangeIndex(); i++) {
this._addScriptMap(ret,i,true);
}
for (var i=0; i<this.addressIndex; i++) {
for (var i=0; i<this.indexes.getReceiveIndex(); i++) {
this._addScriptMap(ret,i,false);
}
return ret;
@ -278,22 +262,6 @@ PublicKeyRing.prototype._checkInPRK = function(inPKR, ignoreId) {
};
PublicKeyRing.prototype._mergeIndexes = function(inPKR) {
var hasChanged = false;
// Indexes
if (inPKR.changeAddressIndex > this.changeAddressIndex) {
this.changeAddressIndex = inPKR.changeAddressIndex;
hasChanged = true;
}
if (inPKR.addressIndex > this.addressIndex) {
this.addressIndex = inPKR.addressIndex;
hasChanged = true;
}
return hasChanged;
};
PublicKeyRing.prototype._mergePubkeys = function(inPKR) {
var self = this;
var hasChanged = false;
@ -330,7 +298,7 @@ PublicKeyRing.prototype.merge = function(inPKR, ignoreId) {
this._checkInPRK(inPKR, ignoreId);
if (this._mergeIndexes(inPKR))
if (this.indexes.merge(inPKR.indexes))
hasChanged = true;
if (this._mergePubkeys(inPKR))

View file

@ -72,6 +72,16 @@ Wallet.prototype.connectToAll = function() {
}
};
Wallet.prototype._handleIndexes = function(senderId, data, isInbound) {
this.log('RECV INDEXES:', data);
var inIndexes = copay.AddressIndex.fromObj(data.indexes);
var hasChanged = this.publicKeyRing.indexes.merge(inIndexes);
if (hasChanged) {
this.emit('publicKeyRingUpdated');
this.store();
}
};
Wallet.prototype._handlePublicKeyRing = function(senderId, data, isInbound) {
this.log('RECV PUBLICKEYRING:', data);
@ -87,9 +97,9 @@ Wallet.prototype._handlePublicKeyRing = function(senderId, data, isInbound) {
if (this.publicKeyRing.isComplete()) {
this._lockIncomming();
}
this.emit('publicKeyRingUpdated');
this.store();
}
this.emit('publicKeyRingUpdated');
this.store();
};
@ -142,6 +152,9 @@ Wallet.prototype._handleData = function(senderId, data, isInbound) {
case 'txProposals':
this._handleTxProposals(senderId, data, isInbound);
break;
case 'indexes':
this._handleIndexes(senderId, data, isInbound);
break;
}
};
@ -378,6 +391,15 @@ Wallet.prototype.sendPublicKeyRing = function(recipients) {
walletId: this.id,
});
};
Wallet.prototype.sendIndexes = function(recipients) {
this.log('### INDEXES TO:', recipients || 'All', this.publicKeyRing.indexes.toObj());
this.network.send(recipients, {
type: 'indexes',
indexes: this.publicKeyRing.indexes.toObj(),
walletId: this.id,
});
};
Wallet.prototype.getName = function() {
return this.name || this.id;
@ -390,7 +412,7 @@ Wallet.prototype._doGenerateAddress = function(isChange) {
Wallet.prototype.generateAddress = function(isChange, cb) {
var addr = this._doGenerateAddress(isChange);
this.sendPublicKeyRing();
this.sendIndexes();
this.store();
if (cb) return cb(addr);
return addr;
@ -596,7 +618,7 @@ Wallet.prototype.createTx = function(toAddress, amountSatStr, opts, cb) {
this.getUnspent(function(err, safeUnspent) {
var ntxid = self.createTxSync(toAddress, amountSatStr, safeUnspent, opts);
if (ntxid) {
self.sendPublicKeyRing();
self.sendIndexes();
self.sendTxProposals(null, ntxid);
self.store();
self.emit('txProposalsUpdated');

84
test/test.AddressIndex.js Normal file
View file

@ -0,0 +1,84 @@
'use strict';
var chai = chai || require('chai');
var should = chai.should();
var bitcore = bitcore || require('bitcore');
var Address = bitcore.Address;
var buffertools = bitcore.buffertools;
var copay = copay || require('../copay');
var PublicKeyRing = copay.PublicKeyRing;
var AddressIndex = copay.AddressIndex;
var config = {
networkName:'livenet',
};
var createAI = function () {
var i = new AddressIndex();
should.exist(i);
i.walletId = '1234567';
return i;
};
describe('AddressIndex model', function() {
it('should create an instance (livenet)', function () {
var i = new AddressIndex();
should.exist(i);
});
it('show be able to tostore and read', function () {
var i = createAI();
var changeN = 2;
var addressN = 2;
for(var j=0; j<changeN; j++) {
i.increment(true);
}
for(var j=0; j<addressN; j++) {
i.increment(false);
}
var data = i.toObj();
should.exist(data);
var i2 = AddressIndex.fromObj(data);
i2.walletId.should.equal(i.walletId);
i2.getChangeIndex().should.equal(changeN);
i2.getReceiveIndex().should.equal(addressN);
});
it('should count generation indexes', function () {
var j = createAI();
for(var i=0; i<3; i++)
j.increment(true);
for(var i=0; i<2; i++)
j.increment(false);
j.changeIndex.should.equal(3);
j.receiveIndex.should.equal(2);
});
it('#merge tests', function () {
var j = createAI();
for(var i=0; i<15; i++)
j.increment(true);
for(var i=0; i<7; i++)
j.increment(false);
var j2 = new AddressIndex({
walletId: j.walletId,
});
j2.merge(j).should.equal(true);
j2.changeIndex.should.equal(15);
j2.receiveIndex.should.equal(7);
j2.merge(j).should.equal(false);
});
});

View file

@ -96,8 +96,8 @@ describe('PublicKeyRing model', function() {
(function() {w.addCopayer(copayers[i])}).should.throw();
}
w2.changeAddressIndex.should.equal(changeN);
w2.addressIndex.should.equal(addressN);
w2.indexes.getChangeIndex().should.equal(changeN);
w2.indexes.getReceiveIndex().should.equal(addressN);
});
@ -148,8 +148,8 @@ describe('PublicKeyRing model', function() {
for(var i=0; i<2; i++)
w.generateAddress(false);
w.changeAddressIndex.should.equal(3);
w.addressIndex.should.equal(2);
w.indexes.getChangeIndex().should.equal(3);
w.indexes.getReceiveIndex().should.equal(2);
});
it('#merge index tests', function () {
@ -168,8 +168,8 @@ describe('PublicKeyRing model', function() {
w2.merge(w).should.equal(true);
w2.requiredCopayers.should.equal(3);
w2.totalCopayers.should.equal(5);
w2.changeAddressIndex.should.equal(2);
w2.addressIndex.should.equal(3);
w2.indexes.getChangeIndex().should.equal(2);
w2.indexes.getReceiveIndex().should.equal(3);
//
w2.merge(w).should.equal(false);

View file

@ -66,7 +66,6 @@ var vopts = {
describe('TxProposals model', function() {
it('verify TXs', function (done) {
var priv = new PrivateKey(config);
@ -95,8 +94,8 @@ describe('TxProposals model', function() {
var b = w.txps[k].builder;
var tx = b.build();
tx.isComplete().should.equal(false);
b.sign( priv2.getAll(pkr.addressIndex, pkr.changeAddressIndex) );
b.sign( priv3.getAll(pkr.addressIndex, pkr.changeAddressIndex) );
b.sign(priv2.getAll(pkr.indexes.getReceiveIndex(), pkr.indexes.getChangeIndex()) );
b.sign(priv3.getAll(pkr.indexes.getReceiveIndex(), pkr.indexes.getChangeIndex()) );
tx = b.build();
tx.isComplete().should.equal(true);
@ -142,7 +141,7 @@ describe('TxProposals model', function() {
var signRet;
if (priv) {
b.sign( priv.getAll(pkr.addressIndex, pkr.changeAddressIndex) );
b.sign( priv.getAll(pkr.indexes.getReceiveIndex(), pkr.indexes.getChangeIndex()) );
}
var me = {};
if (priv) me[priv.id] = Date.now();

View file

@ -61,7 +61,8 @@ describe('WalletFactory model', function() {
});
it('#fromObj #toObj round trip', function() {
var o = '{"opts":{"id":"dbfe10c3fae71cea","spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"reconnectDelay":100,"netKey":"LppzFYqlgT0=","version":"0.0.5"},"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"changeAddressIndex":3,"addressIndex":3,"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{},"publicKeysCache":{"m/0/1/0":["0314368b8efa07e8c7dad30498d0a7e3aa575db1fef833347c6d381c1a33a17b17","02cfd95f89ab46bd3bd86954dd9f83dbab0cd2e4466dee587e8e4d8d733fc0d748","02568969eb6212fe946450be6c5b3353fc754a40b2cdc4aed501a8976fec371da8","0360f870a088ae0ef1c37035a9b6a462ca8dcdd5da275f4e2dcd19f44b81d3e7e4","0300ad8f1bded838b02e127bb25961fbcee718db2df81f680f889692acdcbdd73d"],"m/0/1/1":["024f97a9adb2fa9306c4e3d9244f5e5355c7e2c6b3dd4122ba804e17dc9729df5d","0214834a5adcbc4ad0f3bbbc1c280b8ac480387fcc9a1fd988c1526ed496d923c4","024e72338bd5e976375d076bd71a9649e9141b4cbfc9e16cb7109b354b3e913a05","0322045ea35c3118aa7ab9f2c9f182b0120956b0aa65cc72b9d093f145327a4b17","030dc2450c72df366c1960739c577a2efd4451070bd78effcb6f71d1bcd7dfc7a8"],"m/0/1/2":["0247de59deb66783b8f9b0c326234a9569d00866c2a73f599e77a4d0cab5cbce8f","0376e49f0ac3647404034aae0dc8dd927c34a634ef24ea36f56a272f75fce9539b","032fbaa2593bd1eea4a46e7ac15f15802cdd1eb65a7d5bc4364ddd9d52f0838234","03a81f2a7e1f7191aa0b0c6e0a4ccefc71edd3564e86014972fe338045f68d5a5a","02eb8a012ea9a709392502cacda6ef5115d6d2319ab470d546d9068ab941621a99"],"m/0/0/0":["036dcbd378b4352120d6b720b6294dd2d0dd02801fcf010bb69dadbec1f3999279","022089eedb85dc45d1efa418e1ea226588deedebc1d85acca15ff72783e33636c0","0388aa5fd432b74c56427396f350d236c3ca8f7b2f62da513ce4c2e6ff04a67e9c","02fc4caa7449db7483d2e1fccdacac6fa2f736278c758af9966402589b5632f13e","02e4a15b885d8b2d586f82fa85d16179644e60a154674bde0ec3004810b1bdab99"],"m/0/0/1":["039afa26b2f341c76c7b3c3d0672438f35ac6ebb67b1ddfefac9cd79b7b24418c1","021acaaf500d431ebc396f50630767b01c91ce98ae48e968775ceaad932b7e3b8e","022a947259c4a9f76d5e95c0849df31d01233df41d0d75d631b89317a48d8cddce","03d38d9f94217da780303d9a8987c86d737ef39683febc0cd6632cddbfa62186fd","0394d2581b307fe2af19721888d922aab58ab198ef88cedf9506177e30d807811e"],"m/0/0/2":["037825ffce15d34f9bd6c02bcda7701826706471a4d6ab5004eb965f98811c2098","023768dd6d3c71b7df5733ccda5b2d8b454d5b4c4179d91a6fda74db8b869a2406","021a79e91f003f308764d43039e9b5d56bc8f33ca2f4d30ec6cc5a37c0d09dc273","02437f1e388b273936319f79a5d22958ef5ebff9c8cd7b6f6f72518445b1e30867","0373b0881cb4fd02baa62589023fdfe9739c6148cf104d907549f2528eb80146f5"]}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet","privateKeyCache":{}}}';
var o = '{"opts":{"id":"dbfe10c3fae71cea","spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"reconnectDelay":100,"netKey":"LppzFYqlgT0=","version":"0.0.5"},"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":{"changeIndex":0,"receiveIndex":0},"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{},"publicKeysCache":{"m/0/1/0":["0314368b8efa07e8c7dad30498d0a7e3aa575db1fef833347c6d381c1a33a17b17","02cfd95f89ab46bd3bd86954dd9f83dbab0cd2e4466dee587e8e4d8d733fc0d748","02568969eb6212fe946450be6c5b3353fc754a40b2cdc4aed501a8976fec371da8","0360f870a088ae0ef1c37035a9b6a462ca8dcdd5da275f4e2dcd19f44b81d3e7e4","0300ad8f1bded838b02e127bb25961fbcee718db2df81f680f889692acdcbdd73d"],"m/0/1/1":["024f97a9adb2fa9306c4e3d9244f5e5355c7e2c6b3dd4122ba804e17dc9729df5d","0214834a5adcbc4ad0f3bbbc1c280b8ac480387fcc9a1fd988c1526ed496d923c4","024e72338bd5e976375d076bd71a9649e9141b4cbfc9e16cb7109b354b3e913a05","0322045ea35c3118aa7ab9f2c9f182b0120956b0aa65cc72b9d093f145327a4b17","030dc2450c72df366c1960739c577a2efd4451070bd78effcb6f71d1bcd7dfc7a8"],"m/0/1/2":["0247de59deb66783b8f9b0c326234a9569d00866c2a73f599e77a4d0cab5cbce8f","0376e49f0ac3647404034aae0dc8dd927c34a634ef24ea36f56a272f75fce9539b","032fbaa2593bd1eea4a46e7ac15f15802cdd1eb65a7d5bc4364ddd9d52f0838234","03a81f2a7e1f7191aa0b0c6e0a4ccefc71edd3564e86014972fe338045f68d5a5a","02eb8a012ea9a709392502cacda6ef5115d6d2319ab470d546d9068ab941621a99"],"m/0/0/0":["036dcbd378b4352120d6b720b6294dd2d0dd02801fcf010bb69dadbec1f3999279","022089eedb85dc45d1efa418e1ea226588deedebc1d85acca15ff72783e33636c0","0388aa5fd432b74c56427396f350d236c3ca8f7b2f62da513ce4c2e6ff04a67e9c","02fc4caa7449db7483d2e1fccdacac6fa2f736278c758af9966402589b5632f13e","02e4a15b885d8b2d586f82fa85d16179644e60a154674bde0ec3004810b1bdab99"],"m/0/0/1":["039afa26b2f341c76c7b3c3d0672438f35ac6ebb67b1ddfefac9cd79b7b24418c1","021acaaf500d431ebc396f50630767b01c91ce98ae48e968775ceaad932b7e3b8e","022a947259c4a9f76d5e95c0849df31d01233df41d0d75d631b89317a48d8cddce","03d38d9f94217da780303d9a8987c86d737ef39683febc0cd6632cddbfa62186fd","0394d2581b307fe2af19721888d922aab58ab198ef88cedf9506177e30d807811e"],"m/0/0/2":["037825ffce15d34f9bd6c02bcda7701826706471a4d6ab5004eb965f98811c2098","023768dd6d3c71b7df5733ccda5b2d8b454d5b4c4179d91a6fda74db8b869a2406","021a79e91f003f308764d43039e9b5d56bc8f33ca2f4d30ec6cc5a37c0d09dc273","02437f1e388b273936319f79a5d22958ef5ebff9c8cd7b6f6f72518445b1e30867","0373b0881cb4fd02baa62589023fdfe9739c6148cf104d907549f2528eb80146f5"]}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet","privateKeyCache":{}}}';
var wf = new WalletFactory(config, '0.0.5');
var w = wf.fromObj(JSON.parse(o));