From a1155c2798e4f4cfbfe63b5243ec3cf7d9c94cd2 Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Tue, 8 Jul 2014 20:00:42 -0700 Subject: [PATCH 1/3] add support for 8 byte big endian nonce to Message ...network protocol is backwards incompatible with previous network protocl. also includes a second version number for backwards-compatible changes (the original version number was for backwards-incompatible changes). --- js/models/core/Message.js | 84 ++++++++++++++++++++++++++++++++----- js/models/network/WebRTC.js | 15 +++---- test/test.Message.js | 61 +++++++++++++++++++++++++-- test/test.network.WebRTC.js | 18 ++++---- 4 files changed, 149 insertions(+), 29 deletions(-) diff --git a/js/models/core/Message.js b/js/models/core/Message.js index b281eecd8..7c9de9c27 100644 --- a/js/models/core/Message.js +++ b/js/models/core/Message.js @@ -7,9 +7,20 @@ var bitcore = require('bitcore'); var Message = function() { }; -Message.encode = function(topubkey, fromkey, payload) { - var version = new Buffer([0]); - var toencrypt = Buffer.concat([version, payload]); +Message.encode = function(topubkey, fromkey, payload, opts) { + var version1 = new Buffer([1]); //peers will reject messges containing not-understood version1 + //i.e., increment version1 to prevent communications with old clients + var version2 = new Buffer([0]); //peers will not reject messages containing not-understood version2 + //i.e., increment version2 to allow communication with old clients, but signal new clients + + if (opts && opts.nonce && Buffer.isBuffer(opts.nonce) && opts.nonce.length == 8) { + var nonce = opts.nonce; + } else { + var nonce = new Buffer(8); + nonce.fill(0); //nonce is a big endian 8 byte number + } + + var toencrypt = Buffer.concat([version1, version2, nonce, payload]); var encrypted = Message._encrypt(topubkey, toencrypt); var sig = Message._sign(fromkey, encrypted); var encoded = { @@ -20,7 +31,14 @@ Message.encode = function(topubkey, fromkey, payload) { return encoded; }; -Message.decode = function(key, encoded) { +Message.decode = function(key, encoded, opts) { + if (opts && opts.prevnonce && Buffer.isBuffer(opts.prevnonce) && opts.prevnonce.length == 8) { + var prevnonce = opts.prevnonce; + } else { + var prevnonce = new Buffer(8); + prevnonce.fill(0); //nonce is a big endian 8 byte number + } + try { var frompubkey = new Buffer(encoded.pubkey, 'hex'); } catch (e) { @@ -40,8 +58,9 @@ Message.decode = function(key, encoded) { throw new Error('Error verifying signature: ' + e); } - if (!v) + if (!v) { throw new Error('Invalid signature'); + } try { var decrypted = Message._decrypt(key.private, encrypted); @@ -49,14 +68,59 @@ Message.decode = function(key, encoded) { throw new Error('Cannot decrypt data: ' + e); } - if (decrypted[0] !== 0) - throw new Error('Invalid version number'); + try { + var version1 = decrypted[0]; + var version2 = decrypted[1]; + var nonce = decrypted.slice(2, 10); + var payload = decrypted.slice(10); + } catch (e) { + throw new Error('Cannot parse decrypted data: ' + e); + } - if (decrypted.length === 0) + if (payload.length === 0) { throw new Error('No data present'); + } - var payload = decrypted.slice(1); - return payload; + if (version1 !== 1) { + throw new Error('Invalid version number'); + } + + if (version2 !== 0) { + //put special version2 handling code here, if ever needed + } + + if (!Message._noncegt(nonce, prevnonce) && prevnonce.toString('hex') !== '0000000000000000') { + throw new Error('Nonce not equal to zero and not greater than the previous nonce'); + } + + var decoded = { + version1: version1, + version2: version2, + nonce: nonce, + payload: payload + }; + + return decoded; +}; + +//return true if nonce > prevnonce; false otherwise +Message._noncegt = function(nonce, prevnonce) { + var noncep1 = nonce.slice(0, 4).readUInt32BE(0); + var prevnoncep1 = prevnonce.slice(0, 4).readUInt32BE(0); + + if (noncep1 > prevnoncep1) + return true; + + if (noncep1 < prevnoncep1) + return false; + + var noncep2 = nonce.slice(4, 8).readUInt32BE(0); + var prevnoncep2 = prevnonce.slice(4, 8).readUInt32BE(0); + + if (noncep2 > prevnoncep2) + return true; + + return false; }; Message._encrypt = function(topubkey, payload, r, iv) { diff --git a/js/models/network/WebRTC.js b/js/models/network/WebRTC.js index 91d4092d7..462c258a5 100644 --- a/js/models/network/WebRTC.js +++ b/js/models/network/WebRTC.js @@ -376,18 +376,19 @@ Network.prototype.getPeer = function() { return this.peer; }; -Network.prototype._encode = function(topubkey, fromkey, payload) { - var encoded = Message.encode(topubkey, fromkey, payload); +Network.prototype._encode = function(topubkey, fromkey, payload, opts) { + var encoded = Message.encode(topubkey, fromkey, payload, opts); return encoded; }; -Network.prototype._decode = function(key, encoded) { - var payload = Message.decode(key, encoded); +Network.prototype._decode = function(key, encoded, opts) { + var decoded = Message.decode(key, encoded, opts); + var payload = decoded.payload; return payload; }; -Network.prototype._sendToOne = function(copayerId, payload, cb) { +Network.prototype._sendToOne = function(copayerId, payload, opts, cb) { if (!Buffer.isBuffer(payload)) throw new Error('payload must be a buffer'); var peerId = this.peerFromCopayer(copayerId); @@ -400,7 +401,7 @@ Network.prototype._sendToOne = function(copayerId, payload, cb) { if (typeof cb === 'function') cb(); }; -Network.prototype.send = function(copayerIds, payload, cb) { +Network.prototype.send = function(copayerIds, payload, opts, cb) { if (!payload) return cb(); var self = this; @@ -421,7 +422,7 @@ Network.prototype.send = function(copayerIds, payload, cb) { var copayerIdBuf = new Buffer(copayerId, 'hex'); var encPayload = self._encode(copayerIdBuf, self.getKey(), payloadBuf); var enc = new Buffer(JSON.stringify(encPayload)); - self._sendToOne(copayerId, enc, function() { + self._sendToOne(copayerId, enc, opts, function() { if (++i === l && typeof cb === 'function') cb(); }); }); diff --git a/test/test.Message.js b/test/test.Message.js index 6abe1e3e3..10134b736 100644 --- a/test/test.Message.js +++ b/test/test.Message.js @@ -36,15 +36,70 @@ describe('Message model', function() { var encoded = Message.encode(key2.public, key, message); var decoded = Message.decode(key2, encoded); - decoded.toString('hex').should.equal(messagehex); + var payload = decoded.payload; + payload.toString('hex').should.equal(messagehex); + }); + + it('should decode an encoded message with proper prevnonce', function() { + var message = new Buffer('message'); + var messagehex = message.toString('hex'); + var nonce = new Buffer([0, 0, 0, 0, 0, 0, 0, 2]); + var opts = {nonce: nonce}; + var encoded = Message.encode(key2.public, key, message, opts); + + var prevnonce = new Buffer([0, 0, 0, 0, 0, 0, 0, 1]); + opts = {prevnonce: prevnonce}; + var decoded = Message.decode(key2, encoded, opts); + var payload = decoded.payload; + payload.toString('hex').should.equal(messagehex); + }); + + it('should decode an encoded message with proper prevnonce - for first part', function() { + var message = new Buffer('message'); + var messagehex = message.toString('hex'); + var nonce = new Buffer([0, 0, 0, 2, 0, 0, 0, 0]); + var opts = {nonce: nonce}; + var encoded = Message.encode(key2.public, key, message, opts); + + var prevnonce = new Buffer([0, 0, 0, 1, 0, 0, 0, 0]); + opts = {prevnonce: prevnonce}; + var decoded = Message.decode(key2, encoded, opts); + var payload = decoded.payload; + payload.toString('hex').should.equal(messagehex); + }); + + it('should fail if prevnonce is too high', function() { + var message = new Buffer('message'); + var messagehex = message.toString('hex'); + var nonce = new Buffer([0, 0, 0, 0, 0, 0, 0, 1]); + var opts = {nonce: nonce}; + var encoded = Message.encode(key2.public, key, message, opts); + + var prevnonce = new Buffer([0, 0, 0, 0, 0, 0, 0, 1]); + opts = {prevnonce: prevnonce}; + (function() {Message.decode(key2, encoded, opts)}).should.throw('Nonce not equal to zero and not greater than the previous nonce'); + }); + + it('should fail if prevnonce is too high - for first part', function() { + var message = new Buffer('message'); + var messagehex = message.toString('hex'); + var nonce = new Buffer([0, 0, 0, 1, 0, 0, 0, 0]); + var opts = {nonce: nonce}; + var encoded = Message.encode(key2.public, key, message, opts); + + var prevnonce = new Buffer([0, 0, 0, 1, 0, 0, 0, 0]); + opts = {prevnonce: prevnonce}; + (function() {Message.decode(key2, encoded, opts)}).should.throw('Nonce not equal to zero and not greater than the previous nonce'); }); it('should fail if the version number is incorrect', function() { var payload = new Buffer('message'); var fromkey = key; var topubkey = key2.public; - var version = new Buffer([1]); - var toencrypt = Buffer.concat([version, payload]); + var version1 = new Buffer([2]); + var version2 = new Buffer([0]); + var nonce = new Buffer([0, 0, 0, 0, 0, 0, 0, 0]); + var toencrypt = Buffer.concat([version1, version2, nonce, payload]); var encrypted = Message._encrypt(topubkey, toencrypt); var sig = Message._sign(fromkey, encrypted); var encoded = { diff --git a/test/test.network.WebRTC.js b/test/test.network.WebRTC.js index 1acbb32c5..24f414c12 100644 --- a/test/test.network.WebRTC.js +++ b/test/test.network.WebRTC.js @@ -168,11 +168,11 @@ describe('Network / WebRTC', function() { key.regenerateSync(); var copayerId = key.public.toString('hex'); - n._sendToOne = function(a1, a2, cb) { + n._sendToOne = function(a1, a2, a3, cb) { cb(); }; - var sig = undefined; - n.send(copayerId, data, function() { + var opts = {}; + n.send(copayerId, data, opts, function() { done(); }); @@ -191,13 +191,13 @@ describe('Network / WebRTC', function() { key.regenerateSync(); var copayerId = key.public.toString('hex'); - n._sendToOne = function(a1, enc, cb) { + n._sendToOne = function(a1, enc, opts, cb) { var encPayload = JSON.parse(enc.toString()); encPayload.sig.length.should.be.greaterThan(0); cb(); }; - var sig = undefined; - n.send(copayerId, data, function() { + var opts = {}; + n.send(copayerId, data, opts, function() { done(); }); @@ -216,11 +216,11 @@ describe('Network / WebRTC', function() { key.regenerateSync(); var copayerIds = [key.public.toString('hex')]; - n._sendToOne = function(a1, a2, cb) { + n._sendToOne = function(a1, a2, a3, cb) { cb(); }; - var sig = undefined; - n.send(copayerIds, data, function() { + var opts = {}; + n.send(copayerIds, data, opts, function() { done(); }); From 88ab38eb00e233556078aef8a7166a4763c32f5f Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Tue, 8 Jul 2014 23:03:30 -0700 Subject: [PATCH 2/3] add nonce support to WebRTC and Wallet Each person keeps track of their own nonce, and the nonces of the other copayers. The nonce is iterated for each message. If a person ever doesn't iterate their nonce, that message is discarded by the copayers. The nonces are saved as networkNonce (your nonce) and networkNonces (the nonces of your copayers) in the wallet file. In order to support restoring old wallets, the first four bytes of the 8 byte nonce are actually the current time in seconds. Thus you can restore an old wallet, because certainly at least one second has passed since your last message. Only if you try to restore an old wallet within 1 second from the time of your last message will you have a problem (or if your system clock is grossly inaccurate). --- js/models/core/Wallet.js | 27 ++++- js/models/network/WebRTC.js | 76 ++++++++++-- test/mocks/FakeNetwork.js | 51 +++++++++ test/test.Wallet.js | 11 ++ test/test.WalletFactory.js | 6 +- test/test.network.WebRTC.js | 222 ++++++++++++++++++++++++++++++++++-- 6 files changed, 370 insertions(+), 23 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 75ca23191..51924554d 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -50,6 +50,11 @@ function Wallet(opts) { this.registeredPeerIds = []; this.addressBook = opts.addressBook || {}; this.publicKey = this.privateKey.publicHex; + + //network nonces are 8 byte buffers, representing a big endian number + //one nonce for oneself, and then one nonce for each copayer + this.network.setHexNonce(opts.networkNonce); + this.network.setHexNonces(opts.networkNonces); } Wallet.parent = EventEmitter; @@ -343,8 +348,14 @@ Wallet.prototype.store = function() { Wallet.prototype.toObj = function() { var optsObj = this._optsToObj(); + + var networkNonce = this.network.getHexNonce(); + var networkNonces = this.network.getHexNonces(); + var walletObj = { opts: optsObj, + networkNonce: networkNonce, //yours + networkNonces: networkNonces, //copayers publicKeyRing: this.publicKeyRing.toObj(), txProposals: this.txProposals.toObj(), privateKey: this.privateKey ? this.privateKey.toObj() : undefined, @@ -374,6 +385,10 @@ Wallet.prototype.toEncryptedObj = function() { return this.storage.export(walletObj); }; +Wallet.prototype.send = function(recipients, obj) { + this.network.send(recipients, obj); +}; + Wallet.prototype.sendAllTxProposals = function(recipients) { var ntxids = this.txProposals.getNtxids(); for (var i in ntxids) { @@ -386,7 +401,7 @@ Wallet.prototype.sendTxProposal = function(ntxid, recipients) { preconditions.checkArgument(ntxid); preconditions.checkState(this.txProposals.txps[ntxid]); this.log('### SENDING txProposal ' + ntxid + ' TO:', recipients || 'All', this.txProposals); - this.network.send(recipients, { + this.send(recipients, { type: 'txProposal', txProposal: this.txProposals.txps[ntxid].toObj(), walletId: this.id, @@ -396,7 +411,7 @@ Wallet.prototype.sendTxProposal = function(ntxid, recipients) { Wallet.prototype.sendWalletReady = function(recipients) { this.log('### SENDING WalletReady TO:', recipients); - this.network.send(recipients, { + this.send(recipients, { type: 'walletReady', walletId: this.id, }); @@ -405,7 +420,7 @@ Wallet.prototype.sendWalletReady = function(recipients) { Wallet.prototype.sendWalletId = function(recipients) { this.log('### SENDING walletId TO:', recipients || 'All', this.id); - this.network.send(recipients, { + this.send(recipients, { type: 'walletId', walletId: this.id, opts: this._optsToObj(), @@ -419,7 +434,7 @@ Wallet.prototype.sendPublicKeyRing = function(recipients) { var publicKeyRing = this.publicKeyRing.toObj(); delete publicKeyRing.publicKeysCache; // exclude publicKeysCache from network obj - this.network.send(recipients, { + this.send(recipients, { type: 'publicKeyRing', publicKeyRing: publicKeyRing, walletId: this.id, @@ -429,7 +444,7 @@ Wallet.prototype.sendIndexes = function(recipients) { var indexes = AddressIndex.serialize(this.publicKeyRing.indexes); this.log('### INDEXES TO:', recipients || 'All', indexes); - this.network.send(recipients, { + this.send(recipients, { type: 'indexes', indexes: indexes, walletId: this.id, @@ -438,7 +453,7 @@ Wallet.prototype.sendIndexes = function(recipients) { Wallet.prototype.sendAddressBook = function(recipients) { this.log('### SENDING addressBook TO:', recipients || 'All', this.addressBook); - this.network.send(recipients, { + this.send(recipients, { type: 'addressbook', addressBook: this.addressBook, walletId: this.id, diff --git a/js/models/network/WebRTC.js b/js/models/network/WebRTC.js index 462c258a5..ea88c890a 100644 --- a/js/models/network/WebRTC.js +++ b/js/models/network/WebRTC.js @@ -163,6 +163,57 @@ Network.prototype.getKey = function() { return this.key; }; +//hex version of one's own nonce +Network.prototype.setHexNonce = function(networkNonce) { + if (networkNonce) { + if (networkNonce.length !== 16) + throw new Error('incorrect length of hex nonce'); + this.networkNonce = new Buffer(networkNonce, 'hex'); + } + else + this.iterateNonce(); +}; + +//hex version of copayers' nonces +Network.prototype.setHexNonces = function(networkNonces) { + for (var i in networkNonces) { + if (!this.networkNonces) + this.networkNonces = {}; + if (networkNonces[i].length === 16) + this.networkNonces[i] = new Buffer(networkNonces[i], 'hex'); + } +}; + +//for oneself +Network.prototype.getHexNonce = function() { + return this.networkNonce.toString('hex'); +}; + +//for copayers +Network.prototype.getHexNonces = function() { + var networkNoncesHex = []; + for (var i in this.networkNonces) { + networkNoncesHex[i] = this.networkNonces[i].toString('hex'); + } + return networkNoncesHex; +}; + +Network.prototype.iterateNonce = function() { + if (!this.networkNonce || this.networkNonce.length !== 8) { + this.networkNonce = new Buffer(8); + this.networkNonce.fill(0); + } + //the first 4 bytes of a nonce is a unix timestamp in seconds + //the second 4 bytes is just an iterated "sub" nonce + //the whole thing is interpreted as one big endian number + var noncep1 = this.networkNonce.slice(0, 4); + noncep1.writeUInt32BE(Math.floor(Date.now()/1000), 0); + var noncep2uint = this.networkNonce.slice(4, 8).readUInt32BE(0); + var noncep2 = this.networkNonce.slice(4, 8); + noncep2.writeUInt32BE(noncep2uint + 1, 0); + return this.networkNonce; +}; + Network.prototype._onData = function(enc, isInbound, peerId) { var encUint8Array = new Uint8Array(enc); var encbuf = new Buffer(encUint8Array); @@ -173,7 +224,16 @@ Network.prototype._onData = function(enc, isInbound, peerId) { try { var encoded = JSON.parse(encstr); - var databuf = this._decode(key, encoded); + var prevnonce = this.networkNonces ? this.networkNonces[peerId] : undefined; + var opts = {prevnonce: prevnonce}; + var decoded = this._decode(key, encoded, opts); + + //if no error thrown in the last step, we can set the copayer's nonce + if (!this.networkNonces) + this.networkNonces = {}; + this.networkNonces[peerId] = decoded.nonce; + + var databuf = decoded.payload; var datastr = databuf.toString(); var payload = JSON.parse(datastr); } catch (e) { @@ -384,13 +444,13 @@ Network.prototype._encode = function(topubkey, fromkey, payload, opts) { Network.prototype._decode = function(key, encoded, opts) { var decoded = Message.decode(key, encoded, opts); - var payload = decoded.payload; - return payload; + return decoded; }; -Network.prototype._sendToOne = function(copayerId, payload, opts, cb) { +Network.prototype._sendToOne = function(copayerId, payload, cb) { if (!Buffer.isBuffer(payload)) throw new Error('payload must be a buffer'); + var peerId = this.peerFromCopayer(copayerId); if (peerId !== this.peerId) { var dataConn = this.connections[peerId]; @@ -401,7 +461,7 @@ Network.prototype._sendToOne = function(copayerId, payload, opts, cb) { if (typeof cb === 'function') cb(); }; -Network.prototype.send = function(copayerIds, payload, opts, cb) { +Network.prototype.send = function(copayerIds, payload, cb) { if (!payload) return cb(); var self = this; @@ -419,10 +479,12 @@ Network.prototype.send = function(copayerIds, payload, opts, cb) { var l = copayerIds.length; var i = 0; copayerIds.forEach(function(copayerId) { + self.iterateNonce(); + var opts = {nonce: self.networkNonce}; var copayerIdBuf = new Buffer(copayerId, 'hex'); - var encPayload = self._encode(copayerIdBuf, self.getKey(), payloadBuf); + var encPayload = self._encode(copayerIdBuf, self.getKey(), payloadBuf, opts); var enc = new Buffer(JSON.stringify(encPayload)); - self._sendToOne(copayerId, enc, opts, function() { + self._sendToOne(copayerId, enc, function() { if (++i === l && typeof cb === 'function') cb(); }); }); diff --git a/test/mocks/FakeNetwork.js b/test/mocks/FakeNetwork.js index 68b8cb528..4bdf68487 100644 --- a/test/mocks/FakeNetwork.js +++ b/test/mocks/FakeNetwork.js @@ -41,4 +41,55 @@ Network.prototype.peerFromCopayer = function(copayerId) { return copayerId; }; +//hex version of one's own nonce +Network.prototype.setHexNonce = function(networkNonce) { + if (networkNonce && networkNonce.length === 16) + this.networkNonce = new Buffer(networkNonce, 'hex'); + else + this.iterateNonce(); +}; + +//hex version of copayers' nonces +Network.prototype.setHexNonces = function(networkNonces) { + for (var i in networkNonces) { + if (!this.networkNonces) + this.networkNonces = {}; + if (networkNonces[i].length === 16) + this.networkNonces[i] = new Buffer(networkNonces[i], 'hex'); + } +}; + +//for oneself +Network.prototype.getHexNonce = function() { + return this.networkNonce.toString('hex'); +}; + +//for copayers +Network.prototype.getHexNonces = function() { + var networkNoncesHex = []; + for (var i in this.networkNonces) { + networkNoncesHex = this.networkNonces[i].toString('hex'); + } + return networkNoncesHex; +}; + +Network.prototype.iterateNonce = function() { + if (!this.networkNonce || this.networkNonce.length !== 8) { + this.networkNonce = new Buffer(8); + this.networkNonce.fill(0); + this.networkNonce[7] = 1; + return this.networkNonce; + } + //the first 4 bytes of a nonce is a unix timestamp in seconds + //the second 4 bytes is just an iterated "sub" nonce + //the whole thing is interpreted as one big endian number + var noncep1 = this.networkNonce.slice(0, 4); + noncep1.writeUInt32BE(Math.floor(Date.now()/1000), 0); + var noncep2uint = this.networkNonce.slice(4, 8).readUInt32BE(0); + var noncep2 = this.networkNonce.slice(4, 8); + noncep2.writeUInt32BE(noncep2uint + 1, 0); + return this.networkNonce; +}; + + module.exports = require('soop')(Network); diff --git a/test/test.Wallet.js b/test/test.Wallet.js index fdd85ed50..ac8e400aa 100644 --- a/test/test.Wallet.js +++ b/test/test.Wallet.js @@ -687,6 +687,7 @@ describe('Wallet model', function() { done(); }); }); + it('should send all TxProposal', function(done) { var w = cachedCreateW2(); var utxo = createUTXO(w); @@ -700,6 +701,16 @@ describe('Wallet model', function() { }); }); + describe('#send', function() { + it('should call this.network.send', function () { + var w = cachedCreateW2(); + var save = w.network.send; + w.network.send = sinon.spy(); + w.send(); + w.network.send.calledOnce.should.equal(true); + w.network.send = save; + }); + }); describe('#indexDiscovery', function() { var ADDRESSES_CHANGE, ADDRESSES_RECEIVE, w; diff --git a/test/test.WalletFactory.js b/test/test.WalletFactory.js index 5a1bec35f..ef27ea3bc 100644 --- a/test/test.WalletFactory.js +++ b/test/test.WalletFactory.js @@ -11,7 +11,7 @@ var FakeBlockchain = require('./mocks/FakeBlockchain'); var FakeStorage = require('./mocks/FakeStorage'); var WalletFactory = require('../js/models/core/WalletFactory'); -var o = '{"opts":{"id":"dbfe10c3fae71cea","spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5"},"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":[{"cosigner":2,"changeIndex":0,"receiveIndex":0}],"copayersBackup":[],"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":{}},"addressBook":{}}'; +var o = '{"opts":{"id":"dbfe10c3fae71cea","spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5"},"networkNonce":"0000000000000001","networkNonces":[],"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":[{"cosigner":2,"changeIndex":0,"receiveIndex":0}],"copayersBackup":[],"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":{}},"addressBook":{}}'; describe('WalletFactory model', function() { var config = { @@ -97,8 +97,8 @@ describe('WalletFactory model', function() { }); it('support old index schema: #fromObj #toObj round trip', function() { - var o = '{"opts":{"id":"dbfe10c3fae71cea","spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5"},"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":{"changeIndex":0,"receiveIndex":0},"copayersBackup":[],"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":{}},"addressBook":{}}'; - var o2 = '{"opts":{"id":"dbfe10c3fae71cea","spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5"},"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":[{"cosigner":2147483647,"changeIndex":0,"receiveIndex":0},{"cosigner":0,"changeIndex":0,"receiveIndex":0},{"cosigner":1,"changeIndex":0,"receiveIndex":0},{"cosigner":2,"changeIndex":0,"receiveIndex":0},{"cosigner":3,"changeIndex":0,"receiveIndex":0},{"cosigner":4,"changeIndex":0,"receiveIndex":0}],"copayersBackup":[],"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":{}},"addressBook":{}}'; + var o = '{"opts":{"id":"dbfe10c3fae71cea","spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5"},"networkNonce":"0000000000000001","networkNonces":[],"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":{"changeIndex":0,"receiveIndex":0},"copayersBackup":[],"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":{}},"addressBook":{}}'; + var o2 = '{"opts":{"id":"dbfe10c3fae71cea","spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5"},"networkNonce":"0000000000000001","networkNonces":[],"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":[{"cosigner":2147483647,"changeIndex":0,"receiveIndex":0},{"cosigner":0,"changeIndex":0,"receiveIndex":0},{"cosigner":1,"changeIndex":0,"receiveIndex":0},{"cosigner":2,"changeIndex":0,"receiveIndex":0},{"cosigner":3,"changeIndex":0,"receiveIndex":0},{"cosigner":4,"changeIndex":0,"receiveIndex":0}],"copayersBackup":[],"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":{}},"addressBook":{}}'; var wf = new WalletFactory(config, '0.0.5'); var w = wf.fromObj(JSON.parse(o)); diff --git a/test/test.network.WebRTC.js b/test/test.network.WebRTC.js index 24f414c12..8231991dc 100644 --- a/test/test.network.WebRTC.js +++ b/test/test.network.WebRTC.js @@ -148,7 +148,7 @@ describe('Network / WebRTC', function() { var encoded = n._encode(key.public, key, data); var decoded = n._decode(key, encoded); encoded.sig.should.not.equal(0); - decoded.toString().should.equal('my data to encrypt'); + decoded.payload.toString().should.equal('my data to encrypt'); }); }); @@ -168,11 +168,11 @@ describe('Network / WebRTC', function() { key.regenerateSync(); var copayerId = key.public.toString('hex'); - n._sendToOne = function(a1, a2, a3, cb) { + n._sendToOne = function(a1, a2, cb) { cb(); }; var opts = {}; - n.send(copayerId, data, opts, function() { + n.send(copayerId, data, function() { done(); }); @@ -191,13 +191,13 @@ describe('Network / WebRTC', function() { key.regenerateSync(); var copayerId = key.public.toString('hex'); - n._sendToOne = function(a1, enc, opts, cb) { + n._sendToOne = function(a1, enc, cb) { var encPayload = JSON.parse(enc.toString()); encPayload.sig.length.should.be.greaterThan(0); cb(); }; var opts = {}; - n.send(copayerId, data, opts, function() { + n.send(copayerId, data, function() { done(); }); @@ -216,11 +216,11 @@ describe('Network / WebRTC', function() { key.regenerateSync(); var copayerIds = [key.public.toString('hex')]; - n._sendToOne = function(a1, a2, a3, cb) { + n._sendToOne = function(a1, a2, cb) { cb(); }; var opts = {}; - n.send(copayerIds, data, opts, function() { + n.send(copayerIds, data, function() { done(); }); @@ -294,6 +294,214 @@ describe('Network / WebRTC', function() { n._deletePeer.getCall(0).args[1].should.equal('incorrect pubkey for peerId'); }); + it('should not reject data sent from a peer with no previously set nonce but who is setting one now', function() { + var n = new WebRTC(); + n.privkey = key2.private.toString('hex'); + //n.networkNonces = {}; + //n.networkNonces[(new bitcore.SIN(key1.public)).toString()] = new Buffer('0000000000000001', 'hex'); //previously used nonce + + var message = { + type: 'hello', + copayerId: key1.public.toString('hex') + }; + var messagestr = JSON.stringify(message); + var messagebuf = new Buffer(messagestr); + + var opts = {nonce: new Buffer('0000000000000001', 'hex')}; //message send with new nonce + var encoded = n._encode(key2.public, key1, messagebuf, opts); + var encodedstr = JSON.stringify(encoded); + var encodeduint = new Buffer(encodedstr); + + var isInbound = true; + var peerId = new bitcore.SIN(key1.public); + + n._deletePeer = sinon.spy(); + + n._onData(encodeduint, isInbound, peerId); + n._deletePeer.calledOnce.should.equal(false); + n.getHexNonces()[(new bitcore.SIN(key1.public)).toString()].toString('hex').should.equal('0000000000000001'); + }); + + it('should not reject data sent from a peer with a really big new nonce', function() { + var n = new WebRTC(); + n.privkey = key2.private.toString('hex'); + n.networkNonces = {}; + n.networkNonces[(new bitcore.SIN(key1.public)).toString()] = new Buffer('5000000000000001', 'hex'); //previously used nonce + + var message = { + type: 'hello', + copayerId: key1.public.toString('hex') + }; + var messagestr = JSON.stringify(message); + var messagebuf = new Buffer(messagestr); + + var opts = {nonce: new Buffer('5000000000000002', 'hex')}; //message send with new nonce + var encoded = n._encode(key2.public, key1, messagebuf, opts); + var encodedstr = JSON.stringify(encoded); + var encodeduint = new Buffer(encodedstr); + + var isInbound = true; + var peerId = new bitcore.SIN(key1.public); + + n._deletePeer = sinon.spy(); + + n._onData(encodeduint, isInbound, peerId); + n._deletePeer.calledOnce.should.equal(false); + }); + + it('should not reject data sent from a peer with a really big new nonce', function() { + var n = new WebRTC(); + n.privkey = key2.private.toString('hex'); + n.networkNonces = {}; + n.networkNonces[(new bitcore.SIN(key1.public)).toString()] = new Buffer('5000000000000001', 'hex'); //previously used nonce + + var message = { + type: 'hello', + copayerId: key1.public.toString('hex') + }; + var messagestr = JSON.stringify(message); + var messagebuf = new Buffer(messagestr); + + var opts = {nonce: new Buffer('5000000000000002', 'hex')}; //message send with new nonce + var encoded = n._encode(key2.public, key1, messagebuf, opts); + var encodedstr = JSON.stringify(encoded); + var encodeduint = new Buffer(encodedstr); + + var isInbound = true; + var peerId = new bitcore.SIN(key1.public); + + n._deletePeer = sinon.spy(); + + n._onData(encodeduint, isInbound, peerId); + n._deletePeer.calledOnce.should.equal(false); + }); + + it('should reject data sent from a peer with an outdated nonce', function() { + var n = new WebRTC(); + n.privkey = key2.private.toString('hex'); + n.networkNonces = {}; + n.networkNonces[(new bitcore.SIN(key1.public)).toString()] = new Buffer('0000000000000002', 'hex'); //previously used nonce + + var message = { + type: 'hello', + copayerId: key1.public.toString('hex') + }; + var messagestr = JSON.stringify(message); + var messagebuf = new Buffer(messagestr); + + var opts = {nonce: new Buffer('0000000000000001', 'hex')}; //message send with old nonce + var encoded = n._encode(key2.public, key1, messagebuf, opts); + var encodedstr = JSON.stringify(encoded); + var encodeduint = new Buffer(encodedstr); + + var isInbound = true; + var peerId = new bitcore.SIN(key1.public); + + n._deletePeer = sinon.spy(); + + n._onData(encodeduint, isInbound, peerId); + n._deletePeer.calledOnce.should.equal(true); + }); + + it('should reject data sent from a peer with a really big outdated nonce', function() { + var n = new WebRTC(); + n.privkey = key2.private.toString('hex'); + n.networkNonces = {}; + n.networkNonces[(new bitcore.SIN(key1.public)).toString()] = new Buffer('5000000000000002', 'hex'); //previously used nonce + + var message = { + type: 'hello', + copayerId: key1.public.toString('hex') + }; + var messagestr = JSON.stringify(message); + var messagebuf = new Buffer(messagestr); + + var opts = {nonce: new Buffer('5000000000000001', 'hex')}; //message send with old nonce + var encoded = n._encode(key2.public, key1, messagebuf, opts); + var encodedstr = JSON.stringify(encoded); + var encodeduint = new Buffer(encodedstr); + + var isInbound = true; + var peerId = new bitcore.SIN(key1.public); + + n._deletePeer = sinon.spy(); + + n._onData(encodeduint, isInbound, peerId); + n._deletePeer.calledOnce.should.equal(true); + }); + + }); + + describe('#setHexNonce', function() { + + it('should set a nonce from a hex value', function() { + var hex = '0000000000000000'; + var n = new WebRTC(); + n.setHexNonce(hex); + n.getHexNonce().should.equal(hex); + n.networkNonce.toString('hex').should.equal(hex); + }); + + }); + + describe('#setHexNonces', function() { + + it('should set a nonce from a hex value', function() { + var hex = '0000000000000000'; + var n = new WebRTC(); + n.setHexNonces({fakeid: hex}); + n.getHexNonces().fakeid.should.equal(hex); + }); + + }); + + describe('#getHexNonce', function() { + + it('should get a nonce hex value', function() { + var hex = '0000000000000000'; + var n = new WebRTC(); + n.setHexNonce(hex); + n.getHexNonce().should.equal(hex); + }); + + }); + + describe('#getHexNonces', function() { + + it('should get a nonce from a hex value', function() { + var hex = '0000000000000000'; + var n = new WebRTC(); + n.setHexNonces({fakeid: hex}); + n.getHexNonces().fakeid.should.equal(hex); + }); + + }); + + describe('#iterateNonce', function() { + + it('should set a nonce not already set', function() { + var n = new WebRTC(); + n.iterateNonce(); + n.networkNonce.slice(4, 8).toString('hex').should.equal('00000001'); + n.networkNonce.slice(0, 4).toString('hex').should.not.equal('00000000'); + }); + + it('called twice should increment', function() { + var n = new WebRTC(); + n.iterateNonce(); + n.networkNonce.slice(4, 8).toString('hex').should.equal('00000001'); + n.iterateNonce(); + n.networkNonce.slice(4, 8).toString('hex').should.equal('00000002'); + }); + + it('should set the first byte to the most significant "now" digit', function() { + var n = new WebRTC(); + n.iterateNonce(); + var buf = new Buffer(4); + buf.writeUInt32BE(Math.floor(Date.now()/1000), 0); + n.networkNonce[0].should.equal(buf[0]); + }); + }); }); From 643cad3a393b49ba839cd6daac543e25f370a57a Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Wed, 9 Jul 2014 01:13:42 -0700 Subject: [PATCH 3/3] change encryption to work on hex strings ..instead of binary, to work around an issue with bitcore/sjcl. I'm not sure what the issue is exactly, except that encryption of binary data isn't working correctly due to some kind of string stuff involving decodeURIComponent inside sjcl. I haven't fully figured it out. For now I am changing the network protocol to hex to workaround the issue. See this: https://github.com/bitpay/bitcore/pull/416 --- js/models/core/Message.js | 6 ++++-- test/test.Message.js | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/js/models/core/Message.js b/js/models/core/Message.js index 7c9de9c27..79b94d8e3 100644 --- a/js/models/core/Message.js +++ b/js/models/core/Message.js @@ -21,7 +21,8 @@ Message.encode = function(topubkey, fromkey, payload, opts) { } var toencrypt = Buffer.concat([version1, version2, nonce, payload]); - var encrypted = Message._encrypt(topubkey, toencrypt); + var toencrypthexbuf = new Buffer(toencrypt.toString('hex')); //due to bug in sjcl/bitcore, must use hex string + var encrypted = Message._encrypt(topubkey, toencrypthexbuf); var sig = Message._sign(fromkey, encrypted); var encoded = { pubkey: fromkey.public.toString('hex'), @@ -63,7 +64,8 @@ Message.decode = function(key, encoded, opts) { } try { - var decrypted = Message._decrypt(key.private, encrypted); + var decryptedhexbuf = Message._decrypt(key.private, encrypted); + var decrypted = new Buffer(decryptedhexbuf.toString(), 'hex'); //workaround for bug in bitcore/sjcl } catch (e) { throw new Error('Cannot decrypt data: ' + e); } diff --git a/test/test.Message.js b/test/test.Message.js index 10134b736..9bad687d1 100644 --- a/test/test.Message.js +++ b/test/test.Message.js @@ -100,7 +100,8 @@ describe('Message model', function() { var version2 = new Buffer([0]); var nonce = new Buffer([0, 0, 0, 0, 0, 0, 0, 0]); var toencrypt = Buffer.concat([version1, version2, nonce, payload]); - var encrypted = Message._encrypt(topubkey, toencrypt); + var toencrypt_workaround = new Buffer(toencrypt.toString('hex')); + var encrypted = Message._encrypt(topubkey, toencrypt_workaround); var sig = Message._sign(fromkey, encrypted); var encoded = { pubkey: fromkey.public.toString('hex'),