2014-04-10 13:57:41 -07:00
|
|
|
'use strict';
|
2014-04-24 16:56:36 -07:00
|
|
|
var imports = require('soop').imports();
|
2014-04-14 13:17:56 -07:00
|
|
|
|
2014-06-17 10:28:26 -07:00
|
|
|
var http = require('http');
|
|
|
|
var EventEmitter = imports.EventEmitter || require('events').EventEmitter;
|
|
|
|
var async = require('async');
|
2014-06-24 10:40:03 -07:00
|
|
|
var preconditions = require('preconditions').singleton();
|
2014-07-30 09:04:02 -07:00
|
|
|
var parseBitcoinURI = require('./HDPath').parseBitcoinURI;
|
2014-06-17 10:28:26 -07:00
|
|
|
|
2014-04-24 16:56:36 -07:00
|
|
|
var bitcore = require('bitcore');
|
2014-05-21 12:10:39 -07:00
|
|
|
var bignum = bitcore.Bignum;
|
2014-04-24 16:56:36 -07:00
|
|
|
var coinUtil = bitcore.util;
|
2014-04-14 14:30:08 -07:00
|
|
|
var buffertools = bitcore.buffertools;
|
2014-04-24 16:56:36 -07:00
|
|
|
var Builder = bitcore.TransactionBuilder;
|
2014-05-09 07:59:38 -07:00
|
|
|
var SecureRandom = bitcore.SecureRandom;
|
|
|
|
var Base58Check = bitcore.Base58.base58Check;
|
2014-07-04 07:40:07 -07:00
|
|
|
var Address = bitcore.Address;
|
2014-07-25 18:46:29 -07:00
|
|
|
var PayPro = bitcore.PayPro;
|
|
|
|
var Transaction = bitcore.Transaction;
|
2014-04-10 13:57:41 -07:00
|
|
|
|
2014-07-29 07:23:58 -07:00
|
|
|
var HDParams = require('./HDParams');
|
2014-06-17 10:28:26 -07:00
|
|
|
var PublicKeyRing = require('./PublicKeyRing');
|
2014-08-02 16:51:31 -07:00
|
|
|
var TxProposal = require('./TxProposal');
|
2014-07-31 21:09:46 -07:00
|
|
|
var TxProposals = require('./TxProposals');
|
2014-06-17 10:28:26 -07:00
|
|
|
var PrivateKey = require('./PrivateKey');
|
2014-07-08 08:34:49 -07:00
|
|
|
var copayConfig = require('../../../config');
|
2014-06-17 10:28:26 -07:00
|
|
|
|
2014-04-16 13:50:10 -07:00
|
|
|
function Wallet(opts) {
|
|
|
|
var self = this;
|
2014-04-14 13:17:56 -07:00
|
|
|
|
2014-04-16 13:50:10 -07:00
|
|
|
//required params
|
|
|
|
['storage', 'network', 'blockchain',
|
|
|
|
'requiredCopayers', 'totalCopayers', 'spendUnconfirmed',
|
2014-06-02 13:40:29 -07:00
|
|
|
'publicKeyRing', 'txProposals', 'privateKey', 'version',
|
|
|
|
'reconnectDelay'
|
2014-04-24 16:56:36 -07:00
|
|
|
].forEach(function(k) {
|
2014-05-21 07:10:19 -07:00
|
|
|
if (typeof opts[k] === 'undefined')
|
|
|
|
throw new Error('missing required option for Wallet: ' + k);
|
2014-04-16 13:50:10 -07:00
|
|
|
self[k] = opts[k];
|
|
|
|
});
|
2014-07-08 14:53:02 -07:00
|
|
|
if (copayConfig.forceNetwork && this.getNetworkName() !== copayConfig.networkName)
|
2014-07-24 17:18:38 -07:00
|
|
|
throw new Error('Network forced to ' + copayConfig.networkName +
|
2014-07-30 17:20:08 -07:00
|
|
|
' and tried to create a Wallet with network ' + this.getNetworkName());
|
2014-04-14 13:17:56 -07:00
|
|
|
|
2014-04-24 16:56:36 -07:00
|
|
|
this.log('creating ' + opts.requiredCopayers + ' of ' + opts.totalCopayers + ' wallet');
|
2014-04-16 15:45:22 -07:00
|
|
|
|
2014-04-16 13:50:10 -07:00
|
|
|
this.id = opts.id || Wallet.getRandomId();
|
2014-04-24 12:35:52 -07:00
|
|
|
this.name = opts.name;
|
2014-04-30 08:58:40 -07:00
|
|
|
|
2014-08-12 12:26:15 -07:00
|
|
|
this.ignoreLock = opts.ignoreLock;
|
2014-04-16 18:12:30 -07:00
|
|
|
this.verbose = opts.verbose;
|
2014-04-16 13:50:10 -07:00
|
|
|
this.publicKeyRing.walletId = this.id;
|
|
|
|
this.txProposals.walletId = this.id;
|
2014-04-24 19:13:55 -07:00
|
|
|
this.network.maxPeers = this.totalCopayers;
|
2014-05-01 05:41:18 -07:00
|
|
|
this.registeredPeerIds = [];
|
2014-06-18 16:18:13 -07:00
|
|
|
this.addressBook = opts.addressBook || {};
|
2014-07-03 07:18:01 -07:00
|
|
|
this.publicKey = this.privateKey.publicHex;
|
2014-07-08 23:03:30 -07:00
|
|
|
|
2014-07-31 12:23:06 -07:00
|
|
|
this.paymentRequests = opts.paymentRequests || {};
|
|
|
|
|
2014-07-08 23:03:30 -07:00
|
|
|
//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);
|
2014-04-14 13:42:10 -07:00
|
|
|
}
|
2014-04-10 13:57:41 -07:00
|
|
|
|
2014-07-25 13:45:56 -07:00
|
|
|
|
2014-08-03 18:34:47 -07:00
|
|
|
Wallet.builderOpts = {
|
2014-07-30 17:20:08 -07:00
|
|
|
lockTime: null,
|
|
|
|
signhash: bitcore.Transaction.SIGNHASH_ALL,
|
|
|
|
fee: null,
|
|
|
|
feeSat: null,
|
2014-07-25 13:45:56 -07:00
|
|
|
};
|
|
|
|
|
2014-04-24 16:56:36 -07:00
|
|
|
Wallet.parent = EventEmitter;
|
|
|
|
Wallet.prototype.log = function() {
|
2014-04-17 05:38:46 -07:00
|
|
|
if (!this.verbose) return;
|
2014-04-20 16:24:24 -07:00
|
|
|
if (console)
|
2014-04-24 16:56:36 -07:00
|
|
|
console.log.apply(console, arguments);
|
2014-04-16 13:50:10 -07:00
|
|
|
};
|
2014-04-15 10:28:49 -07:00
|
|
|
|
2014-04-16 13:50:10 -07:00
|
|
|
Wallet.getRandomId = function() {
|
2014-04-24 11:49:37 -07:00
|
|
|
var r = bitcore.SecureRandom.getPseudoRandomBuffer(8).toString('hex');
|
|
|
|
return r;
|
2014-04-16 13:50:10 -07:00
|
|
|
};
|
2014-04-15 10:28:49 -07:00
|
|
|
|
2014-05-09 10:35:57 -07:00
|
|
|
Wallet.prototype.seedCopayer = function(pubKey) {
|
|
|
|
this.seededCopayerId = pubKey;
|
|
|
|
};
|
|
|
|
|
2014-05-01 17:48:17 -07:00
|
|
|
Wallet.prototype.connectToAll = function() {
|
2014-05-16 21:19:52 -07:00
|
|
|
|
2014-05-01 17:48:17 -07:00
|
|
|
var all = this.publicKeyRing.getAllCopayerIds();
|
|
|
|
this.network.connectToCopayers(all);
|
2014-05-09 10:35:57 -07:00
|
|
|
if (this.seededCopayerId) {
|
|
|
|
this.sendWalletReady(this.seededCopayerId);
|
|
|
|
this.seededCopayerId = null;
|
2014-05-01 17:48:17 -07:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-08-12 12:26:15 -07:00
|
|
|
Wallet.prototype.getLock = function() {
|
|
|
|
return this.storage.getLock(this.id);
|
2014-08-07 14:57:19 -07:00
|
|
|
};
|
|
|
|
|
2014-08-12 12:26:15 -07:00
|
|
|
Wallet.prototype.setLock = function() {
|
|
|
|
return this.storage.setLock(this.id);
|
2014-08-07 14:57:19 -07:00
|
|
|
};
|
|
|
|
|
2014-08-12 12:26:15 -07:00
|
|
|
Wallet.prototype.unlock = function() {
|
|
|
|
this.storage.removeLock(this.id);
|
2014-08-07 14:57:19 -07:00
|
|
|
};
|
|
|
|
|
2014-08-12 12:26:15 -07:00
|
|
|
Wallet.prototype.checkAndLock = function() {
|
|
|
|
if (this.getLock()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.setLock();
|
|
|
|
return false;
|
2014-08-07 14:57:19 -07:00
|
|
|
};
|
|
|
|
|
2014-06-04 10:24:46 -07:00
|
|
|
Wallet.prototype._handleIndexes = function(senderId, data, isInbound) {
|
|
|
|
this.log('RECV INDEXES:', data);
|
2014-07-29 07:23:58 -07:00
|
|
|
var inIndexes = HDParams.fromList(data.indexes);
|
2014-07-01 05:41:28 -07:00
|
|
|
var hasChanged = this.publicKeyRing.mergeIndexes(inIndexes);
|
2014-06-04 10:24:46 -07:00
|
|
|
if (hasChanged) {
|
|
|
|
this.emit('publicKeyRingUpdated');
|
|
|
|
this.store();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-04-16 13:50:10 -07:00
|
|
|
Wallet.prototype._handlePublicKeyRing = function(senderId, data, isInbound) {
|
2014-04-24 16:56:36 -07:00
|
|
|
this.log('RECV PUBLICKEYRING:', data);
|
2014-04-16 13:50:10 -07:00
|
|
|
|
2014-06-17 10:28:26 -07:00
|
|
|
var inPKR = PublicKeyRing.fromObj(data.publicKeyRing);
|
2014-05-27 13:05:43 -07:00
|
|
|
var wasIncomplete = !this.publicKeyRing.isComplete();
|
2014-06-09 17:00:28 -07:00
|
|
|
var hasChanged;
|
|
|
|
|
2014-06-13 07:30:48 -07:00
|
|
|
try {
|
2014-06-09 17:00:28 -07:00
|
|
|
hasChanged = this.publicKeyRing.merge(inPKR, true);
|
2014-06-13 07:30:48 -07:00
|
|
|
} catch (e) {
|
2014-08-13 07:16:07 -07:00
|
|
|
this.log('## WALLET ERROR', e);
|
2014-06-09 17:00:28 -07:00
|
|
|
this.emit('connectionError', e.message);
|
|
|
|
return;
|
|
|
|
}
|
2014-04-18 14:25:51 -07:00
|
|
|
|
2014-04-24 16:56:36 -07:00
|
|
|
if (hasChanged) {
|
2014-05-27 13:05:43 -07:00
|
|
|
if (wasIncomplete) {
|
|
|
|
this.sendPublicKeyRing();
|
|
|
|
}
|
2014-04-24 19:13:55 -07:00
|
|
|
if (this.publicKeyRing.isComplete()) {
|
|
|
|
this._lockIncomming();
|
|
|
|
}
|
2014-06-04 10:24:46 -07:00
|
|
|
this.emit('publicKeyRingUpdated');
|
|
|
|
this.store();
|
2014-04-16 13:50:10 -07:00
|
|
|
}
|
2014-04-14 13:42:10 -07:00
|
|
|
};
|
2014-04-14 13:17:56 -07:00
|
|
|
|
2014-04-14 14:30:08 -07:00
|
|
|
|
2014-08-03 19:57:23 -07:00
|
|
|
Wallet.prototype._processProposalEvents = function(senderId, m) {
|
|
|
|
var ev;
|
|
|
|
if (m) {
|
|
|
|
if (m.new) {
|
2014-08-03 18:34:47 -07:00
|
|
|
ev = {
|
|
|
|
type: 'new',
|
|
|
|
cid: senderId
|
|
|
|
}
|
2014-08-05 12:25:02 -07:00
|
|
|
} else if (m.newCopayer) {
|
|
|
|
ev = {
|
2014-08-03 19:57:23 -07:00
|
|
|
type: 'signed',
|
2014-08-05 12:25:02 -07:00
|
|
|
cid: m.newCopayer
|
2014-08-03 19:57:23 -07:00
|
|
|
};
|
2014-08-02 16:51:31 -07:00
|
|
|
}
|
|
|
|
} else {
|
2014-08-03 18:34:47 -07:00
|
|
|
ev = {
|
|
|
|
type: 'corrupt',
|
|
|
|
cId: senderId,
|
|
|
|
};
|
|
|
|
}
|
2014-08-03 19:57:23 -07:00
|
|
|
|
2014-08-03 18:34:47 -07:00
|
|
|
if (ev)
|
|
|
|
this.emit('txProposalEvent', ev);
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* OTDO
|
|
|
|
events.push({
|
|
|
|
type: 'signed',
|
|
|
|
cId: k,
|
|
|
|
txId: ntxid
|
|
|
|
});
|
|
|
|
*/
|
2014-08-04 08:43:21 -07:00
|
|
|
Wallet.prototype._getKeyMap = function(txp) {
|
2014-08-04 13:12:53 -07:00
|
|
|
preconditions.checkArgument(txp);
|
2014-08-03 18:34:47 -07:00
|
|
|
|
2014-08-04 13:12:53 -07:00
|
|
|
var keyMap = this.publicKeyRing.copayersForPubkeys(txp._inputSignatures[0], txp.inputChainPaths);
|
2014-08-04 08:43:21 -07:00
|
|
|
|
|
|
|
var inSig = JSON.stringify(txp._inputSignatures[0].sort());
|
|
|
|
|
|
|
|
if (JSON.stringify(Object.keys(keyMap).sort()) !== inSig) {
|
|
|
|
throw new Error('inputSignatures dont match know copayers pubkeys');
|
|
|
|
}
|
2014-08-03 18:34:47 -07:00
|
|
|
|
|
|
|
var keyMapStr = JSON.stringify(keyMap);
|
|
|
|
// All inputs must be signed with the same copayers
|
2014-08-04 08:43:21 -07:00
|
|
|
for (var i in txp._inputSignatures) {
|
2014-08-03 18:34:47 -07:00
|
|
|
if (!i) continue;
|
2014-08-04 08:43:21 -07:00
|
|
|
var inSigX = JSON.stringify(txp._inputSignatures[i].sort());
|
|
|
|
if (inSigX !== inSig)
|
|
|
|
throw new Error('found inputs with different signatures:');
|
2014-08-02 16:51:31 -07:00
|
|
|
}
|
2014-08-04 08:43:21 -07:00
|
|
|
return keyMap;
|
2014-08-02 16:51:31 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2014-08-05 12:25:02 -07:00
|
|
|
Wallet.prototype._checkSentTx = function(ntxid, cb) {
|
|
|
|
var txp = this.txProposals.get(ntxid);
|
|
|
|
var tx = txp.builder.build();
|
|
|
|
|
|
|
|
this.blockchain.checkSentTx(tx, function(err, txid) {
|
|
|
|
var ret = false;
|
|
|
|
if (txid) {
|
|
|
|
txp.setSent(txid);
|
|
|
|
ret = txid;
|
|
|
|
}
|
|
|
|
return cb(ret);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2014-06-17 13:43:06 -07:00
|
|
|
Wallet.prototype._handleTxProposal = function(senderId, data) {
|
2014-08-05 12:25:02 -07:00
|
|
|
var self = this;
|
2014-07-25 13:45:56 -07:00
|
|
|
this.log('RECV TXPROPOSAL: ', data);
|
2014-08-03 18:34:47 -07:00
|
|
|
var m;
|
2014-08-02 16:51:31 -07:00
|
|
|
|
2014-07-30 17:20:08 -07:00
|
|
|
try {
|
2014-08-05 12:25:02 -07:00
|
|
|
m = this.txProposals.merge(data.txProposal, Wallet.builderOpts);
|
|
|
|
var keyMap = this._getKeyMap(m.txp);
|
|
|
|
ret.newCopayer = m.txp.setCopayers(senderId, keyMap);
|
2014-08-03 18:34:47 -07:00
|
|
|
|
2014-07-30 17:20:08 -07:00
|
|
|
} catch (e) {
|
2014-08-04 13:12:53 -07:00
|
|
|
this.log('Corrupt TX proposal received from:', senderId, e);
|
2014-07-24 06:42:47 -07:00
|
|
|
}
|
2014-06-17 13:43:06 -07:00
|
|
|
|
2014-08-03 18:34:47 -07:00
|
|
|
if (m) {
|
2014-08-05 12:25:02 -07:00
|
|
|
|
|
|
|
if (m.hasChanged) {
|
|
|
|
this.sendSeen(m.ntxid);
|
|
|
|
var tx = m.txp.builder.build();
|
|
|
|
if (tx.isComplete()) {
|
|
|
|
this._checkSentTx(m.ntxid, function(ret) {
|
|
|
|
if (ret) {
|
|
|
|
self.emit('txProposalsUpdated');
|
|
|
|
self.store();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
this.sendTxProposal(m.ntxid);
|
|
|
|
}
|
|
|
|
}
|
2014-08-03 18:34:47 -07:00
|
|
|
this.emit('txProposalsUpdated');
|
|
|
|
this.store();
|
|
|
|
}
|
|
|
|
this._processProposalEvents(senderId, m);
|
2014-08-02 16:51:31 -07:00
|
|
|
};
|
2014-05-15 13:43:41 -07:00
|
|
|
|
2014-08-02 16:51:31 -07:00
|
|
|
|
|
|
|
Wallet.prototype._handleReject = function(senderId, data, isInbound) {
|
2014-08-04 08:43:21 -07:00
|
|
|
preconditions.checkState(data.ntxid);
|
2014-08-02 16:51:31 -07:00
|
|
|
this.log('RECV REJECT:', data);
|
2014-08-03 18:34:47 -07:00
|
|
|
|
2014-08-05 12:25:02 -07:00
|
|
|
var txp = this.txProposals.get(data.ntxid);
|
2014-08-03 18:34:47 -07:00
|
|
|
|
|
|
|
if (!txp)
|
2014-08-04 08:43:21 -07:00
|
|
|
throw new Error('Received Reject for an unknown TX from:' + senderId);
|
2014-08-03 18:34:47 -07:00
|
|
|
|
|
|
|
if (txp.signedBy[senderId])
|
|
|
|
throw new Error('Received Reject for an already signed TX from:' + senderId);
|
|
|
|
|
|
|
|
txp.setRejected(senderId);
|
2014-08-02 16:51:31 -07:00
|
|
|
this.store();
|
|
|
|
|
2014-08-03 18:34:47 -07:00
|
|
|
this.emit('txProposalsUpdated');
|
|
|
|
this.emit('txProposalEvent', {
|
|
|
|
type: 'rejected',
|
|
|
|
cId: senderId,
|
|
|
|
txId: data.ntxid,
|
|
|
|
});
|
2014-08-02 16:51:31 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
Wallet.prototype._handleSeen = function(senderId, data, isInbound) {
|
2014-08-04 08:43:21 -07:00
|
|
|
preconditions.checkState(data.ntxid);
|
2014-08-02 16:51:31 -07:00
|
|
|
this.log('RECV SEEN:', data);
|
2014-08-04 08:43:21 -07:00
|
|
|
|
2014-08-05 12:25:02 -07:00
|
|
|
var txp = this.txProposals.get(data.ntxid);
|
2014-08-04 08:43:21 -07:00
|
|
|
txp.setSeen(senderId);
|
2014-08-02 16:51:31 -07:00
|
|
|
this.store();
|
2014-08-03 18:34:47 -07:00
|
|
|
this.emit('txProposalsUpdated');
|
|
|
|
this.emit('txProposalEvent', {
|
|
|
|
type: 'seen',
|
|
|
|
cId: senderId,
|
|
|
|
txId: data.ntxid,
|
|
|
|
});
|
|
|
|
|
2014-04-14 13:42:10 -07:00
|
|
|
};
|
|
|
|
|
2014-08-02 16:51:31 -07:00
|
|
|
|
|
|
|
|
2014-06-18 16:18:13 -07:00
|
|
|
Wallet.prototype._handleAddressBook = function(senderId, data, isInbound) {
|
2014-08-04 08:43:21 -07:00
|
|
|
preconditions.checkState(data.addressBook);
|
2014-06-18 16:18:13 -07:00
|
|
|
this.log('RECV ADDRESSBOOK:', data);
|
|
|
|
var rcv = data.addressBook;
|
|
|
|
var hasChange;
|
2014-06-24 08:36:32 -07:00
|
|
|
for (var key in rcv) {
|
2014-06-18 16:18:13 -07:00
|
|
|
if (!this.addressBook[key]) {
|
2014-07-07 16:01:50 -07:00
|
|
|
var isVerified = this.verifyAddressbookEntry(rcv[key], senderId, key);
|
|
|
|
if (isVerified) {
|
|
|
|
this.addressBook[key] = rcv[key];
|
|
|
|
hasChange = true;
|
|
|
|
}
|
2014-06-18 16:18:13 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (hasChange) {
|
|
|
|
this.emit('addressBookUpdated');
|
|
|
|
this.store();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-04-16 13:50:10 -07:00
|
|
|
Wallet.prototype._handleData = function(senderId, data, isInbound) {
|
2014-06-09 12:17:00 -07:00
|
|
|
|
2014-04-20 12:16:09 -07:00
|
|
|
// TODO check message signature
|
2014-06-13 07:30:48 -07:00
|
|
|
|
2014-06-09 12:17:00 -07:00
|
|
|
if (data.type !== 'walletId' && this.id !== data.walletId) {
|
2014-04-24 16:56:36 -07:00
|
|
|
this.emit('badMessage', senderId);
|
2014-08-13 07:16:07 -07:00
|
|
|
this.log('badMessage FROM:', senderId);
|
2014-04-17 12:27:15 -07:00
|
|
|
return;
|
|
|
|
}
|
2014-08-13 07:16:07 -07:00
|
|
|
|
2014-04-24 16:56:36 -07:00
|
|
|
switch (data.type) {
|
2014-04-20 13:16:21 -07:00
|
|
|
// This handler is repeaded on WalletFactory (#join). TODO
|
|
|
|
case 'walletId':
|
|
|
|
this.sendWalletReady(senderId);
|
2014-07-30 17:20:08 -07:00
|
|
|
break;
|
2014-04-20 12:16:09 -07:00
|
|
|
case 'walletReady':
|
|
|
|
this.sendPublicKeyRing(senderId);
|
2014-07-30 17:20:08 -07:00
|
|
|
this.sendAddressBook(senderId);
|
|
|
|
this.sendAllTxProposals(senderId); // send old txps
|
|
|
|
break;
|
2014-04-16 13:50:10 -07:00
|
|
|
case 'publicKeyRing':
|
|
|
|
this._handlePublicKeyRing(senderId, data, isInbound);
|
2014-07-30 17:20:08 -07:00
|
|
|
break;
|
2014-08-02 16:51:31 -07:00
|
|
|
case 'reject':
|
|
|
|
this._handleReject(senderId, data, isInbound);
|
2014-08-03 18:34:47 -07:00
|
|
|
break;
|
2014-08-02 16:51:31 -07:00
|
|
|
case 'seen':
|
2014-08-03 18:34:47 -07:00
|
|
|
this._handleSeen(senderId, data, isInbound);
|
|
|
|
break;
|
2014-06-17 13:43:06 -07:00
|
|
|
case 'txProposal':
|
|
|
|
this._handleTxProposal(senderId, data, isInbound);
|
2014-07-30 17:20:08 -07:00
|
|
|
break;
|
2014-06-04 10:24:46 -07:00
|
|
|
case 'indexes':
|
|
|
|
this._handleIndexes(senderId, data, isInbound);
|
2014-07-30 17:20:08 -07:00
|
|
|
break;
|
2014-06-18 16:18:13 -07:00
|
|
|
case 'addressbook':
|
|
|
|
this._handleAddressBook(senderId, data, isInbound);
|
2014-07-30 17:20:08 -07:00
|
|
|
break;
|
2014-04-16 13:50:10 -07:00
|
|
|
}
|
|
|
|
};
|
2014-04-14 13:42:10 -07:00
|
|
|
|
2014-05-09 10:35:57 -07:00
|
|
|
Wallet.prototype._handleConnect = function(newCopayerId) {
|
2014-04-23 12:02:23 -07:00
|
|
|
if (newCopayerId) {
|
2014-04-25 13:36:00 -07:00
|
|
|
this.log('#### Setting new COPAYER:', newCopayerId);
|
2014-04-23 09:44:20 -07:00
|
|
|
this.sendWalletId(newCopayerId);
|
2014-04-17 12:27:15 -07:00
|
|
|
}
|
2014-05-09 10:35:57 -07:00
|
|
|
var peerID = this.network.peerFromCopayer(newCopayerId)
|
|
|
|
this.emit('connect', peerID);
|
2014-04-14 13:42:10 -07:00
|
|
|
};
|
|
|
|
|
2014-05-09 10:44:05 -07:00
|
|
|
Wallet.prototype._handleDisconnect = function(peerID) {
|
2014-06-09 16:08:12 -07:00
|
|
|
this.currentDelay = null;
|
2014-05-09 10:44:05 -07:00
|
|
|
this.emit('disconnect', peerID);
|
2014-05-09 10:35:57 -07:00
|
|
|
};
|
2014-04-20 12:16:09 -07:00
|
|
|
|
2014-06-09 14:01:15 -07:00
|
|
|
|
|
|
|
Wallet.prototype.getNetworkName = function() {
|
|
|
|
return this.publicKeyRing.network.name;
|
|
|
|
};
|
|
|
|
|
2014-04-24 16:56:36 -07:00
|
|
|
Wallet.prototype._optsToObj = function() {
|
2014-04-17 14:02:20 -07:00
|
|
|
var obj = {
|
|
|
|
id: this.id,
|
|
|
|
spendUnconfirmed: this.spendUnconfirmed,
|
|
|
|
requiredCopayers: this.requiredCopayers,
|
|
|
|
totalCopayers: this.totalCopayers,
|
2014-04-24 12:35:52 -07:00
|
|
|
name: this.name,
|
2014-05-14 17:02:01 -07:00
|
|
|
version: this.version,
|
2014-04-17 14:02:20 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
return obj;
|
|
|
|
};
|
|
|
|
|
2014-04-18 10:40:16 -07:00
|
|
|
|
2014-04-23 09:44:20 -07:00
|
|
|
Wallet.prototype.getCopayerId = function(index) {
|
2014-04-21 08:10:34 -07:00
|
|
|
return this.publicKeyRing.getCopayerId(index || 0);
|
2014-04-20 08:41:28 -07:00
|
|
|
};
|
|
|
|
|
2014-04-18 14:25:51 -07:00
|
|
|
|
2014-04-23 09:44:20 -07:00
|
|
|
Wallet.prototype.getMyCopayerId = function() {
|
2014-06-19 11:03:31 -07:00
|
|
|
return this.getCopayerId(0); //copayer id is hex of a public key
|
2014-04-18 10:40:16 -07:00
|
|
|
};
|
|
|
|
|
2014-06-18 20:12:38 -07:00
|
|
|
Wallet.prototype.getMyCopayerIdPriv = function() {
|
2014-06-19 11:03:31 -07:00
|
|
|
return this.privateKey.getIdPriv(); //copayer idpriv is hex of a private key
|
2014-06-18 20:12:38 -07:00
|
|
|
};
|
|
|
|
|
2014-04-30 08:58:40 -07:00
|
|
|
Wallet.prototype.getSecret = function() {
|
2014-06-08 12:59:48 -07:00
|
|
|
var pubkeybuf = new Buffer(this.getMyCopayerId(), 'hex');
|
|
|
|
var str = Base58Check.encode(pubkeybuf);
|
2014-04-30 08:58:40 -07:00
|
|
|
return str;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
Wallet.decodeSecret = function(secretB) {
|
|
|
|
var secret = Base58Check.decode(secretB);
|
2014-05-09 07:59:38 -07:00
|
|
|
var pubKeyBuf = secret.slice(0, 33);
|
2014-04-30 08:58:40 -07:00
|
|
|
return {
|
2014-06-18 10:24:33 -07:00
|
|
|
pubKey: pubKeyBuf.toString('hex')
|
2014-04-30 08:58:40 -07:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-07-03 07:37:26 -07:00
|
|
|
|
2014-04-24 19:13:55 -07:00
|
|
|
Wallet.prototype._lockIncomming = function() {
|
|
|
|
this.network.lockIncommingConnections(this.publicKeyRing.getAllCopayerIds());
|
|
|
|
};
|
|
|
|
|
2014-06-19 11:03:31 -07:00
|
|
|
Wallet.prototype.netStart = function(callback) {
|
2014-04-16 13:50:10 -07:00
|
|
|
var self = this;
|
|
|
|
var net = this.network;
|
2014-08-07 14:57:19 -07:00
|
|
|
|
2014-08-12 12:26:15 -07:00
|
|
|
if (this.checkAndLock() && !this.ignoreLock) {
|
|
|
|
this.emit('locked');
|
|
|
|
return;
|
|
|
|
}
|
2014-08-07 14:57:19 -07:00
|
|
|
|
2014-04-17 12:50:48 -07:00
|
|
|
net.removeAllListeners();
|
2014-05-09 10:35:57 -07:00
|
|
|
net.on('connect', self._handleConnect.bind(self));
|
|
|
|
net.on('disconnect', self._handleDisconnect.bind(self));
|
2014-04-24 16:56:36 -07:00
|
|
|
net.on('data', self._handleData.bind(self));
|
2014-04-17 12:27:15 -07:00
|
|
|
net.on('close', function() {
|
|
|
|
self.emit('close');
|
|
|
|
});
|
2014-06-07 15:12:24 -07:00
|
|
|
net.on('serverError', function(msg) {
|
|
|
|
self.emit('serverError', msg);
|
2014-06-06 08:30:50 -07:00
|
|
|
});
|
2014-04-20 08:41:28 -07:00
|
|
|
|
2014-04-23 09:44:20 -07:00
|
|
|
var myId = self.getMyCopayerId();
|
2014-06-18 20:12:38 -07:00
|
|
|
var myIdPriv = self.getMyCopayerIdPriv();
|
2014-06-26 08:49:22 -07:00
|
|
|
|
2014-04-24 16:56:36 -07:00
|
|
|
var startOpts = {
|
2014-04-23 18:43:17 -07:00
|
|
|
copayerId: myId,
|
2014-06-18 20:12:38 -07:00
|
|
|
privkey: myIdPriv,
|
2014-06-18 10:24:33 -07:00
|
|
|
maxPeers: self.totalCopayers
|
2014-04-20 08:41:28 -07:00
|
|
|
};
|
2014-04-24 12:35:52 -07:00
|
|
|
|
2014-04-24 19:13:55 -07:00
|
|
|
if (this.publicKeyRing.isComplete()) {
|
|
|
|
this._lockIncomming();
|
|
|
|
}
|
2014-06-09 07:41:13 -07:00
|
|
|
|
2014-04-24 16:56:36 -07:00
|
|
|
net.start(startOpts, function() {
|
2014-05-09 08:14:57 -07:00
|
|
|
self.emit('ready', net.getPeer());
|
2014-06-03 07:54:07 -07:00
|
|
|
setTimeout(function() {
|
2014-05-16 21:19:52 -07:00
|
|
|
self.emit('publicKeyRingUpdated', true);
|
2014-06-02 13:40:29 -07:00
|
|
|
self.scheduleConnect();
|
2014-05-16 14:33:06 -07:00
|
|
|
self.emit('txProposalsUpdated');
|
2014-06-03 07:54:07 -07:00
|
|
|
}, 10);
|
2014-04-23 18:43:17 -07:00
|
|
|
});
|
2014-04-16 13:50:10 -07:00
|
|
|
};
|
2014-04-15 06:22:50 -07:00
|
|
|
|
2014-06-02 13:40:29 -07:00
|
|
|
Wallet.prototype.scheduleConnect = function() {
|
|
|
|
var self = this;
|
2014-06-03 07:54:07 -07:00
|
|
|
if (self.network.isOnline()) {
|
|
|
|
self.connectToAll();
|
2014-06-13 07:30:48 -07:00
|
|
|
self.currentDelay = self.currentDelay * 2 || self.reconnectDelay;
|
2014-06-09 16:08:12 -07:00
|
|
|
setTimeout(self.scheduleConnect.bind(self), self.currentDelay);
|
2014-06-03 07:54:07 -07:00
|
|
|
}
|
2014-06-02 13:40:29 -07:00
|
|
|
}
|
|
|
|
|
2014-04-24 12:07:49 -07:00
|
|
|
Wallet.prototype.getOnlinePeerIDs = function() {
|
|
|
|
return this.network.getOnlinePeerIDs();
|
|
|
|
};
|
|
|
|
|
2014-05-15 12:12:43 -07:00
|
|
|
Wallet.prototype.getRegisteredCopayerIds = function() {
|
|
|
|
var l = this.publicKeyRing.registeredCopayers();
|
|
|
|
var copayers = [];
|
|
|
|
for (var i = 0; i < l; i++) {
|
|
|
|
var cid = this.getCopayerId(i);
|
|
|
|
copayers.push(cid);
|
|
|
|
}
|
|
|
|
return copayers;
|
|
|
|
};
|
|
|
|
|
2014-04-24 12:07:49 -07:00
|
|
|
Wallet.prototype.getRegisteredPeerIds = function() {
|
2014-05-01 05:41:18 -07:00
|
|
|
var l = this.publicKeyRing.registeredCopayers();
|
|
|
|
if (this.registeredPeerIds.length !== l) {
|
|
|
|
this.registeredPeerIds = [];
|
2014-05-15 12:12:43 -07:00
|
|
|
var copayers = this.getRegisteredCopayerIds();
|
2014-05-01 05:41:18 -07:00
|
|
|
for (var i = 0; i < l; i++) {
|
2014-05-15 12:12:43 -07:00
|
|
|
var cid = copayers[i];
|
2014-05-01 05:41:18 -07:00
|
|
|
var pid = this.network.peerFromCopayer(cid);
|
|
|
|
this.registeredPeerIds.push({
|
|
|
|
peerId: pid,
|
2014-07-08 06:43:10 -07:00
|
|
|
copayerId: cid,
|
2014-07-07 13:00:26 -07:00
|
|
|
nick: this.publicKeyRing.nicknameForCopayer(cid),
|
|
|
|
index: i,
|
2014-05-01 05:41:18 -07:00
|
|
|
});
|
|
|
|
}
|
2014-04-24 12:07:49 -07:00
|
|
|
}
|
2014-05-01 05:41:18 -07:00
|
|
|
return this.registeredPeerIds;
|
2014-04-24 12:07:49 -07:00
|
|
|
};
|
|
|
|
|
2014-05-09 07:59:38 -07:00
|
|
|
Wallet.prototype.store = function() {
|
2014-04-17 14:02:20 -07:00
|
|
|
var wallet = this.toObj();
|
|
|
|
this.storage.setFromObj(this.id, wallet);
|
2014-05-09 07:59:38 -07:00
|
|
|
this.log('Wallet stored');
|
2014-04-17 14:02:20 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
Wallet.prototype.toObj = function() {
|
|
|
|
var optsObj = this._optsToObj();
|
2014-07-08 23:03:30 -07:00
|
|
|
|
|
|
|
var networkNonce = this.network.getHexNonce();
|
|
|
|
var networkNonces = this.network.getHexNonces();
|
|
|
|
|
2014-04-17 14:02:20 -07:00
|
|
|
var walletObj = {
|
|
|
|
opts: optsObj,
|
2014-07-08 23:03:30 -07:00
|
|
|
networkNonce: networkNonce, //yours
|
|
|
|
networkNonces: networkNonces, //copayers
|
2014-04-17 14:55:17 -07:00
|
|
|
publicKeyRing: this.publicKeyRing.toObj(),
|
2014-04-17 14:02:20 -07:00
|
|
|
txProposals: this.txProposals.toObj(),
|
2014-06-17 21:00:32 -07:00
|
|
|
privateKey: this.privateKey ? this.privateKey.toObj() : undefined,
|
2014-06-25 13:14:12 -07:00
|
|
|
addressBook: this.addressBook,
|
2014-04-17 14:02:20 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
return walletObj;
|
2014-04-15 06:22:50 -07:00
|
|
|
};
|
|
|
|
|
2014-08-02 16:51:31 -07:00
|
|
|
// fromObj => from a trusted source
|
2014-04-28 08:02:43 -07:00
|
|
|
Wallet.fromObj = function(o, storage, network, blockchain) {
|
2014-05-09 07:59:38 -07:00
|
|
|
var opts = JSON.parse(JSON.stringify(o.opts));
|
2014-06-17 21:00:32 -07:00
|
|
|
opts.addressBook = o.addressBook;
|
2014-06-25 13:14:12 -07:00
|
|
|
|
2014-06-17 10:28:26 -07:00
|
|
|
opts.publicKeyRing = PublicKeyRing.fromObj(o.publicKeyRing);
|
2014-07-31 21:09:46 -07:00
|
|
|
opts.txProposals = TxProposals.fromObj(o.txProposals, Wallet.builderOpts);
|
2014-06-17 10:28:26 -07:00
|
|
|
opts.privateKey = PrivateKey.fromObj(o.privateKey);
|
2014-04-28 08:02:43 -07:00
|
|
|
|
2014-05-09 07:59:38 -07:00
|
|
|
opts.storage = storage;
|
|
|
|
opts.network = network;
|
|
|
|
opts.blockchain = blockchain;
|
2014-04-17 14:02:20 -07:00
|
|
|
var w = new Wallet(opts);
|
|
|
|
return w;
|
|
|
|
};
|
2014-04-15 08:17:28 -07:00
|
|
|
|
2014-05-01 14:32:22 -07:00
|
|
|
Wallet.prototype.toEncryptedObj = function() {
|
|
|
|
var walletObj = this.toObj();
|
|
|
|
return this.storage.export(walletObj);
|
|
|
|
};
|
|
|
|
|
2014-07-08 23:03:30 -07:00
|
|
|
Wallet.prototype.send = function(recipients, obj) {
|
|
|
|
this.network.send(recipients, obj);
|
|
|
|
};
|
|
|
|
|
2014-06-18 06:09:40 -07:00
|
|
|
Wallet.prototype.sendAllTxProposals = function(recipients) {
|
|
|
|
var ntxids = this.txProposals.getNtxids();
|
|
|
|
for (var i in ntxids) {
|
|
|
|
var ntxid = ntxids[i];
|
|
|
|
this.sendTxProposal(ntxid, recipients);
|
2014-05-15 12:39:22 -07:00
|
|
|
}
|
2014-04-15 08:17:28 -07:00
|
|
|
};
|
|
|
|
|
2014-06-18 06:09:40 -07:00
|
|
|
Wallet.prototype.sendTxProposal = function(ntxid, recipients) {
|
|
|
|
preconditions.checkArgument(ntxid);
|
2014-08-04 13:12:53 -07:00
|
|
|
|
2014-06-24 08:36:32 -07:00
|
|
|
this.log('### SENDING txProposal ' + ntxid + ' TO:', recipients || 'All', this.txProposals);
|
2014-07-08 23:03:30 -07:00
|
|
|
this.send(recipients, {
|
2014-06-17 13:43:06 -07:00
|
|
|
type: 'txProposal',
|
2014-08-05 12:25:02 -07:00
|
|
|
txProposal: this.txProposals.get(ntxid).toObjTrim(),
|
2014-06-17 13:43:06 -07:00
|
|
|
walletId: this.id,
|
|
|
|
});
|
2014-04-15 08:17:28 -07:00
|
|
|
};
|
|
|
|
|
2014-08-02 16:51:31 -07:00
|
|
|
Wallet.prototype.sendSeen = function(ntxid) {
|
|
|
|
preconditions.checkArgument(ntxid);
|
|
|
|
this.log('### SENDING seen: ' + ntxid + ' TO: All');
|
|
|
|
this.send(null, {
|
|
|
|
type: 'seen',
|
|
|
|
ntxid: ntxid,
|
|
|
|
walletId: this.id,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
Wallet.prototype.sendReject = function(ntxid) {
|
|
|
|
preconditions.checkArgument(ntxid);
|
|
|
|
this.log('### SENDING reject: ' + ntxid + ' TO: All');
|
|
|
|
this.send(null, {
|
|
|
|
type: 'reject',
|
|
|
|
ntxid: ntxid,
|
|
|
|
walletId: this.id,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2014-04-20 12:16:09 -07:00
|
|
|
Wallet.prototype.sendWalletReady = function(recipients) {
|
|
|
|
this.log('### SENDING WalletReady TO:', recipients);
|
|
|
|
|
2014-07-08 23:03:30 -07:00
|
|
|
this.send(recipients, {
|
2014-04-24 16:56:36 -07:00
|
|
|
type: 'walletReady',
|
2014-04-20 12:16:09 -07:00
|
|
|
walletId: this.id,
|
|
|
|
});
|
|
|
|
};
|
2014-04-16 16:58:57 -07:00
|
|
|
|
|
|
|
Wallet.prototype.sendWalletId = function(recipients) {
|
2014-04-25 13:36:00 -07:00
|
|
|
this.log('### SENDING walletId TO:', recipients || 'All', this.id);
|
2014-04-16 16:58:57 -07:00
|
|
|
|
2014-07-08 23:03:30 -07:00
|
|
|
this.send(recipients, {
|
2014-04-24 16:56:36 -07:00
|
|
|
type: 'walletId',
|
2014-04-16 16:58:57 -07:00
|
|
|
walletId: this.id,
|
2014-06-09 17:00:28 -07:00
|
|
|
opts: this._optsToObj(),
|
|
|
|
networkName: this.getNetworkName(),
|
2014-04-16 16:58:57 -07:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2014-04-15 08:17:28 -07:00
|
|
|
Wallet.prototype.sendPublicKeyRing = function(recipients) {
|
2014-04-24 16:56:36 -07:00
|
|
|
this.log('### SENDING publicKeyRing TO:', recipients || 'All', this.publicKeyRing.toObj());
|
2014-06-09 06:30:37 -07:00
|
|
|
var publicKeyRing = this.publicKeyRing.toObj();
|
|
|
|
delete publicKeyRing.publicKeysCache; // exclude publicKeysCache from network obj
|
2014-04-15 08:17:28 -07:00
|
|
|
|
2014-07-08 23:03:30 -07:00
|
|
|
this.send(recipients, {
|
2014-04-24 16:56:36 -07:00
|
|
|
type: 'publicKeyRing',
|
2014-06-09 06:30:37 -07:00
|
|
|
publicKeyRing: publicKeyRing,
|
2014-04-15 08:17:28 -07:00
|
|
|
walletId: this.id,
|
|
|
|
});
|
|
|
|
};
|
2014-06-04 10:24:46 -07:00
|
|
|
Wallet.prototype.sendIndexes = function(recipients) {
|
2014-07-29 07:23:58 -07:00
|
|
|
var indexes = HDParams.serialize(this.publicKeyRing.indexes);
|
2014-07-03 12:42:03 -07:00
|
|
|
this.log('### INDEXES TO:', recipients || 'All', indexes);
|
2014-06-04 10:24:46 -07:00
|
|
|
|
2014-07-08 23:03:30 -07:00
|
|
|
this.send(recipients, {
|
2014-06-04 10:24:46 -07:00
|
|
|
type: 'indexes',
|
2014-07-03 12:42:03 -07:00
|
|
|
indexes: indexes,
|
2014-06-04 10:24:46 -07:00
|
|
|
walletId: this.id,
|
|
|
|
});
|
|
|
|
};
|
2014-04-15 08:17:28 -07:00
|
|
|
|
2014-06-18 16:18:13 -07:00
|
|
|
Wallet.prototype.sendAddressBook = function(recipients) {
|
|
|
|
this.log('### SENDING addressBook TO:', recipients || 'All', this.addressBook);
|
2014-07-08 23:03:30 -07:00
|
|
|
this.send(recipients, {
|
2014-06-18 16:18:13 -07:00
|
|
|
type: 'addressbook',
|
|
|
|
addressBook: this.addressBook,
|
|
|
|
walletId: this.id,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2014-05-16 14:33:06 -07:00
|
|
|
Wallet.prototype.getName = function() {
|
|
|
|
return this.name || this.id;
|
|
|
|
};
|
|
|
|
|
|
|
|
Wallet.prototype._doGenerateAddress = function(isChange) {
|
2014-07-03 07:18:01 -07:00
|
|
|
return this.publicKeyRing.generateAddress(isChange, this.publicKey);
|
2014-05-16 14:33:06 -07:00
|
|
|
};
|
2014-04-17 12:27:15 -07:00
|
|
|
|
2014-05-16 14:33:06 -07:00
|
|
|
|
|
|
|
Wallet.prototype.generateAddress = function(isChange, cb) {
|
|
|
|
var addr = this._doGenerateAddress(isChange);
|
2014-06-04 10:24:46 -07:00
|
|
|
this.sendIndexes();
|
2014-05-09 07:59:38 -07:00
|
|
|
this.store();
|
2014-05-16 14:33:06 -07:00
|
|
|
if (cb) return cb(addr);
|
2014-04-15 12:50:16 -07:00
|
|
|
return addr;
|
|
|
|
};
|
2014-04-15 08:17:28 -07:00
|
|
|
|
2014-04-22 22:01:54 -07:00
|
|
|
|
2014-07-31 21:09:46 -07:00
|
|
|
Wallet.prototype.getTxProposals = function() {
|
2014-04-15 11:25:55 -07:00
|
|
|
var ret = [];
|
2014-05-15 12:12:43 -07:00
|
|
|
var copayers = this.getRegisteredCopayerIds();
|
2014-06-18 06:21:26 -07:00
|
|
|
for (var ntxid in this.txProposals.txps) {
|
|
|
|
var txp = this.txProposals.getTxProposal(ntxid, copayers);
|
2014-06-17 10:31:15 -07:00
|
|
|
txp.signedByUs = txp.signedBy[this.getMyCopayerId()] ? true : false;
|
|
|
|
txp.rejectedByUs = txp.rejectedBy[this.getMyCopayerId()] ? true : false;
|
|
|
|
if (this.totalCopayers - txp.rejectCount < this.requiredCopayers) {
|
|
|
|
txp.finallyRejected = true;
|
|
|
|
}
|
2014-04-21 03:27:45 -07:00
|
|
|
|
2014-07-25 13:45:56 -07:00
|
|
|
if (txp.readonly && !txp.finallyRejected && !txp.sentTs) {} else {
|
2014-07-25 07:59:13 -07:00
|
|
|
ret.push(txp);
|
|
|
|
}
|
2014-04-16 13:50:10 -07:00
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
};
|
|
|
|
|
2014-04-22 22:01:54 -07:00
|
|
|
|
|
|
|
Wallet.prototype.reject = function(ntxid) {
|
2014-08-05 12:25:02 -07:00
|
|
|
var txp = this.txProposals.reject(ntxid, this.getMyCopayerId());
|
2014-08-02 16:51:31 -07:00
|
|
|
this.sendReject(ntxid);
|
2014-05-09 07:59:38 -07:00
|
|
|
this.store();
|
2014-05-13 01:02:21 -07:00
|
|
|
this.emit('txProposalsUpdated');
|
2014-04-18 15:28:28 -07:00
|
|
|
};
|
2014-04-16 13:50:10 -07:00
|
|
|
|
2014-05-16 14:33:06 -07:00
|
|
|
Wallet.prototype.sign = function(ntxid, cb) {
|
2014-06-18 14:39:29 -07:00
|
|
|
preconditions.checkState(typeof this.getMyCopayerId() !== 'undefined');
|
2014-04-18 15:28:28 -07:00
|
|
|
var self = this;
|
2014-05-16 14:33:06 -07:00
|
|
|
setTimeout(function() {
|
|
|
|
var myId = self.getMyCopayerId();
|
2014-08-05 12:25:02 -07:00
|
|
|
var txp = self.txProposals.get(ntxid);
|
2014-08-02 16:51:31 -07:00
|
|
|
// if (!txp || txp.rejectedBy[myId] || txp.signedBy[myId]) {
|
|
|
|
// if (cb) cb(false);
|
|
|
|
// }
|
|
|
|
//
|
2014-08-05 11:07:16 -07:00
|
|
|
|
2014-08-05 11:10:31 -07:00
|
|
|
// If this is a payment protocol request,
|
|
|
|
// ensure it hasn't been tampered with.
|
2014-08-05 11:07:16 -07:00
|
|
|
if (!self.verifyPaymentRequest(ntxid)) {
|
|
|
|
if (cb) cb(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-05-30 11:07:52 -07:00
|
|
|
var keys = self.privateKey.getForPaths(txp.inputChainPaths);
|
2014-04-18 15:28:28 -07:00
|
|
|
|
2014-05-16 14:33:06 -07:00
|
|
|
var b = txp.builder;
|
2014-07-24 17:18:38 -07:00
|
|
|
var before = txp.countSignatures();
|
2014-05-16 14:33:06 -07:00
|
|
|
b.sign(keys);
|
2014-04-18 15:28:28 -07:00
|
|
|
|
2014-05-16 14:33:06 -07:00
|
|
|
var ret = false;
|
2014-07-24 17:18:38 -07:00
|
|
|
if (txp.countSignatures() > before) {
|
2014-05-16 14:33:06 -07:00
|
|
|
txp.signedBy[myId] = Date.now();
|
2014-06-18 06:09:40 -07:00
|
|
|
self.sendTxProposal(ntxid);
|
2014-05-16 14:33:06 -07:00
|
|
|
self.store();
|
|
|
|
self.emit('txProposalsUpdated');
|
|
|
|
ret = true;
|
|
|
|
}
|
|
|
|
if (cb) return cb(ret);
|
2014-06-03 07:54:07 -07:00
|
|
|
}, 10);
|
2014-04-15 11:25:55 -07:00
|
|
|
};
|
|
|
|
|
2014-04-18 15:28:28 -07:00
|
|
|
|
2014-08-05 12:25:02 -07:00
|
|
|
Wallet.prototype.sendTx = function(ntxid, cb) {
|
|
|
|
var txp = this.txProposals.get(ntxid);
|
2014-07-25 18:46:29 -07:00
|
|
|
|
2014-07-28 09:48:45 -07:00
|
|
|
if (txp.merchant) {
|
2014-07-28 10:44:45 -07:00
|
|
|
return this.sendPaymentTx(ntxid, cb);
|
2014-07-28 09:48:45 -07:00
|
|
|
}
|
2014-07-25 18:46:29 -07:00
|
|
|
|
2014-04-18 15:28:28 -07:00
|
|
|
var tx = txp.builder.build();
|
2014-08-05 12:25:02 -07:00
|
|
|
if (!tx.isComplete())
|
|
|
|
throw new Error('Tx is not complete. Can not broadcast');
|
2014-06-09 13:52:08 -07:00
|
|
|
this.log('Broadcasting Transaction');
|
2014-05-23 13:22:24 -07:00
|
|
|
var scriptSig = tx.ins[0].getScript();
|
|
|
|
var size = scriptSig.serialize().length;
|
|
|
|
|
2014-04-18 15:28:28 -07:00
|
|
|
var txHex = tx.serialize().toString('hex');
|
2014-06-09 13:52:08 -07:00
|
|
|
this.log('Raw transaction: ', txHex);
|
2014-04-18 15:28:28 -07:00
|
|
|
|
|
|
|
var self = this;
|
|
|
|
this.blockchain.sendRawTransaction(txHex, function(txid) {
|
2014-06-09 13:52:08 -07:00
|
|
|
self.log('BITCOIND txid:', txid);
|
2014-04-18 15:28:28 -07:00
|
|
|
if (txid) {
|
2014-08-05 12:25:02 -07:00
|
|
|
self.txProposals.get(ntxid).setSent(txid);
|
2014-06-18 06:09:40 -07:00
|
|
|
self.sendTxProposal(ntxid);
|
2014-05-16 14:33:06 -07:00
|
|
|
self.store();
|
2014-08-05 12:25:02 -07:00
|
|
|
return cb(txid);
|
|
|
|
} else {
|
|
|
|
self.log('Sent failed. Checking is the TX was sent already');
|
|
|
|
self._checkSentTx(ntxid, function(txid) {
|
|
|
|
if (txid)
|
|
|
|
self.store();
|
|
|
|
|
|
|
|
return cb(txid);
|
|
|
|
});
|
2014-04-18 15:28:28 -07:00
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
2014-04-15 11:25:55 -07:00
|
|
|
|
2014-07-28 10:44:45 -07:00
|
|
|
Wallet.prototype.createPaymentTx = function(options, cb) {
|
2014-07-25 18:46:29 -07:00
|
|
|
var self = this;
|
|
|
|
|
|
|
|
if (typeof options === 'string') {
|
|
|
|
options = { uri: options };
|
|
|
|
}
|
2014-07-31 12:31:14 -07:00
|
|
|
options.uri = options.uri || options.url;
|
2014-07-25 18:46:29 -07:00
|
|
|
|
2014-07-29 11:14:07 -07:00
|
|
|
if (options.uri.indexOf('bitcoin:') === 0) {
|
|
|
|
options.uri = parseBitcoinURI(options.uri).merchant;
|
|
|
|
if (!options.uri) {
|
|
|
|
return cb(new Error('No URI.'));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-31 12:37:47 -07:00
|
|
|
var req = this.paymentRequests[options.uri];
|
|
|
|
if (req) {
|
|
|
|
delete this.paymentRequests[options.uri];
|
2014-07-31 13:16:03 -07:00
|
|
|
this.receivePaymentRequest(options, req.pr, cb);
|
2014-07-31 12:37:47 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-08-06 09:58:16 -07:00
|
|
|
return Wallet.request({
|
2014-07-25 18:46:29 -07:00
|
|
|
method: options.method || 'POST',
|
2014-07-31 12:37:47 -07:00
|
|
|
url: options.uri,
|
2014-07-25 18:46:29 -07:00
|
|
|
headers: {
|
2014-08-11 15:39:04 -07:00
|
|
|
'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE
|
2014-07-25 18:46:29 -07:00
|
|
|
},
|
|
|
|
responseType: 'arraybuffer'
|
|
|
|
})
|
|
|
|
.success(function(data, status, headers, config) {
|
|
|
|
data = PayPro.PaymentRequest.decode(data);
|
|
|
|
var pr = new PayPro();
|
|
|
|
pr = pr.makePaymentRequest(data);
|
2014-07-28 18:34:15 -07:00
|
|
|
return self.receivePaymentRequest(options, pr, cb);
|
2014-07-25 18:46:29 -07:00
|
|
|
})
|
|
|
|
.error(function(data, status, headers, config) {
|
|
|
|
return cb(new Error('Status: ' + JSON.stringify(status)));
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2014-07-31 12:08:21 -07:00
|
|
|
Wallet.prototype.fetchPaymentTx = function(options, cb) {
|
2014-07-31 12:23:06 -07:00
|
|
|
var self = this;
|
2014-07-31 12:37:47 -07:00
|
|
|
|
2014-07-31 12:08:21 -07:00
|
|
|
options = options || {};
|
|
|
|
if (typeof options === 'string') {
|
|
|
|
options = { uri: options };
|
|
|
|
}
|
2014-07-31 12:31:14 -07:00
|
|
|
options.uri = options.uri || options.url;
|
2014-07-31 12:08:21 -07:00
|
|
|
options.fetch = true;
|
2014-07-31 12:37:47 -07:00
|
|
|
|
|
|
|
var req = this.paymentRequests[options.uri];
|
|
|
|
if (req) {
|
|
|
|
return cb(null, req.merchantData);
|
2014-07-31 12:31:14 -07:00
|
|
|
}
|
2014-07-31 12:37:47 -07:00
|
|
|
|
2014-07-31 13:16:03 -07:00
|
|
|
return this.createPaymentTx(options, function(err, merchantData, pr) {
|
|
|
|
if (err) return cb(err);
|
2014-07-31 12:31:14 -07:00
|
|
|
self.paymentRequests[options.uri] = {
|
2014-07-31 12:23:06 -07:00
|
|
|
merchantData: merchantData,
|
|
|
|
pr: pr
|
|
|
|
};
|
|
|
|
return cb(null, merchantData);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2014-07-28 18:34:15 -07:00
|
|
|
Wallet.prototype.receivePaymentRequest = function(options, pr, cb) {
|
2014-07-25 18:46:29 -07:00
|
|
|
var self = this;
|
|
|
|
|
|
|
|
var ver = pr.get('payment_details_version');
|
|
|
|
var pki_type = pr.get('pki_type');
|
|
|
|
var pki_data = pr.get('pki_data');
|
|
|
|
var details = pr.get('serialized_payment_details');
|
|
|
|
var sig = pr.get('signature');
|
|
|
|
|
|
|
|
var certs = PayPro.X509Certificates.decode(pki_data);
|
|
|
|
certs = certs.certificate;
|
|
|
|
|
2014-08-01 19:49:39 -07:00
|
|
|
// Fix for older versions of bitcore
|
2014-07-28 18:34:15 -07:00
|
|
|
if (!PayPro.RootCerts) {
|
|
|
|
PayPro.RootCerts = {
|
|
|
|
getTrusted: function() {}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2014-07-28 10:44:45 -07:00
|
|
|
var trusted = certs.map(function(cert) {
|
2014-07-25 18:46:29 -07:00
|
|
|
var der = cert.toString('hex');
|
|
|
|
var pem = PayPro.prototype._DERtoPEM(der, 'CERTIFICATE');
|
2014-07-28 18:34:15 -07:00
|
|
|
return PayPro.RootCerts.getTrusted(pem);
|
2014-08-01 14:39:24 -07:00
|
|
|
}).filter(Boolean);
|
2014-07-25 18:46:29 -07:00
|
|
|
|
|
|
|
// Verify Signature
|
|
|
|
var verified = pr.verify();
|
|
|
|
|
|
|
|
if (!verified) {
|
|
|
|
return cb(new Error('Server sent a bad signature.'));
|
|
|
|
}
|
|
|
|
|
2014-07-28 10:44:45 -07:00
|
|
|
var ca = trusted[0];
|
2014-07-25 18:46:29 -07:00
|
|
|
|
|
|
|
details = PayPro.PaymentDetails.decode(details);
|
|
|
|
var pd = new PayPro();
|
|
|
|
pd = pd.makePaymentDetails(details);
|
2014-07-28 09:48:45 -07:00
|
|
|
|
2014-07-25 18:46:29 -07:00
|
|
|
var network = pd.get('network');
|
|
|
|
var outputs = pd.get('outputs');
|
|
|
|
var time = pd.get('time');
|
|
|
|
var expires = pd.get('expires');
|
|
|
|
var memo = pd.get('memo');
|
|
|
|
var payment_url = pd.get('payment_url');
|
|
|
|
var merchant_data = pd.get('merchant_data');
|
|
|
|
|
2014-07-28 09:48:45 -07:00
|
|
|
var merchantData = {
|
|
|
|
pr: {
|
2014-07-28 10:44:45 -07:00
|
|
|
payment_details_version: ver,
|
2014-07-28 09:48:45 -07:00
|
|
|
pki_type: pki_type,
|
2014-07-28 10:44:45 -07:00
|
|
|
pki_data: certs,
|
2014-07-28 09:48:45 -07:00
|
|
|
pd: {
|
|
|
|
network: network,
|
2014-07-28 10:44:45 -07:00
|
|
|
outputs: outputs.map(function(output) {
|
|
|
|
return {
|
|
|
|
amount: output.get('amount'),
|
2014-07-28 14:46:08 -07:00
|
|
|
script: {
|
|
|
|
offset: output.get('script').offset,
|
|
|
|
limit: output.get('script').limit,
|
2014-08-01 14:52:50 -07:00
|
|
|
// NOTE: For some reason output.script.buffer
|
|
|
|
// is only an ArrayBuffer
|
2014-07-31 17:13:58 -07:00
|
|
|
buffer: new Buffer(new Uint8Array(
|
|
|
|
output.get('script').buffer)).toString('hex')
|
2014-07-28 14:46:08 -07:00
|
|
|
}
|
2014-07-28 10:44:45 -07:00
|
|
|
};
|
|
|
|
}),
|
2014-07-28 09:48:45 -07:00
|
|
|
time: time,
|
|
|
|
expires: expires,
|
2014-08-01 14:52:50 -07:00
|
|
|
memo: memo || 'This server would like some BTC from you.',
|
2014-07-28 09:48:45 -07:00
|
|
|
payment_url: payment_url,
|
2014-07-28 10:44:45 -07:00
|
|
|
merchant_data: merchant_data.toString('hex')
|
2014-07-28 09:48:45 -07:00
|
|
|
},
|
2014-08-05 12:59:44 -07:00
|
|
|
signature: sig.toString('hex'),
|
2014-08-01 16:07:56 -07:00
|
|
|
ca: ca,
|
|
|
|
untrusted: !ca
|
2014-07-30 13:35:57 -07:00
|
|
|
},
|
2014-07-31 16:33:09 -07:00
|
|
|
request_url: options.uri,
|
2014-08-05 10:11:59 -07:00
|
|
|
total: bignum('0', 10).toString(10),
|
|
|
|
// Expose so other copayers can verify signature
|
|
|
|
// and identity, not to mention data.
|
|
|
|
raw: pr.serialize().toString('hex')
|
2014-07-28 09:48:45 -07:00
|
|
|
};
|
|
|
|
|
2014-07-28 18:34:15 -07:00
|
|
|
return this.getUnspent(function(err, unspent) {
|
2014-07-31 12:23:06 -07:00
|
|
|
if (options.fetch) {
|
2014-08-06 16:40:32 -07:00
|
|
|
if (!unspent || !unspent.length) {
|
2014-08-07 10:36:44 -07:00
|
|
|
return cb(new Error('No unspent outputs available.'));
|
2014-08-06 16:40:32 -07:00
|
|
|
}
|
2014-07-31 12:23:06 -07:00
|
|
|
self.createPaymentTxSync(options, merchantData, unspent);
|
2014-07-31 13:16:03 -07:00
|
|
|
return cb(null, merchantData, pr);
|
2014-07-31 12:23:06 -07:00
|
|
|
}
|
|
|
|
|
2014-07-28 09:48:45 -07:00
|
|
|
var ntxid = self.createPaymentTxSync(options, merchantData, unspent);
|
2014-07-27 22:56:57 -07:00
|
|
|
if (ntxid) {
|
|
|
|
self.sendIndexes();
|
|
|
|
self.sendTxProposal(ntxid);
|
|
|
|
self.store();
|
|
|
|
self.emit('txProposalsUpdated');
|
|
|
|
}
|
2014-07-25 18:46:29 -07:00
|
|
|
|
2014-08-05 15:14:31 -07:00
|
|
|
self.log('You are currently on this BTC network:');
|
|
|
|
self.log(network);
|
|
|
|
self.log('The server sent you a message:');
|
|
|
|
self.log(memo);
|
|
|
|
|
2014-08-04 10:47:04 -07:00
|
|
|
return cb(ntxid, merchantData);
|
2014-07-27 22:56:57 -07:00
|
|
|
});
|
|
|
|
};
|
2014-07-25 18:46:29 -07:00
|
|
|
|
2014-07-28 09:48:45 -07:00
|
|
|
Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) {
|
2014-07-27 22:56:57 -07:00
|
|
|
var self = this;
|
2014-07-25 18:46:29 -07:00
|
|
|
|
2014-07-28 10:44:45 -07:00
|
|
|
if (!cb) {
|
|
|
|
cb = options;
|
|
|
|
options = {};
|
|
|
|
}
|
|
|
|
|
2014-08-06 17:17:01 -07:00
|
|
|
var txp = this.txProposals.get(ntxid);
|
2014-07-27 22:56:57 -07:00
|
|
|
if (!txp) return;
|
2014-07-25 18:46:29 -07:00
|
|
|
|
2014-07-27 22:56:57 -07:00
|
|
|
var tx = txp.builder.build();
|
|
|
|
if (!tx.isComplete()) return;
|
|
|
|
this.log('Sending Transaction');
|
2014-07-25 18:46:29 -07:00
|
|
|
|
2014-07-27 22:56:57 -07:00
|
|
|
var refund_outputs = [];
|
2014-07-25 18:46:29 -07:00
|
|
|
|
2014-07-28 18:45:18 -07:00
|
|
|
options.refund_to = options.refund_to
|
2014-08-01 15:55:21 -07:00
|
|
|
|| this.publicKeyRing.getPubKeys(0, false, this.getMyCopayerId())[0];
|
2014-07-27 22:56:57 -07:00
|
|
|
|
|
|
|
if (options.refund_to) {
|
2014-07-31 16:33:09 -07:00
|
|
|
var total = txp.merchant.pr.pd.outputs.reduce(function(total, _, i) {
|
2014-08-06 13:07:16 -07:00
|
|
|
// XXX reverse endianness to work around bignum bug:
|
|
|
|
var txv = tx.outs[i].v;
|
|
|
|
var v = new Buffer(8);
|
|
|
|
for (var j = 0; j < 8; j++) v[j] = txv[7 - j];
|
|
|
|
return total.add(bignum.fromBuffer(v, {
|
|
|
|
endian: 'big',
|
2014-07-31 16:33:09 -07:00
|
|
|
size: 1
|
|
|
|
}));
|
2014-08-01 19:45:24 -07:00
|
|
|
}, bignum('0', 10));
|
2014-08-04 10:41:03 -07:00
|
|
|
|
2014-07-27 22:56:57 -07:00
|
|
|
var rpo = new PayPro();
|
|
|
|
rpo = rpo.makeOutput();
|
2014-08-04 10:41:03 -07:00
|
|
|
|
2014-08-01 14:52:50 -07:00
|
|
|
// XXX Bad - the amount *has* to be a Number in protobufjs
|
2014-08-04 10:41:03 -07:00
|
|
|
// Possibly does not matter - server can ignore the amount anyway.
|
2014-07-30 13:31:57 -07:00
|
|
|
rpo.set('amount', +total.toString(10));
|
2014-08-04 10:41:03 -07:00
|
|
|
|
2014-07-27 22:56:57 -07:00
|
|
|
rpo.set('script',
|
|
|
|
Buffer.concat([
|
|
|
|
new Buffer([
|
|
|
|
118, // OP_DUP
|
|
|
|
169, // OP_HASH160
|
|
|
|
76, // OP_PUSHDATA1
|
|
|
|
20, // number of bytes
|
|
|
|
]),
|
2014-08-04 10:41:03 -07:00
|
|
|
// needs to be ripesha'd
|
|
|
|
bitcore.util.sha256ripe160(options.refund_to),
|
2014-07-27 22:56:57 -07:00
|
|
|
new Buffer([
|
|
|
|
136, // OP_EQUALVERIFY
|
|
|
|
172 // OP_CHECKSIG
|
|
|
|
])
|
|
|
|
])
|
|
|
|
);
|
2014-08-04 10:41:03 -07:00
|
|
|
|
2014-07-27 22:56:57 -07:00
|
|
|
refund_outputs.push(rpo.message);
|
|
|
|
}
|
|
|
|
|
|
|
|
// We send this to the serve after receiving a PaymentRequest
|
|
|
|
var pay = new PayPro();
|
|
|
|
pay = pay.makePayment();
|
2014-07-28 18:45:18 -07:00
|
|
|
var merchant_data = txp.merchant.pr.pd.merchant_data;
|
2014-08-01 15:01:43 -07:00
|
|
|
merchant_data = new Buffer(merchant_data, 'hex');
|
2014-07-28 10:44:45 -07:00
|
|
|
pay.set('merchant_data', merchant_data);
|
2014-07-27 22:56:57 -07:00
|
|
|
pay.set('transactions', [tx.serialize()]);
|
|
|
|
pay.set('refund_to', refund_outputs);
|
|
|
|
|
2014-07-28 10:44:45 -07:00
|
|
|
options.memo = options.memo || options.comment
|
|
|
|
|| 'Hi server, I would like to give you some money.';
|
2014-07-27 22:56:57 -07:00
|
|
|
|
2014-08-01 15:03:17 -07:00
|
|
|
pay.set('memo', options.memo);
|
2014-07-25 18:46:29 -07:00
|
|
|
|
2014-08-01 19:45:24 -07:00
|
|
|
pay = pay.serialize();
|
|
|
|
|
2014-08-05 15:14:31 -07:00
|
|
|
this.log('Sending Payment Message:');
|
|
|
|
this.log(pay.toString('hex'));
|
|
|
|
|
2014-08-01 19:45:24 -07:00
|
|
|
var buf = new ArrayBuffer(pay.length);
|
|
|
|
var view = new Uint8Array(buf);
|
|
|
|
for (var i = 0; i < pay.length; i++) {
|
|
|
|
view[i] = pay[i];
|
|
|
|
}
|
|
|
|
|
2014-08-06 09:58:16 -07:00
|
|
|
return Wallet.request({
|
2014-07-25 18:46:29 -07:00
|
|
|
method: 'POST',
|
2014-07-28 10:44:45 -07:00
|
|
|
url: txp.merchant.pr.pd.payment_url,
|
2014-07-25 18:46:29 -07:00
|
|
|
headers: {
|
|
|
|
// BIP-71
|
2014-08-11 15:37:48 -07:00
|
|
|
'Accept': PayPro.PAYMENT_ACK_CONTENT_TYPE,
|
2014-08-01 19:45:24 -07:00
|
|
|
'Content-Type': PayPro.PAYMENT_CONTENT_TYPE
|
|
|
|
// XHR does not allow these:
|
|
|
|
// 'Content-Length': (pay.byteLength || pay.length) + '',
|
|
|
|
// 'Content-Transfer-Encoding': 'binary'
|
2014-07-25 18:46:29 -07:00
|
|
|
},
|
2014-08-01 20:22:11 -07:00
|
|
|
// Technically how this should be done via XHR (used to
|
|
|
|
// be the ArrayBuffer, now you send the View instead).
|
|
|
|
data: view,
|
2014-07-25 18:46:29 -07:00
|
|
|
responseType: 'arraybuffer'
|
|
|
|
})
|
|
|
|
.success(function(data, status, headers, config) {
|
|
|
|
data = PayPro.PaymentACK.decode(data);
|
|
|
|
var ack = new PayPro();
|
|
|
|
ack = ack.makePaymentACK(data);
|
2014-08-08 14:10:18 -07:00
|
|
|
return self.receivePaymentRequestACK(ntxid, tx, txp, ack, cb);
|
2014-07-25 18:46:29 -07:00
|
|
|
})
|
|
|
|
.error(function(data, status, headers, config) {
|
|
|
|
return cb(new Error('Status: ' + JSON.stringify(status)));
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2014-08-08 14:10:18 -07:00
|
|
|
Wallet.prototype.receivePaymentRequestACK = function(ntxid, tx, txp, ack, cb) {
|
2014-07-25 18:46:29 -07:00
|
|
|
var self = this;
|
2014-08-01 14:52:50 -07:00
|
|
|
|
2014-07-25 18:46:29 -07:00
|
|
|
var payment = ack.get('payment');
|
|
|
|
var memo = ack.get('memo');
|
2014-08-01 14:52:50 -07:00
|
|
|
|
2014-08-05 15:14:31 -07:00
|
|
|
this.log('Our payment was acknowledged!');
|
|
|
|
this.log('Message from Merchant: %s', memo);
|
|
|
|
|
2014-07-25 18:46:29 -07:00
|
|
|
payment = PayPro.Payment.decode(payment);
|
|
|
|
var pay = new PayPro();
|
|
|
|
payment = pay.makePayment(payment);
|
2014-08-01 14:52:50 -07:00
|
|
|
|
2014-08-08 14:10:18 -07:00
|
|
|
txp.merchant.ack = {
|
|
|
|
memo: memo
|
|
|
|
};
|
|
|
|
|
2014-07-25 18:46:29 -07:00
|
|
|
var tx = payment.message.transactions[0];
|
2014-08-01 19:45:24 -07:00
|
|
|
|
|
|
|
if (!tx) {
|
2014-08-08 14:10:18 -07:00
|
|
|
this.log('Sending to server was not met with a returned tx.');
|
|
|
|
return this._checkSentTx(ntxid, function(txid) {
|
|
|
|
self.log('[Wallet.js.1048:txid:%s]', txid);
|
|
|
|
if (txid) self.store();
|
|
|
|
return cb(txid, txp.merchant);
|
|
|
|
});
|
2014-08-01 19:45:24 -07:00
|
|
|
}
|
|
|
|
|
2014-07-25 18:46:29 -07:00
|
|
|
if (tx.buffer) {
|
2014-08-01 19:45:24 -07:00
|
|
|
tx.buffer = new Buffer(new Uint8Array(tx.buffer));
|
2014-07-25 18:46:29 -07:00
|
|
|
tx.buffer = tx.buffer.slice(tx.offset, tx.limit);
|
|
|
|
var ptx = new bitcore.Transaction();
|
|
|
|
ptx.parse(tx.buffer);
|
|
|
|
tx = ptx;
|
|
|
|
}
|
2014-08-01 14:52:50 -07:00
|
|
|
|
2014-07-25 18:46:29 -07:00
|
|
|
var txid = tx.getHash().toString('hex');
|
2014-08-08 14:10:18 -07:00
|
|
|
var txHex = tx.serialize().toString('hex');
|
|
|
|
this.log('Raw transaction: ', txHex);
|
|
|
|
this.log('BITCOIND txid:', txid);
|
|
|
|
this.txProposals.get(ntxid).setSent(txid);
|
|
|
|
this.sendTxProposal(ntxid);
|
|
|
|
this.store();
|
|
|
|
|
2014-08-04 10:47:04 -07:00
|
|
|
return cb(txid, txp.merchant);
|
2014-07-25 18:46:29 -07:00
|
|
|
};
|
|
|
|
|
2014-07-28 09:48:45 -07:00
|
|
|
Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) {
|
2014-07-25 18:46:29 -07:00
|
|
|
var self = this;
|
2014-07-28 18:34:15 -07:00
|
|
|
var priv = this.privateKey;
|
|
|
|
var pkr = this.publicKeyRing;
|
2014-07-25 18:46:29 -07:00
|
|
|
|
2014-07-28 18:34:15 -07:00
|
|
|
preconditions.checkState(pkr.isComplete());
|
2014-08-01 14:52:50 -07:00
|
|
|
if (options.memo) {
|
|
|
|
preconditions.checkArgument(options.memo.length <= 100);
|
|
|
|
}
|
2014-07-25 18:46:29 -07:00
|
|
|
|
2014-07-27 22:56:57 -07:00
|
|
|
var opts = {
|
|
|
|
remainderOut: {
|
|
|
|
address: this._doGenerateAddress(true).toString()
|
2014-07-25 18:46:29 -07:00
|
|
|
}
|
2014-07-27 22:56:57 -07:00
|
|
|
};
|
2014-07-25 18:46:29 -07:00
|
|
|
|
2014-08-05 17:01:17 -07:00
|
|
|
merchantData.total = bignum(merchantData.total, 10);
|
|
|
|
|
|
|
|
var outs = [];
|
|
|
|
merchantData.pr.pd.outputs.forEach(function(output) {
|
|
|
|
var amount = output.amount;
|
|
|
|
|
2014-08-06 12:49:48 -07:00
|
|
|
// big endian
|
2014-08-05 17:01:17 -07:00
|
|
|
var v = new Buffer(8);
|
|
|
|
v[0] = (amount.high >> 24) & 0xff;
|
|
|
|
v[1] = (amount.high >> 16) & 0xff;
|
|
|
|
v[2] = (amount.high >> 8) & 0xff;
|
|
|
|
v[3] = (amount.high >> 0) & 0xff;
|
|
|
|
v[4] = (amount.low >> 24) & 0xff;
|
|
|
|
v[5] = (amount.low >> 16) & 0xff;
|
|
|
|
v[6] = (amount.low >> 8) & 0xff;
|
|
|
|
v[7] = (amount.low >> 0) & 0xff;
|
|
|
|
|
|
|
|
var script = {
|
|
|
|
offset: output.script.offset,
|
|
|
|
limit: output.script.limit,
|
|
|
|
buffer: new Buffer(output.script.buffer, 'hex')
|
|
|
|
};
|
|
|
|
var s = script.buffer.slice(script.offset, script.limit);
|
|
|
|
var network = merchantData.pr.pd.network === 'main' ? 'livenet' : 'testnet';
|
|
|
|
var addr = bitcore.Address.fromScriptPubKey(new bitcore.Script(s), network);
|
|
|
|
|
|
|
|
outs.push({
|
|
|
|
address: addr[0].toString(),
|
|
|
|
amountSatStr: bignum.fromBuffer(v, {
|
2014-08-06 13:07:16 -07:00
|
|
|
endian: 'big',
|
2014-08-05 17:01:17 -07:00
|
|
|
size: 1
|
|
|
|
}).toString(10)
|
|
|
|
});
|
|
|
|
|
|
|
|
merchantData.total = merchantData.total.add(bignum.fromBuffer(v, {
|
2014-08-06 13:07:16 -07:00
|
|
|
endian: 'big',
|
2014-08-05 17:01:17 -07:00
|
|
|
size: 1
|
|
|
|
}));
|
|
|
|
});
|
|
|
|
|
|
|
|
merchantData.total = merchantData.total.toString(10);
|
|
|
|
|
2014-07-27 22:56:57 -07:00
|
|
|
var b = new Builder(opts)
|
|
|
|
.setUnspent(unspent)
|
|
|
|
.setOutputs(outs);
|
2014-07-25 18:46:29 -07:00
|
|
|
|
2014-08-06 10:31:39 -07:00
|
|
|
merchantData.pr.pd.outputs.forEach(function(output, i) {
|
|
|
|
var script = {
|
|
|
|
offset: output.script.offset,
|
|
|
|
limit: output.script.limit,
|
|
|
|
buffer: new Buffer(output.script.buffer, 'hex')
|
|
|
|
};
|
|
|
|
var s = script.buffer.slice(script.offset, script.limit);
|
|
|
|
b.tx.outs[i].s = s;
|
|
|
|
});
|
2014-08-05 18:12:26 -07:00
|
|
|
|
2014-08-05 16:14:50 -07:00
|
|
|
var selectedUtxos = b.getSelectedUnspent();
|
|
|
|
var inputChainPaths = selectedUtxos.map(function(utxo) {
|
|
|
|
return pkr.pathForAddress(utxo.address);
|
|
|
|
});
|
|
|
|
|
|
|
|
b = b.setHashToScriptMap(pkr.getRedeemScriptMap(inputChainPaths));
|
|
|
|
|
2014-08-06 17:17:01 -07:00
|
|
|
var keys = priv.getForPaths(inputChainPaths);
|
|
|
|
var signed = b.sign(keys);
|
2014-08-05 16:14:50 -07:00
|
|
|
|
2014-07-31 12:23:06 -07:00
|
|
|
if (options.fetch) return;
|
|
|
|
|
2014-08-05 15:14:31 -07:00
|
|
|
this.log('');
|
|
|
|
this.log('Created transaction:');
|
|
|
|
this.log(b.tx.getStandardizedObject());
|
|
|
|
this.log('');
|
|
|
|
|
2014-07-27 22:56:57 -07:00
|
|
|
var myId = this.getMyCopayerId();
|
|
|
|
var now = Date.now();
|
|
|
|
|
|
|
|
var tx = b.build();
|
2014-08-06 17:17:01 -07:00
|
|
|
if (!tx.countInputSignatures(0))
|
|
|
|
throw new Error('Could not sign generated tx');
|
2014-07-27 22:56:57 -07:00
|
|
|
|
2014-08-06 17:17:01 -07:00
|
|
|
var me = {};
|
|
|
|
me[myId] = now;
|
2014-07-27 22:56:57 -07:00
|
|
|
var meSeen = {};
|
|
|
|
if (priv) meSeen[myId] = now;
|
|
|
|
|
2014-08-06 17:17:01 -07:00
|
|
|
var ntxid = this.txProposals.add(new TxProposal({
|
2014-07-27 22:56:57 -07:00
|
|
|
inputChainPaths: inputChainPaths,
|
|
|
|
signedBy: me,
|
|
|
|
seenBy: meSeen,
|
|
|
|
creator: myId,
|
|
|
|
createdTs: now,
|
|
|
|
builder: b,
|
2014-07-28 00:06:03 -07:00
|
|
|
comment: options.memo,
|
2014-07-28 09:48:45 -07:00
|
|
|
merchant: merchantData
|
2014-08-06 17:17:01 -07:00
|
|
|
}));
|
2014-07-27 22:56:57 -07:00
|
|
|
return ntxid;
|
2014-07-25 18:46:29 -07:00
|
|
|
};
|
|
|
|
|
2014-08-05 11:10:31 -07:00
|
|
|
// This essentially ensures that a copayer hasn't tampered with a
|
|
|
|
// PaymentRequest message from a payment server. It verifies the signature
|
|
|
|
// based on the cert, and checks to ensure the desired outputs are the same as
|
|
|
|
// the ones on the tx proposal.
|
2014-08-05 10:50:09 -07:00
|
|
|
Wallet.prototype.verifyPaymentRequest = function(ntxid) {
|
2014-08-05 12:46:57 -07:00
|
|
|
if (!ntxid) return false;
|
2014-08-05 10:50:09 -07:00
|
|
|
|
|
|
|
var txp = typeof ntxid !== 'object'
|
2014-08-06 17:17:01 -07:00
|
|
|
? this.txProposals.get(ntxid)
|
2014-08-05 10:50:09 -07:00
|
|
|
: ntxid;
|
|
|
|
|
|
|
|
// If we're not a payment protocol proposal, ignore.
|
|
|
|
if (!txp.merchant) return true;
|
|
|
|
|
|
|
|
// The copayer didn't send us the raw payment request, unverifiable.
|
|
|
|
if (!txp.merchant.raw) return false;
|
|
|
|
|
|
|
|
// var tx = txp.builder.tx;
|
|
|
|
var tx = txp.builder.build();
|
|
|
|
|
|
|
|
var data = new Buffer(txp.merchant.raw, 'hex');
|
|
|
|
data = PayPro.PaymentRequest.decode(data);
|
|
|
|
var pr = new PayPro();
|
|
|
|
pr = pr.makePaymentRequest(data);
|
|
|
|
|
|
|
|
// Verify the signature so we know this is the real request.
|
|
|
|
if (!pr.verify()) {
|
|
|
|
// Signature does not match cert. It may have
|
|
|
|
// been modified by an untrustworthy person.
|
|
|
|
// We should not sign this transaction proposal!
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
var details = pr.get('serialized_payment_details');
|
|
|
|
details = PayPro.PaymentDetails.decode(details);
|
|
|
|
var pd = new PayPro();
|
|
|
|
pd = pd.makePaymentDetails(details);
|
|
|
|
|
|
|
|
var outputs = pd.get('outputs');
|
|
|
|
|
2014-08-05 11:13:11 -07:00
|
|
|
if (tx.outs.length < outputs.length) {
|
|
|
|
// Outputs do not and cannot match.
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2014-08-06 10:46:13 -07:00
|
|
|
// Figure out whether the user is supposed
|
|
|
|
// to decide the value of the outputs.
|
|
|
|
var undecided = false;
|
|
|
|
var total = bignum('0', 10);
|
|
|
|
for (var i = 0; i < outputs.length; i++) {
|
|
|
|
var output = outputs[i];
|
|
|
|
var amount = output.get('amount');
|
2014-08-06 12:49:48 -07:00
|
|
|
// big endian
|
2014-08-06 10:46:13 -07:00
|
|
|
var v = new Buffer(8);
|
|
|
|
v[0] = (amount.high >> 24) & 0xff;
|
|
|
|
v[1] = (amount.high >> 16) & 0xff;
|
|
|
|
v[2] = (amount.high >> 8) & 0xff;
|
|
|
|
v[3] = (amount.high >> 0) & 0xff;
|
|
|
|
v[4] = (amount.low >> 24) & 0xff;
|
|
|
|
v[5] = (amount.low >> 16) & 0xff;
|
|
|
|
v[6] = (amount.low >> 8) & 0xff;
|
|
|
|
v[7] = (amount.low >> 0) & 0xff;
|
|
|
|
total = total.add(bignum.fromBuffer(v, {
|
2014-08-06 13:07:16 -07:00
|
|
|
endian: 'big',
|
2014-08-06 10:46:13 -07:00
|
|
|
size: 1
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
if (+total.toString(10) === 0) {
|
|
|
|
undecided = true;
|
|
|
|
}
|
|
|
|
|
2014-08-05 10:50:09 -07:00
|
|
|
for (var i = 0; i < outputs.length; i++) {
|
|
|
|
var output = outputs[i];
|
|
|
|
|
|
|
|
var amount = output.get('amount');
|
|
|
|
var script = {
|
|
|
|
offset: output.get('script').offset,
|
|
|
|
limit: output.get('script').limit,
|
|
|
|
buffer: new Buffer(new Uint8Array(output.get('script').buffer))
|
|
|
|
};
|
|
|
|
|
|
|
|
// Expected value
|
2014-08-06 13:07:16 -07:00
|
|
|
// little endian (keep this LE to compare with tx output value)
|
2014-08-05 11:13:11 -07:00
|
|
|
var ev = new Buffer(8);
|
|
|
|
ev[0] = (amount.low >> 0) & 0xff;
|
|
|
|
ev[1] = (amount.low >> 8) & 0xff;
|
|
|
|
ev[2] = (amount.low >> 16) & 0xff;
|
|
|
|
ev[3] = (amount.low >> 24) & 0xff;
|
|
|
|
ev[4] = (amount.high >> 0) & 0xff;
|
|
|
|
ev[5] = (amount.high >> 8) & 0xff;
|
|
|
|
ev[6] = (amount.high >> 16) & 0xff;
|
|
|
|
ev[7] = (amount.high >> 24) & 0xff;
|
2014-08-05 10:50:09 -07:00
|
|
|
|
|
|
|
// Expected script
|
|
|
|
var es = script.buffer.slice(script.offset, script.limit);
|
|
|
|
|
|
|
|
// Actual value
|
2014-08-05 11:13:11 -07:00
|
|
|
var av = tx.outs[i].v;
|
2014-08-05 10:50:09 -07:00
|
|
|
|
|
|
|
// Actual script
|
2014-08-06 10:31:39 -07:00
|
|
|
var as = tx.outs[i].s;
|
2014-08-05 17:01:17 -07:00
|
|
|
|
|
|
|
// XXX allow changing of script as long as address is same
|
2014-08-06 10:31:39 -07:00
|
|
|
// var as = es;
|
2014-08-05 17:01:17 -07:00
|
|
|
|
|
|
|
// XXX allow changing of script as long as address is same
|
|
|
|
// var network = pd.get('network') === 'main' ? 'livenet' : 'testnet';
|
|
|
|
// var es = bitcore.Address.fromScriptPubKey(new bitcore.Script(es), network)[0];
|
|
|
|
// var as = bitcore.Address.fromScriptPubKey(new bitcore.Script(tx.outs[i].s), network)[0];
|
2014-08-05 10:50:09 -07:00
|
|
|
|
2014-08-06 10:46:13 -07:00
|
|
|
if (undecided) {
|
|
|
|
av = ev = new Buffer([0]);
|
|
|
|
}
|
|
|
|
|
2014-08-05 10:50:09 -07:00
|
|
|
// Make sure the tx's output script and values match the payment request's.
|
2014-08-05 11:13:11 -07:00
|
|
|
if (av.toString('hex') !== ev.toString('hex')
|
2014-08-05 10:50:09 -07:00
|
|
|
|| as.toString('hex') !== es.toString('hex')) {
|
|
|
|
// Verifiable outputs do not match outputs of merchant
|
|
|
|
// data. We should not sign this transaction proposal!
|
|
|
|
return false;
|
|
|
|
}
|
2014-08-05 12:49:50 -07:00
|
|
|
|
|
|
|
// Checking the merchant data itself isn't technically
|
|
|
|
// necessary as long as we check the transaction, but
|
|
|
|
// we can do it for good measure.
|
|
|
|
var ro = txp.merchant.pr.pd.outputs[i];
|
|
|
|
|
|
|
|
// Actual value
|
2014-08-06 13:07:16 -07:00
|
|
|
// little endian (keep this LE to compare with the ev above)
|
2014-08-05 12:49:50 -07:00
|
|
|
var av = new Buffer(8);
|
|
|
|
av[0] = (ro.amount.low >> 0) & 0xff;
|
|
|
|
av[1] = (ro.amount.low >> 8) & 0xff;
|
|
|
|
av[2] = (ro.amount.low >> 16) & 0xff;
|
|
|
|
av[3] = (ro.amount.low >> 24) & 0xff;
|
|
|
|
av[4] = (ro.amount.high >> 0) & 0xff;
|
|
|
|
av[5] = (ro.amount.high >> 8) & 0xff;
|
|
|
|
av[6] = (ro.amount.high >> 16) & 0xff;
|
|
|
|
av[7] = (ro.amount.high >> 24) & 0xff;
|
|
|
|
|
|
|
|
// Actual script
|
|
|
|
var as = new Buffer(ro.script.buffer, 'hex')
|
|
|
|
.slice(ro.script.offset, ro.script.limit);
|
|
|
|
|
|
|
|
if (av.toString('hex') !== ev.toString('hex')
|
|
|
|
|| as.toString('hex') !== es.toString('hex')) {
|
|
|
|
return false;
|
|
|
|
}
|
2014-08-05 10:50:09 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
2014-07-25 18:46:29 -07:00
|
|
|
Wallet.prototype.addSeenToTxProposals = function() {
|
|
|
|
var ret = false;
|
|
|
|
var myId = this.getMyCopayerId();
|
|
|
|
|
|
|
|
for (var k in this.txProposals.txps) {
|
|
|
|
var txp = this.txProposals.txps[k];
|
|
|
|
if (!txp.seenBy[myId]) {
|
|
|
|
|
|
|
|
txp.seenBy[myId] = Date.now();
|
|
|
|
ret = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
};
|
2014-04-15 11:25:55 -07:00
|
|
|
|
2014-04-30 15:50:13 -07:00
|
|
|
// TODO: remove this method and use getAddressesInfo everywhere
|
2014-05-01 06:07:30 -07:00
|
|
|
Wallet.prototype.getAddresses = function(opts) {
|
|
|
|
return this.publicKeyRing.getAddresses(opts);
|
2014-04-15 11:50:22 -07:00
|
|
|
};
|
|
|
|
|
2014-05-01 06:07:30 -07:00
|
|
|
Wallet.prototype.getAddressesStr = function(opts) {
|
|
|
|
return this.getAddresses(opts).map(function(a) {
|
2014-04-30 08:25:33 -07:00
|
|
|
return a.toString();
|
2014-04-15 14:23:35 -07:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2014-05-01 07:04:21 -07:00
|
|
|
Wallet.prototype.getAddressesInfo = function(opts) {
|
2014-07-03 09:04:01 -07:00
|
|
|
return this.publicKeyRing.getAddressesInfo(opts, this.publicKey);
|
2014-04-30 15:50:13 -07:00
|
|
|
};
|
|
|
|
|
2014-05-01 07:04:21 -07:00
|
|
|
Wallet.prototype.addressIsOwn = function(addrStr, opts) {
|
|
|
|
var addrList = this.getAddressesStr(opts);
|
2014-04-18 07:19:39 -07:00
|
|
|
var l = addrList.length;
|
|
|
|
var ret = false;
|
|
|
|
|
2014-04-24 16:56:36 -07:00
|
|
|
for (var i = 0; i < l; i++) {
|
2014-04-18 07:19:39 -07:00
|
|
|
if (addrList[i] === addrStr) {
|
2014-04-24 16:56:36 -07:00
|
|
|
ret = true;
|
2014-04-18 07:19:39 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
};
|
|
|
|
|
2014-06-16 08:44:18 -07:00
|
|
|
//retunrs values in SATOSHIs
|
2014-05-15 13:43:41 -07:00
|
|
|
Wallet.prototype.getBalance = function(cb) {
|
2014-04-18 09:20:35 -07:00
|
|
|
var balance = 0;
|
2014-05-15 13:43:41 -07:00
|
|
|
var safeBalance = 0;
|
2014-04-18 09:20:35 -07:00
|
|
|
var balanceByAddr = {};
|
2014-06-12 13:42:26 -07:00
|
|
|
var COIN = coinUtil.COIN;
|
2014-05-15 13:43:41 -07:00
|
|
|
|
2014-05-21 14:03:11 -07:00
|
|
|
this.getUnspent(function(err, safeUnspent, unspent) {
|
|
|
|
if (err) {
|
|
|
|
return cb(err);
|
|
|
|
}
|
|
|
|
|
2014-05-15 13:43:41 -07:00
|
|
|
for (var i = 0; i < unspent.length; i++) {
|
|
|
|
var u = unspent[i];
|
2014-04-18 09:20:35 -07:00
|
|
|
var amt = u.amount * COIN;
|
2014-04-30 08:25:33 -07:00
|
|
|
balance += amt;
|
2014-04-24 16:56:36 -07:00
|
|
|
balanceByAddr[u.address] = (balanceByAddr[u.address] || 0) + amt;
|
2014-04-17 07:51:56 -07:00
|
|
|
}
|
2014-04-30 08:25:33 -07:00
|
|
|
|
2014-06-12 13:42:26 -07:00
|
|
|
// we multiply and divide by BIT to avoid rounding errors when adding
|
2014-04-24 16:56:36 -07:00
|
|
|
for (var a in balanceByAddr) {
|
2014-06-16 08:44:18 -07:00
|
|
|
balanceByAddr[a] = parseInt(balanceByAddr[a].toFixed(0));
|
2014-04-21 08:00:14 -07:00
|
|
|
}
|
2014-06-16 08:44:18 -07:00
|
|
|
|
|
|
|
balance = parseInt(balance.toFixed(0));
|
2014-04-15 14:23:35 -07:00
|
|
|
|
2014-05-15 13:43:41 -07:00
|
|
|
for (var i = 0; i < safeUnspent.length; i++) {
|
|
|
|
var u = safeUnspent[i];
|
|
|
|
var amt = u.amount * COIN;
|
|
|
|
safeBalance += amt;
|
|
|
|
}
|
2014-05-21 14:03:11 -07:00
|
|
|
|
2014-06-16 08:44:18 -07:00
|
|
|
safeBalance = parseInt(safeBalance.toFixed(0));
|
2014-05-21 14:03:11 -07:00
|
|
|
return cb(null, balance, balanceByAddr, safeBalance);
|
2014-04-17 12:42:27 -07:00
|
|
|
});
|
2014-04-15 14:23:35 -07:00
|
|
|
};
|
|
|
|
|
2014-05-15 13:43:41 -07:00
|
|
|
Wallet.prototype.getUnspent = function(cb) {
|
2014-04-21 08:37:32 -07:00
|
|
|
var self = this;
|
2014-05-21 14:03:11 -07:00
|
|
|
this.blockchain.getUnspent(this.getAddressesStr(), function(err, unspentList) {
|
|
|
|
|
|
|
|
if (err) {
|
|
|
|
return cb(err);
|
|
|
|
}
|
2014-04-21 08:37:32 -07:00
|
|
|
|
2014-05-15 13:43:41 -07:00
|
|
|
var safeUnspendList = [];
|
2014-04-22 22:01:54 -07:00
|
|
|
var maxRejectCount = self.totalCopayers - self.requiredCopayers;
|
|
|
|
var uu = self.txProposals.getUsedUnspent(maxRejectCount);
|
2014-04-21 08:37:32 -07:00
|
|
|
|
2014-04-24 16:56:36 -07:00
|
|
|
for (var i in unspentList) {
|
2014-06-13 07:30:48 -07:00
|
|
|
var u = unspentList[i];
|
2014-06-19 06:30:53 -07:00
|
|
|
var name = u.txid + ',' + u.vout;
|
|
|
|
if (!uu[name] && (self.spendUnconfirmed || u.confirmations >= 1))
|
2014-06-05 10:55:19 -07:00
|
|
|
safeUnspendList.push(u);
|
2014-04-21 08:37:32 -07:00
|
|
|
}
|
2014-05-21 14:03:11 -07:00
|
|
|
|
|
|
|
return cb(null, safeUnspendList, unspentList);
|
2014-04-21 08:37:32 -07:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2014-06-13 06:33:30 -07:00
|
|
|
Wallet.prototype.createTx = function(toAddress, amountSatStr, comment, opts, cb) {
|
2014-04-16 13:50:10 -07:00
|
|
|
var self = this;
|
2014-07-28 10:44:45 -07:00
|
|
|
|
2014-07-28 14:46:08 -07:00
|
|
|
if (typeof amountSatStr === 'function') {
|
2014-07-28 10:44:45 -07:00
|
|
|
var cb = amountSatStr;
|
|
|
|
var merchant = toAddress;
|
|
|
|
return this.createPaymentTx({ uri: merchant }, cb);
|
|
|
|
}
|
|
|
|
|
2014-07-28 14:46:08 -07:00
|
|
|
if (typeof comment === 'function') {
|
|
|
|
var cb = comment;
|
|
|
|
var merchant = toAddress;
|
|
|
|
var comment = amountSatStr;
|
|
|
|
return this.createPaymentTx({ uri: merchant, memo: comment }, cb);
|
|
|
|
}
|
|
|
|
|
2014-04-16 13:50:10 -07:00
|
|
|
if (typeof opts === 'function') {
|
|
|
|
cb = opts;
|
|
|
|
opts = {};
|
|
|
|
}
|
|
|
|
opts = opts || {};
|
|
|
|
|
|
|
|
if (typeof opts.spendUnconfirmed === 'undefined') {
|
|
|
|
opts.spendUnconfirmed = this.spendUnconfirmed;
|
|
|
|
}
|
2014-04-21 08:37:32 -07:00
|
|
|
|
2014-05-21 14:03:11 -07:00
|
|
|
this.getUnspent(function(err, safeUnspent) {
|
2014-06-13 06:33:30 -07:00
|
|
|
var ntxid = self.createTxSync(toAddress, amountSatStr, comment, safeUnspent, opts);
|
2014-05-15 12:39:22 -07:00
|
|
|
if (ntxid) {
|
2014-06-04 10:24:46 -07:00
|
|
|
self.sendIndexes();
|
2014-06-18 06:09:40 -07:00
|
|
|
self.sendTxProposal(ntxid);
|
2014-04-22 18:07:18 -07:00
|
|
|
self.store();
|
2014-05-13 08:37:10 -07:00
|
|
|
self.emit('txProposalsUpdated');
|
2014-04-22 18:07:18 -07:00
|
|
|
}
|
2014-05-21 11:14:48 -07:00
|
|
|
return cb(ntxid);
|
2014-04-16 13:50:10 -07:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2014-06-13 06:33:30 -07:00
|
|
|
Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos, opts) {
|
2014-04-24 16:56:36 -07:00
|
|
|
var pkr = this.publicKeyRing;
|
2014-04-15 14:23:35 -07:00
|
|
|
var priv = this.privateKey;
|
|
|
|
opts = opts || {};
|
|
|
|
|
2014-08-03 19:57:23 -07:00
|
|
|
preconditions.checkArgument(new Address(toAddress).network().name === this.getNetworkName(), 'networkname mismatch');
|
|
|
|
preconditions.checkState(pkr.isComplete(), 'pubkey ring incomplete');
|
2014-08-05 12:25:02 -07:00
|
|
|
preconditions.checkState(priv, 'no private key');
|
2014-07-24 17:18:38 -07:00
|
|
|
if (comment) preconditions.checkArgument(comment.length <= 100);
|
2014-06-13 06:33:30 -07:00
|
|
|
|
2014-04-15 14:23:35 -07:00
|
|
|
if (!opts.remainderOut) {
|
2014-04-24 16:56:36 -07:00
|
|
|
opts.remainderOut = {
|
2014-05-16 14:33:06 -07:00
|
|
|
address: this._doGenerateAddress(true).toString()
|
2014-04-24 16:56:36 -07:00
|
|
|
};
|
2014-04-21 07:28:25 -07:00
|
|
|
}
|
2014-04-15 14:23:35 -07:00
|
|
|
|
2014-08-03 18:34:47 -07:00
|
|
|
for (var k in Wallet.builderOpts) {
|
2014-07-25 13:45:56 -07:00
|
|
|
opts[k] = Wallet.builderOpts[k];
|
|
|
|
}
|
|
|
|
|
2014-04-15 14:23:35 -07:00
|
|
|
var b = new Builder(opts)
|
2014-07-30 17:20:08 -07:00
|
|
|
.setUnspent(utxos)
|
|
|
|
.setOutputs([{
|
|
|
|
address: toAddress,
|
|
|
|
amountSatStr: amountSatStr,
|
|
|
|
}]);
|
2014-04-15 14:23:35 -07:00
|
|
|
|
2014-05-30 11:07:52 -07:00
|
|
|
var selectedUtxos = b.getSelectedUnspent();
|
|
|
|
var inputChainPaths = selectedUtxos.map(function(utxo) {
|
|
|
|
return pkr.pathForAddress(utxo.address);
|
|
|
|
});
|
|
|
|
|
2014-06-24 09:17:22 -07:00
|
|
|
b = b.setHashToScriptMap(pkr.getRedeemScriptMap(inputChainPaths));
|
|
|
|
|
2014-08-02 16:51:31 -07:00
|
|
|
var keys = priv.getForPaths(inputChainPaths);
|
|
|
|
var signed = b.sign(keys);
|
2014-04-23 09:44:20 -07:00
|
|
|
var myId = this.getMyCopayerId();
|
2014-04-20 16:24:24 -07:00
|
|
|
var now = Date.now();
|
|
|
|
|
2014-07-24 17:18:38 -07:00
|
|
|
|
|
|
|
var tx = b.build();
|
2014-08-03 18:34:47 -07:00
|
|
|
if (!tx.countInputSignatures(0))
|
|
|
|
throw new Error('Could not sign generated tx');
|
2014-08-02 16:51:31 -07:00
|
|
|
|
|
|
|
var me = {};
|
|
|
|
me[myId] = now;
|
2014-04-22 22:01:54 -07:00
|
|
|
|
|
|
|
var meSeen = {};
|
|
|
|
if (priv) meSeen[myId] = now;
|
2014-04-20 16:24:24 -07:00
|
|
|
|
2014-08-02 16:51:31 -07:00
|
|
|
var ntxid = this.txProposals.add(new TxProposal({
|
2014-05-30 11:07:52 -07:00
|
|
|
inputChainPaths: inputChainPaths,
|
2014-04-22 22:01:54 -07:00
|
|
|
signedBy: me,
|
2014-04-24 16:56:36 -07:00
|
|
|
seenBy: meSeen,
|
|
|
|
creator: myId,
|
2014-04-20 16:24:24 -07:00
|
|
|
createdTs: now,
|
2014-04-15 14:23:35 -07:00
|
|
|
builder: b,
|
2014-06-13 06:33:30 -07:00
|
|
|
comment: comment
|
2014-08-02 16:51:31 -07:00
|
|
|
}));
|
2014-05-30 11:07:52 -07:00
|
|
|
return ntxid;
|
2014-04-15 14:23:35 -07:00
|
|
|
};
|
|
|
|
|
2014-06-18 06:58:34 -07:00
|
|
|
Wallet.prototype.updateIndexes = function(callback) {
|
|
|
|
var self = this;
|
2014-07-02 06:43:00 -07:00
|
|
|
self.log('Updating indexes...');
|
2014-07-03 12:42:03 -07:00
|
|
|
|
|
|
|
var tasks = this.publicKeyRing.indexes.map(function(index) {
|
|
|
|
return function(callback) {
|
|
|
|
self.updateIndex(index, callback);
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
async.parallel(tasks, function(err) {
|
|
|
|
if (err) callback(err);
|
|
|
|
self.log('Indexes updated');
|
|
|
|
self.emit('publicKeyRingUpdated');
|
|
|
|
self.store();
|
|
|
|
callback();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
Wallet.prototype.updateIndex = function(index, callback) {
|
|
|
|
var self = this;
|
2014-07-07 08:26:42 -07:00
|
|
|
var SCANN_WINDOW = 20;
|
|
|
|
self.indexDiscovery(index.changeIndex, true, index.cosigner, SCANN_WINDOW, function(err, changeIndex) {
|
2014-06-18 06:58:34 -07:00
|
|
|
if (err) return callback(err);
|
|
|
|
if (changeIndex != -1)
|
2014-07-03 12:42:03 -07:00
|
|
|
index.changeIndex = changeIndex + 1;
|
2014-06-18 06:58:34 -07:00
|
|
|
|
2014-07-07 08:26:42 -07:00
|
|
|
self.indexDiscovery(index.receiveIndex, false, index.cosigner, SCANN_WINDOW, function(err, receiveIndex) {
|
2014-06-18 06:58:34 -07:00
|
|
|
if (err) return callback(err);
|
|
|
|
if (receiveIndex != -1)
|
2014-07-03 12:42:03 -07:00
|
|
|
index.receiveIndex = receiveIndex + 1;
|
2014-06-18 06:58:34 -07:00
|
|
|
callback();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2014-07-03 12:42:03 -07:00
|
|
|
Wallet.prototype.deriveAddresses = function(index, amout, isChange, cosigner) {
|
2014-06-18 06:58:34 -07:00
|
|
|
var ret = new Array(amout);
|
2014-06-24 08:36:32 -07:00
|
|
|
for (var i = 0; i < amout; i++) {
|
2014-07-03 12:42:03 -07:00
|
|
|
ret[i] = this.publicKeyRing.getAddress(index + i, isChange, cosigner).toString();
|
2014-06-18 06:58:34 -07:00
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
// This function scans the publicKeyRing branch starting at index @start and reports the index with last activity,
|
|
|
|
// using a scan window of @gap. The argument @change defines the branch to scan: internal or external.
|
|
|
|
// Returns -1 if no activity is found in range.
|
2014-07-03 12:42:03 -07:00
|
|
|
Wallet.prototype.indexDiscovery = function(start, change, cosigner, gap, cb) {
|
2014-06-18 06:58:34 -07:00
|
|
|
var scanIndex = start;
|
|
|
|
var lastActive = -1;
|
|
|
|
var hasActivity = false;
|
|
|
|
|
|
|
|
var self = this;
|
|
|
|
async.doWhilst(
|
|
|
|
function _do(next) {
|
2014-07-30 17:20:08 -07:00
|
|
|
// Optimize window to minimize the derivations.
|
|
|
|
var scanWindow = (lastActive == -1) ? gap : gap - (scanIndex - lastActive) + 1;
|
|
|
|
var addresses = self.deriveAddresses(scanIndex, scanWindow, change, cosigner);
|
|
|
|
self.blockchain.checkActivity(addresses, function(err, actives) {
|
|
|
|
if (err) throw err;
|
|
|
|
|
|
|
|
// Check for new activities in the newlly scanned addresses
|
|
|
|
var recentActive = actives.reduce(function(r, e, i) {
|
|
|
|
return e ? scanIndex + i : r;
|
|
|
|
}, lastActive);
|
|
|
|
hasActivity = lastActive != recentActive;
|
|
|
|
lastActive = recentActive;
|
|
|
|
scanIndex += scanWindow;
|
|
|
|
next();
|
|
|
|
});
|
|
|
|
},
|
|
|
|
function _while() {
|
|
|
|
return hasActivity;
|
|
|
|
},
|
|
|
|
function _finnaly(err) {
|
|
|
|
if (err) return cb(err);
|
|
|
|
cb(null, lastActive);
|
|
|
|
}
|
2014-06-18 06:58:34 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-04-16 16:58:57 -07:00
|
|
|
Wallet.prototype.disconnect = function() {
|
2014-04-24 19:13:55 -07:00
|
|
|
this.log('## DISCONNECTING');
|
2014-08-12 12:26:15 -07:00
|
|
|
this.unlock();
|
2014-04-16 16:58:57 -07:00
|
|
|
this.network.disconnect();
|
|
|
|
};
|
|
|
|
|
2014-04-23 17:20:44 -07:00
|
|
|
Wallet.prototype.getNetwork = function() {
|
|
|
|
return this.network;
|
|
|
|
};
|
|
|
|
|
2014-06-18 16:18:13 -07:00
|
|
|
Wallet.prototype._checkAddressBook = function(key) {
|
|
|
|
if (this.addressBook[key] && this.addressBook[key].copayerId != -1) {
|
|
|
|
throw new Error('This address already exists in your Address Book: ' + address);
|
2014-06-17 21:00:32 -07:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-06-18 16:18:13 -07:00
|
|
|
Wallet.prototype.setAddressBook = function(key, label) {
|
|
|
|
this._checkAddressBook(key);
|
2014-07-06 21:33:39 -07:00
|
|
|
var copayerId = this.getMyCopayerId();
|
|
|
|
var ts = Date.now();
|
|
|
|
var payload = {
|
|
|
|
address: key,
|
|
|
|
label: label,
|
|
|
|
copayerId: copayerId,
|
|
|
|
createdTs: ts
|
|
|
|
};
|
2014-07-07 06:58:43 -07:00
|
|
|
var newEntry = {
|
2014-07-06 21:33:39 -07:00
|
|
|
hidden: false,
|
|
|
|
createdTs: ts,
|
|
|
|
copayerId: copayerId,
|
|
|
|
label: label,
|
2014-07-07 06:58:43 -07:00
|
|
|
signature: this.signJson(payload)
|
2014-06-18 16:18:13 -07:00
|
|
|
};
|
2014-07-07 06:58:43 -07:00
|
|
|
this.addressBook[key] = newEntry;
|
2014-06-18 16:18:13 -07:00
|
|
|
this.sendAddressBook();
|
2014-06-17 21:00:32 -07:00
|
|
|
this.store();
|
|
|
|
};
|
|
|
|
|
2014-07-07 16:01:50 -07:00
|
|
|
Wallet.prototype.verifyAddressbookEntry = function(rcvEntry, senderId, key) {
|
2014-07-07 13:38:17 -07:00
|
|
|
if (!key) throw new Error('Keys are required');
|
2014-07-07 16:01:50 -07:00
|
|
|
var signature = rcvEntry.signature;
|
2014-07-07 10:53:34 -07:00
|
|
|
var payload = {
|
|
|
|
address: key,
|
2014-07-07 16:01:50 -07:00
|
|
|
label: rcvEntry.label,
|
|
|
|
copayerId: rcvEntry.copayerId,
|
|
|
|
createdTs: rcvEntry.createdTs
|
2014-07-07 10:53:34 -07:00
|
|
|
};
|
2014-07-07 16:01:50 -07:00
|
|
|
return this.verifySignedJson(senderId, payload, signature);
|
2014-07-06 21:33:39 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
Wallet.prototype.toggleAddressBookEntry = function(key) {
|
2014-07-07 10:53:34 -07:00
|
|
|
if (!key) throw new Error('Key is required');
|
|
|
|
this.addressBook[key].hidden = !this.addressBook[key].hidden;
|
|
|
|
this.store();
|
2014-06-17 21:00:32 -07:00
|
|
|
};
|
|
|
|
|
2014-08-04 12:27:58 -07:00
|
|
|
Wallet.prototype.isShared = function() {
|
|
|
|
return this.totalCopayers > 1;
|
|
|
|
}
|
|
|
|
|
2014-06-25 13:14:12 -07:00
|
|
|
Wallet.prototype.isReady = function() {
|
2014-07-07 14:35:44 -07:00
|
|
|
var ret = this.publicKeyRing.isComplete() && this.publicKeyRing.isFullyBackup();
|
2014-06-25 13:14:12 -07:00
|
|
|
return ret;
|
|
|
|
};
|
|
|
|
|
2014-07-07 14:35:44 -07:00
|
|
|
Wallet.prototype.setBackupReady = function() {
|
|
|
|
this.publicKeyRing.setBackupReady();
|
2014-07-08 06:43:10 -07:00
|
|
|
this.sendPublicKeyRing();
|
|
|
|
this.store();
|
2014-06-25 13:14:12 -07:00
|
|
|
};
|
|
|
|
|
2014-07-07 06:58:43 -07:00
|
|
|
Wallet.prototype.signJson = function(payload) {
|
2014-07-06 21:33:39 -07:00
|
|
|
var key = new bitcore.Key();
|
|
|
|
key.private = new Buffer(this.getMyCopayerIdPriv(), 'hex');
|
|
|
|
key.regenerateSync();
|
|
|
|
var sign = bitcore.Message.sign(JSON.stringify(payload), key);
|
|
|
|
return sign.toString('hex');
|
|
|
|
}
|
|
|
|
|
2014-07-07 13:38:17 -07:00
|
|
|
Wallet.prototype.verifySignedJson = function(senderId, payload, signature) {
|
|
|
|
var pubkey = new Buffer(senderId, 'hex');
|
2014-07-06 21:33:39 -07:00
|
|
|
var sign = new Buffer(signature, 'hex');
|
|
|
|
var v = bitcore.Message.verifyWithPubKey(pubkey, JSON.stringify(payload), sign);
|
|
|
|
return v;
|
|
|
|
}
|
|
|
|
|
2014-08-01 19:45:24 -07:00
|
|
|
// NOTE: Angular $http module does not send ArrayBuffers correctly, so we're
|
|
|
|
// not going to use it. We'll have to write our own. Otherwise, we could
|
|
|
|
// hex-encoded our messages and decode them on the other side, but that
|
2014-08-04 10:28:55 -07:00
|
|
|
// deviates from BIP-70.
|
|
|
|
|
2014-08-01 19:45:24 -07:00
|
|
|
// if (typeof angular !== 'undefined') {
|
2014-08-06 09:58:16 -07:00
|
|
|
// var $http = angular.bootstrap().get('$http');
|
2014-08-01 19:45:24 -07:00
|
|
|
// }
|
|
|
|
|
2014-08-06 09:58:16 -07:00
|
|
|
Wallet.request = function(options, callback) {
|
2014-08-01 19:45:24 -07:00
|
|
|
if (typeof options === 'string') {
|
|
|
|
options = { uri: options };
|
|
|
|
}
|
|
|
|
|
|
|
|
options.method = options.method || 'GET';
|
|
|
|
options.headers = options.headers || {};
|
|
|
|
|
|
|
|
var ret = {
|
|
|
|
success: function(cb) {
|
|
|
|
this._success = cb;
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
error: function(cb) {
|
|
|
|
this._error = cb;
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
_success: function() {
|
|
|
|
;
|
|
|
|
},
|
|
|
|
_error: function(_, err) {
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
var method = (options.method || 'GET').toUpperCase();
|
|
|
|
var uri = options.uri || options.url;
|
|
|
|
var req = options;
|
|
|
|
|
|
|
|
req.headers = req.headers || {};
|
2014-08-04 10:28:55 -07:00
|
|
|
req.body = req.body || req.data || {};
|
2014-08-04 10:11:12 -07:00
|
|
|
|
2014-08-04 10:28:55 -07:00
|
|
|
var xhr = new XMLHttpRequest();
|
|
|
|
xhr.open(method, uri, true);
|
2014-08-04 10:11:12 -07:00
|
|
|
|
2014-08-04 10:28:55 -07:00
|
|
|
Object.keys(req.headers).forEach(function(key) {
|
|
|
|
var val = req.headers[key];
|
|
|
|
if (key === 'Content-Length') return;
|
|
|
|
if (key === 'Content-Transfer-Encoding') return;
|
|
|
|
xhr.setRequestHeader(key, val);
|
|
|
|
});
|
2014-08-01 19:45:24 -07:00
|
|
|
|
2014-08-04 10:28:55 -07:00
|
|
|
if (req.responseType) {
|
|
|
|
xhr.responseType = req.responseType;
|
|
|
|
}
|
2014-08-04 10:11:12 -07:00
|
|
|
|
2014-08-04 10:28:55 -07:00
|
|
|
xhr.onload = function(event) {
|
|
|
|
var response = xhr.response;
|
|
|
|
var buf = new Uint8Array(response);
|
|
|
|
var headers = {};
|
|
|
|
(xhr.getAllResponseHeaders() || '').replace(
|
|
|
|
/(?:\r?\n|^)([^:\r\n]+): *([^\r\n]+)/g,
|
|
|
|
function($0, $1, $2) {
|
|
|
|
headers[$1.toLowerCase()] = $2;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
return ret._success(buf, xhr.status, headers, options);
|
|
|
|
};
|
2014-08-04 10:11:12 -07:00
|
|
|
|
2014-08-04 10:28:55 -07:00
|
|
|
xhr.onerror = function(event) {
|
|
|
|
return ret._error(null, new Error(event.message), null, options);
|
|
|
|
};
|
2014-08-04 10:11:12 -07:00
|
|
|
|
2014-08-04 10:28:55 -07:00
|
|
|
if (req.body) {
|
|
|
|
xhr.send(req.body);
|
|
|
|
} else {
|
|
|
|
xhr.send(null);
|
2014-08-04 10:14:26 -07:00
|
|
|
}
|
2014-08-04 10:11:12 -07:00
|
|
|
|
2014-08-01 19:45:24 -07:00
|
|
|
return ret;
|
|
|
|
};
|
|
|
|
|
2014-04-14 14:30:08 -07:00
|
|
|
module.exports = require('soop')(Wallet);
|