Browse Source

remove bitcore-wallet-utils dependency

activeAddress
Ivan Socolsky 9 years ago
parent
commit
eb006c3db3
  1. 4
      lib/emailservice.js
  2. 8
      lib/model/address.js
  3. 13
      lib/model/addressmanager.js
  4. 19
      lib/model/copayer.js
  5. 16
      lib/model/txproposal.js
  6. 13
      lib/model/wallet.js
  7. 125
      lib/server.js
  8. 152
      lib/utils.js
  9. 26
      package.json
  10. 141
      test/integration/server.js
  11. 3
      test/models/txproposal.js

4
lib/emailservice.js

@ -10,7 +10,7 @@ var fs = require('fs');
var path = require('path');
var nodemailer = require('nodemailer');
var WalletUtils = require('bitcore-wallet-utils');
var Utils = require('./utils');
var Storage = require('./storage');
var MessageBroker = require('./messagebroker');
var Lock = require('./lock');
@ -227,7 +227,7 @@ EmailService.prototype._getDataForTemplate = function(notification, recipient, c
if (data.amount) {
try {
var unit = recipient.unit.toLowerCase();
data.amount = WalletUtils.formatAmount(+data.amount, unit) + ' ' + UNIT_LABELS[unit];
data.amount = Utils.formatAmount(+data.amount, unit) + ' ' + UNIT_LABELS[unit];
} catch (ex) {
return cb(new Error('Could not format amount', ex));
}

8
lib/model/address.js

@ -1,7 +1,7 @@
'use strict';
var WalletUtils = require('bitcore-wallet-utils');
var Bitcore = WalletUtils.Bitcore;
var Bitcore = require('bitcore-lib');
var Constants = require('../constants');
function Address() {};
@ -18,7 +18,7 @@ Address.create = function(opts) {
x.path = opts.path;
x.publicKeys = opts.publicKeys;
x.network = Bitcore.Address(x.address).toObject().network;
x.type = opts.type || WalletUtils.SCRIPT_TYPES.P2SH;
x.type = opts.type || Constants.SCRIPT_TYPES.P2SH;
x.hasActivity = undefined;
return x;
};
@ -34,7 +34,7 @@ Address.fromObj = function(obj) {
x.isChange = obj.isChange;
x.path = obj.path;
x.publicKeys = obj.publicKeys;
x.type = obj.type || WalletUtils.SCRIPT_TYPES.P2SH;
x.type = obj.type || Constants.SCRIPT_TYPES.P2SH;
x.hasActivity = obj.hasActivity;
return x;
};

13
lib/model/addressmanager.js

@ -1,7 +1,8 @@
var _ = require('lodash');
var $ = require('preconditions').singleton();
var WalletUtils = require('bitcore-wallet-utils');
var Bitcore = require('bitcore-lib');
var Constants = require('../constants');
var BIP45_SHARED_INDEX = 0x80000000 - 1;
@ -13,8 +14,8 @@ AddressManager.create = function(opts) {
var x = new AddressManager();
x.version = 2;
x.derivationStrategy = opts.derivationStrategy || WalletUtils.DERIVATION_STRATEGIES.BIP45;
$.checkState(_.contains(_.values(WalletUtils.DERIVATION_STRATEGIES), x.derivationStrategy));
x.derivationStrategy = opts.derivationStrategy || Constants.DERIVATION_STRATEGIES.BIP45;
$.checkState(_.contains(_.values(Constants.DERIVATION_STRATEGIES), x.derivationStrategy));
x.receiveAddressIndex = 0;
x.changeAddressIndex = 0;
@ -27,7 +28,7 @@ AddressManager.fromObj = function(obj) {
var x = new AddressManager();
x.version = obj.version;
x.derivationStrategy = obj.derivationStrategy || WalletUtils.DERIVATION_STRATEGIES.BIP45;
x.derivationStrategy = obj.derivationStrategy || Constants.DERIVATION_STRATEGIES.BIP45;
x.receiveAddressIndex = obj.receiveAddressIndex;
x.changeAddressIndex = obj.changeAddressIndex;
x.copayerIndex = obj.copayerIndex;
@ -36,7 +37,7 @@ AddressManager.fromObj = function(obj) {
};
AddressManager.supportsCopayerBranches = function(derivationStrategy) {
return derivationStrategy == WalletUtils.DERIVATION_STRATEGIES.BIP45;
return derivationStrategy == Constants.DERIVATION_STRATEGIES.BIP45;
};
AddressManager.prototype._incrementIndex = function(isChange) {
@ -58,7 +59,7 @@ AddressManager.prototype.rewindIndex = function(isChange, n) {
AddressManager.prototype.getCurrentAddressPath = function(isChange) {
return 'm/' +
(this.derivationStrategy == WalletUtils.DERIVATION_STRATEGIES.BIP45 ? this.copayerIndex + '/' : '') +
(this.derivationStrategy == Constants.DERIVATION_STRATEGIES.BIP45 ? this.copayerIndex + '/' : '') +
(isChange ? 1 : 0) + '/' +
(isChange ? this.changeAddressIndex : this.receiveAddressIndex);
};

19
lib/model/copayer.js

@ -3,17 +3,22 @@
var $ = require('preconditions').singleton();
var _ = require('lodash');
var util = require('util');
var Uuid = require('uuid');
var sjcl = require('sjcl');
var Address = require('./address');
var AddressManager = require('./addressmanager');
var WalletUtils = require('bitcore-wallet-utils');
var Bitcore = WalletUtils.Bitcore;
var HDPublicKey = Bitcore.HDPublicKey;
var Bitcore = require('bitcore-lib');
var Constants = require('../constants');
var Utils = require('../utils');
function Copayer() {};
Copayer._xPubToCopayerId = function(xpub) {
var hash = sjcl.hash.sha256.hash(xpub);
return sjcl.codec.hex.fromBits(hash);
};
Copayer.create = function(opts) {
opts = opts || {};
$.checkArgument(opts.xPubKey, 'Missing copayer extended public key')
@ -27,7 +32,7 @@ Copayer.create = function(opts) {
x.version = 2;
x.createdOn = Math.floor(Date.now() / 1000);
x.xPubKey = opts.xPubKey;
x.id = WalletUtils.xPubToCopayerId(x.xPubKey);
x.id = Copayer._xPubToCopayerId(x.xPubKey);
x.name = opts.name;
x.requestPubKey = opts.requestPubKey;
x.signature = opts.signature;
@ -36,7 +41,7 @@ Copayer.create = function(opts) {
signature: opts.signature,
}];
var derivationStrategy = opts.derivationStrategy || WalletUtils.DERIVATION_STRATEGIES.BIP45;
var derivationStrategy = opts.derivationStrategy || Constants.DERIVATION_STRATEGIES.BIP45;
if (AddressManager.supportsCopayerBranches(derivationStrategy)) {
x.addressManager = AddressManager.create({
derivationStrategy: derivationStrategy,
@ -82,7 +87,7 @@ Copayer.prototype.createAddress = function(wallet, isChange) {
$.checkState(wallet.isComplete());
var path = this.addressManager.getNewAddressPath(isChange);
var raw = Address.create(WalletUtils.deriveAddress(wallet.addressType, wallet.publicKeyRing, path, wallet.m, wallet.network));
var raw = Address.create(Utils.deriveAddress(wallet.addressType, wallet.publicKeyRing, path, wallet.m, wallet.network));
var address = Address.create(_.extend(raw, {
walletId: wallet.id,
type: wallet.addressType,

16
lib/model/txproposal.js

@ -7,9 +7,9 @@ var log = require('npmlog');
log.debug = log.verbose;
log.disableColor();
var WalletUtils = require('bitcore-wallet-utils');
var Bitcore = WalletUtils.Bitcore;
var Address = Bitcore.Address;
var Bitcore = require('bitcore-lib');
var Constants = require('../constants');
var Utils = require('../utils');
var TxProposalAction = require('./txproposalaction');
@ -76,8 +76,8 @@ TxProposal.create = function(opts) {
x.excludeUnconfirmedUtxos = opts.excludeUnconfirmedUtxos;
x.proposalSignaturePubKey = opts.proposalSignaturePubKey;
x.proposalSignaturePubKeySig = opts.proposalSignaturePubKeySig;
x.derivationStrategy = opts.derivationStrategy || WalletUtils.DERIVATION_STRATEGIES.BIP45;
x.addressType = opts.addressType || WalletUtils.SCRIPT_TYPES.P2SH;
x.derivationStrategy = opts.derivationStrategy || Constants.DERIVATION_STRATEGIES.BIP45;
x.addressType = opts.addressType || Constants.SCRIPT_TYPES.P2SH;
x.customData = opts.customData;
if (_.isFunction(TxProposal._create[x.type])) {
@ -125,8 +125,8 @@ TxProposal.fromObj = function(obj) {
x.excludeUnconfirmedUtxos = obj.excludeUnconfirmedUtxos;
x.proposalSignaturePubKey = obj.proposalSignaturePubKey;
x.proposalSignaturePubKeySig = obj.proposalSignaturePubKeySig;
x.derivationStrategy = obj.derivationStrategy || WalletUtils.DERIVATION_STRATEGIES.BIP45;
x.addressType = obj.addressType || WalletUtils.SCRIPT_TYPES.P2SH;
x.derivationStrategy = obj.derivationStrategy || Constants.DERIVATION_STRATEGIES.BIP45;
x.addressType = obj.addressType || Constants.SCRIPT_TYPES.P2SH;
x.customData = obj.customData;
return x;
@ -164,7 +164,7 @@ TxProposal.prototype._getCurrentSignatures = function() {
TxProposal.prototype.getBitcoreTx = function() {
var self = this;
var t = WalletUtils.buildTx(this);
var t = Utils.buildTx(this);
var sigs = this._getCurrentSignatures();
_.each(sigs, function(x) {

13
lib/model/wallet.js

@ -8,7 +8,8 @@ var Uuid = require('uuid');
var Address = require('./address');
var Copayer = require('./copayer');
var AddressManager = require('./addressmanager');
var WalletUtils = require('bitcore-wallet-utils');
var Constants = require('../constants');
var Utils = require('../utils');
function Wallet() {};
@ -29,8 +30,8 @@ Wallet.create = function(opts) {
x.copayers = [];
x.pubKey = opts.pubKey;
x.network = opts.network;
x.derivationStrategy = opts.derivationStrategy || WalletUtils.DERIVATION_STRATEGIES.BIP45;
x.addressType = opts.addressType || WalletUtils.SCRIPT_TYPES.P2SH;
x.derivationStrategy = opts.derivationStrategy || Constants.DERIVATION_STRATEGIES.BIP45;
x.addressType = opts.addressType || Constants.SCRIPT_TYPES.P2SH;
x.addressManager = AddressManager.create({
derivationStrategy: x.derivationStrategy,
@ -56,8 +57,8 @@ Wallet.fromObj = function(obj) {
});
x.pubKey = obj.pubKey;
x.network = obj.network;
x.derivationStrategy = obj.derivationStrategy || WalletUtils.DERIVATION_STRATEGIES.BIP45;
x.addressType = obj.addressType || WalletUtils.SCRIPT_TYPES.P2SH;
x.derivationStrategy = obj.derivationStrategy || Constants.DERIVATION_STRATEGIES.BIP45;
x.addressType = obj.addressType || Constants.SCRIPT_TYPES.P2SH;
x.addressManager = AddressManager.fromObj(obj.addressManager);
x.scanStatus = obj.scanStatus;
@ -153,7 +154,7 @@ Wallet.prototype.createAddress = function(isChange) {
var self = this;
var path = this.addressManager.getNewAddressPath(isChange);
var raw = WalletUtils.deriveAddress(this.addressType, this.publicKeyRing, path, this.m, this.network);
var raw = Utils.deriveAddress(this.addressType, this.publicKeyRing, path, this.m, this.network);
var address = Address.create(_.extend(raw, {
walletId: self.id,
type: self.addressType,

125
lib/server.js

@ -6,13 +6,11 @@ var log = require('npmlog');
log.debug = log.verbose;
log.disableColor();
var EmailValidator = require('email-validator');
var Stringify = require('json-stable-stringify');
var WalletUtils = require('bitcore-wallet-utils');
var Bitcore = WalletUtils.Bitcore;
var PublicKey = Bitcore.PublicKey;
var HDPublicKey = Bitcore.HDPublicKey;
var Address = Bitcore.Address;
var Bitcore = require('bitcore-lib');
var Constants = require('./constants');
var ClientError = require('./errors/clienterror');
var Errors = require('./errors/errordefinitions');
@ -51,52 +49,6 @@ function WalletService() {
this.notifyTicker = 0;
};
WalletService.SCRIPT_TYPES = {
P2SH: 'P2SH',
P2PKH: 'P2PKH',
};
WalletService.DERIVATION_STRATEGIES = {
BIP44: 'BIP44',
BIP45: 'BIP45',
};
WalletService.DEFAULT_FEE_PER_KB = 10000;
WalletService.MIN_FEE_PER_KB = 0;
WalletService.MAX_FEE_PER_KB = 1000000;
WalletService.MAX_TX_FEE = 1 * 1e8;
WalletService.MAX_KEYS = 100;
// Time after which a Tx proposal can be erased by any copayer. in seconds
WalletService.DELETE_LOCKTIME = 24 * 3600;
// Allowed consecutive txp rejections before backoff is applied.
WalletService.BACKOFF_OFFSET = 3;
// Time a copayer need to wait to create a new TX after her tx previous proposal we rejected. (incremental). in Minutes.
WalletService.BACKOFF_TIME = 2;
WalletService.MAX_MAIN_ADDRESS_GAP = 20;
// Fund scanning parameters
WalletService.SCAN_CONFIG = {
maxGap: WalletService.MAX_MAIN_ADDRESS_GAP,
};
WalletService.FEE_LEVELS = [{
name: 'priority',
nbBlocks: 1,
defaultValue: 50000
}, {
name: 'normal',
nbBlocks: 2,
defaultValue: 20000
}, {
name: 'economy',
nbBlocks: 6,
defaultValue: 10000
}];
/**
* Gets the current version of BWS
@ -247,11 +199,11 @@ WalletService.prototype.createWallet = function(opts, cb) {
opts.supportBIP44AndP2PKH = _.isBoolean(opts.supportBIP44AndP2PKH) ? opts.supportBIP44AndP2PKH : true;
var derivationStrategy = opts.supportBIP44AndP2PKH ? WalletService.DERIVATION_STRATEGIES.BIP44 : WalletService.DERIVATION_STRATEGIES.BIP45;
var addressType = (opts.n == 1 && opts.supportBIP44AndP2PKH) ? WalletService.SCRIPT_TYPES.P2PKH : WalletService.SCRIPT_TYPES.P2SH;
var derivationStrategy = opts.supportBIP44AndP2PKH ? Constants.DERIVATION_STRATEGIES.BIP44 : Constants.DERIVATION_STRATEGIES.BIP45;
var addressType = (opts.n == 1 && opts.supportBIP44AndP2PKH) ? Constants.SCRIPT_TYPES.P2PKH : Constants.SCRIPT_TYPES.P2SH;
try {
pubKey = new PublicKey.fromString(opts.pubKey);
pubKey = new Bitcore.PublicKey.fromString(opts.pubKey);
} catch (ex) {
return cb(new ClientError('Invalid public key'));
};
@ -367,6 +319,7 @@ WalletService.prototype.getStatus = function(opts, cb) {
});
};
/*
* Verifies a signature
* @param text
@ -374,10 +327,21 @@ WalletService.prototype.getStatus = function(opts, cb) {
* @param pubKeys
*/
WalletService.prototype._verifySignature = function(text, signature, pubkey) {
return WalletUtils.verifyMessage(text, signature, pubkey);
return Utils.verifyMessage(text, signature, pubkey);
};
/*
* Verifies a request public key
* @param requestPubKey
* @param signature
* @param xPubKey
*/
WalletService.prototype._verifyRequestPubKey = function(requestPubKey, signature, xPubKey) {
var pub = (new Bitcore.HDPublicKey(xPubKey)).derive(Constants.PATHS.REQUEST_KEY_AUTH).publicKey;
return Utils.verifyMessage(requestPubKey, signature, pub.toString());
};
/*
* Verifies signature againt a collection of pubkeys
* @param text
@ -530,11 +494,11 @@ WalletService.prototype.addAccess = function(opts, cb) {
id: opts.copayerId
}).xPubKey;
if (!WalletUtils.verifyRequestPubKey(opts.requestPubKey, opts.signature, xPubKey)) {
if (!self._verifyRequestPubKey(opts.requestPubKey, opts.signature, xPubKey)) {
return cb(Errors.NOT_AUTHORIZED);
}
if (copayer.requestPubKeys.length > WalletService.MAX_KEYS)
if (copayer.requestPubKeys.length > Constants.MAX_KEYS)
return cb(Errors.TOO_MANY_KEYS);
self._addKeyToCopayer(wallet, copayer, opts, cb);
@ -576,6 +540,10 @@ WalletService.prototype._clientSupportsTXPv2 = function() {
return true;
};
WalletService._getCopayerHash = function(name, xPubKey, requestPubKey) {
return [name, xPubKey, requestPubKey].join('|');
};
/**
* Joins a wallet in creation.
* @param {Object} opts
@ -606,17 +574,17 @@ WalletService.prototype.joinWallet = function(opts, cb) {
if (opts.supportBIP44AndP2PKH) {
// New client trying to join legacy wallet
if (wallet.derivationStrategy == WalletService.DERIVATION_STRATEGIES.BIP45) {
if (wallet.derivationStrategy == Constants.DERIVATION_STRATEGIES.BIP45) {
return cb(new ClientError('The wallet you are trying to join was created with an older version of the client app.'));
}
} else {
// Legacy client trying to join new wallet
if (wallet.derivationStrategy == WalletService.DERIVATION_STRATEGIES.BIP44) {
if (wallet.derivationStrategy == Constants.DERIVATION_STRATEGIES.BIP44) {
return cb(new ClientError(Errors.codes.UPGRADE_NEEDED, 'To join this wallet you need to upgrade your client app.'));
}
}
var hash = WalletUtils.getCopayerHash(opts.name, opts.xPubKey, opts.requestPubKey);
var hash = WalletService._getCopayerHash(opts.name, opts.xPubKey, opts.requestPubKey);
if (!self._verifySignature(hash, opts.copayerSignature, wallet.pubKey)) {
return cb(new ClientError());
}
@ -714,8 +682,8 @@ WalletService.prototype._canCreateAddress = function(ignoreMaxGap, cb) {
if (err) return cb(err);
var latestAddresses = _.takeRight(_.reject(addresses, {
isChange: true
}), WalletService.MAX_MAIN_ADDRESS_GAP);
if (latestAddresses.length < WalletService.MAX_MAIN_ADDRESS_GAP || _.any(latestAddresses, {
}), Constants.MAX_MAIN_ADDRESS_GAP);
if (latestAddresses.length < Constants.MAX_MAIN_ADDRESS_GAP || _.any(latestAddresses, {
hasActivity: true
})) return cb(null, true);
@ -1034,7 +1002,7 @@ WalletService.prototype.getFeeLevels = function(opts, cb) {
if (network != 'livenet' && network != 'testnet')
return cb(new ClientError('Invalid network'));
var levels = WalletService.FEE_LEVELS;
var levels = Constants.FEE_LEVELS;
var samplePoints = _.uniq(_.pluck(levels, 'nbBlocks'));
self._sampleFeeLevels(network, samplePoints, function(err, feeSamples) {
var values = _.map(levels, function(level) {
@ -1157,7 +1125,7 @@ WalletService.prototype._selectTxInputs = function(txp, utxosToExclude, cb) {
WalletService.prototype._canCreateTx = function(copayerId, cb) {
var self = this;
self.storage.fetchLastTxs(self.walletId, copayerId, 5 + WalletService.BACKOFF_OFFSET, function(err, txs) {
self.storage.fetchLastTxs(self.walletId, copayerId, 5 + Constants.BACKOFF_OFFSET, function(err, txs) {
if (err) return cb(err);
if (!txs.length)
@ -1167,7 +1135,7 @@ WalletService.prototype._canCreateTx = function(copayerId, cb) {
status: 'rejected'
});
var exceededRejections = lastRejections.length - WalletService.BACKOFF_OFFSET;
var exceededRejections = lastRejections.length - Constants.BACKOFF_OFFSET;
if (exceededRejections <= 0)
return cb(null, true);
@ -1175,7 +1143,7 @@ WalletService.prototype._canCreateTx = function(copayerId, cb) {
var lastTxTs = txs[0].createdOn;
var now = Math.floor(Date.now() / 1000);
var timeSinceLastRejection = now - lastTxTs;
var backoffTime = 60 * Math.pow(WalletService.BACKOFF_TIME, exceededRejections);
var backoffTime = 60 * Math.pow(Constants.BACKOFF_TIME, exceededRejections);
if (timeSinceLastRejection <= backoffTime)
log.debug('Not allowing to create TX: timeSinceLastRejection/backoffTime', timeSinceLastRejection, backoffTime);
@ -1185,6 +1153,19 @@ WalletService.prototype._canCreateTx = function(copayerId, cb) {
};
WalletService._getProposalHash = function(proposalHeader) {
function getOldHash(toAddress, amount, message, payProUrl) {
return [toAddress, amount, (message || ''), (payProUrl || '')].join('|');
};
// For backwards compatibility
if (arguments.length > 1) {
return getOldHash.apply(this, arguments);
}
return Stringify(proposalHeader);
};
/**
* Creates a new transaction proposal.
* @param {Object} opts
@ -1225,8 +1206,8 @@ WalletService.prototype.createTx = function(opts, cb) {
valid: false
})) return;
var feePerKb = opts.feePerKb || WalletService.DEFAULT_FEE_PER_KB;
if (feePerKb < WalletService.MIN_FEE_PER_KB || feePerKb > WalletService.MAX_FEE_PER_KB)
var feePerKb = opts.feePerKb || Constants.DEFAULT_FEE_PER_KB;
if (feePerKb < Constants.MIN_FEE_PER_KB || feePerKb > Constants.MAX_FEE_PER_KB)
return cb(new ClientError('Invalid fee per KB value'));
self._runLocked(cb, function(cb) {
@ -1237,7 +1218,7 @@ WalletService.prototype.createTx = function(opts, cb) {
var copayer = wallet.getCopayer(self.copayerId);
var hash;
if (!opts.type || opts.type == Model.TxProposal.Types.SIMPLE) {
hash = WalletUtils.getProposalHash(opts.toAddress, opts.amount, opts.message, opts.payProUrl);
hash = WalletService._getProposalHash(opts.toAddress, opts.amount, opts.message, opts.payProUrl);
} else {
// should match bwc api _computeProposalSignature
var header = {
@ -1247,7 +1228,7 @@ WalletService.prototype.createTx = function(opts, cb) {
message: opts.message,
payProUrl: opts.payProUrl
};
hash = WalletUtils.getProposalHash(header)
hash = WalletService._getProposalHash(header)
}
var signingKey = self._getSigningKey(hash, opts.proposalSignature, copayer.requestPubKeys)
@ -1376,7 +1357,7 @@ WalletService.prototype.removeWallet = function(opts, cb) {
WalletService.prototype.getRemainingDeleteLockTime = function(txp) {
var now = Math.floor(Date.now() / 1000);
var lockTimeRemaining = txp.createdOn + WalletService.DELETE_LOCKTIME - now;
var lockTimeRemaining = txp.createdOn + Constants.DELETE_LOCKTIME - now;
if (lockTimeRemaining < 0)
return 0;
@ -1956,7 +1937,7 @@ WalletService.prototype.scan = function(opts, cb) {
function scanBranch(derivator, cb) {
var inactiveCounter = 0;
var allAddresses = [];
var gap = WalletService.SCAN_CONFIG.maxGap;
var gap = Constants.SCAN_CONFIG.maxGap;
async.whilst(function() {
return inactiveCounter < gap;

152
lib/utils.js

@ -1,6 +1,13 @@
var $ = require('preconditions').singleton();
var _ = require('lodash');
var Bitcore = require('bitcore-lib');
var Address = Bitcore.Address;
var crypto = Bitcore.crypto;
var encoding = Bitcore.encoding;
var Constants = require('./constants');
var Utils = {};
Utils.checkRequired = function(obj, args) {
@ -22,7 +29,6 @@ Utils.strip = function(number) {
return (parseFloat(number.toPrecision(12)));
}
/* TODO: It would be nice to be compatible with bitcoind signmessage. How
* the hash is calculated there? */
Utils.hashMessage = function(text) {
@ -33,15 +39,6 @@ Utils.hashMessage = function(text) {
return ret;
};
Utils.signMessage = function(text, privKey) {
$.checkArgument(text);
var priv = new PrivateKey(privKey);
var hash = Utils.hashMessage(text);
return crypto.ECDSA.sign(hash, priv, 'little').toString();
};
Utils.verifyMessage = function(text, signature, pubKey) {
$.checkArgument(text);
$.checkArgument(pubKey);
@ -49,7 +46,7 @@ Utils.verifyMessage = function(text, signature, pubKey) {
if (!signature)
return false;
var pub = new PublicKey(pubKey);
var pub = new Bitcore.PublicKey(pubKey);
var hash = Utils.hashMessage(text);
try {
@ -60,5 +57,138 @@ Utils.verifyMessage = function(text, signature, pubKey) {
}
};
Utils.newBitcoreTransaction = function() {
return new Bitcore.Transaction();
};
Utils.buildTx = function(txp) {
var t = Utils.newBitcoreTransaction();
$.checkState(_.contains(_.values(Constants.SCRIPT_TYPES), txp.addressType));
switch (txp.addressType) {
case Constants.SCRIPT_TYPES.P2SH:
_.each(txp.inputs, function(i) {
t.from(i, i.publicKeys, txp.requiredSignatures);
});
break;
case Constants.SCRIPT_TYPES.P2PKH:
t.from(txp.inputs);
break;
}
if (txp.toAddress && txp.amount && !txp.outputs) {
t.to(txp.toAddress, txp.amount);
} else if (txp.outputs) {
_.each(txp.outputs, function(o) {
$.checkState(!o.script != !o.toAddress, 'Output should have either toAddress or script specified');
if (o.script) {
t.addOutput(new Bitcore.Transaction.Output({
script: o.script,
satoshis: o.amount
}));
} else {
t.to(o.toAddress, o.amount);
}
});
}
if (_.startsWith(txp.version, '1.')) {
Bitcore.Transaction.FEE_SECURITY_MARGIN = 1;
t.feePerKb(txp.feePerKb);
} else {
t.fee(txp.fee);
}
t.change(txp.changeAddress.address);
// Shuffle outputs for improved privacy
if (t.outputs.length > 1) {
$.checkState(t.outputs.length == txp.outputOrder.length);
t.sortOutputs(function(outputs) {
return _.map(txp.outputOrder, function(i) {
return outputs[i];
});
});
}
// Validate inputs vs outputs independently of Bitcore
var totalInputs = _.reduce(txp.inputs, function(memo, i) {
return +i.satoshis + memo;
}, 0);
var totalOutputs = _.reduce(t.outputs, function(memo, o) {
return +o.satoshis + memo;
}, 0);
$.checkState(totalInputs - totalOutputs <= Constants.MAX_TX_FEE);
return t;
};
Utils.deriveAddress = function(scriptType, publicKeyRing, path, m, network) {
$.checkArgument(_.contains(_.values(Constants.SCRIPT_TYPES), scriptType));
var publicKeys = _.map(publicKeyRing, function(item) {
var xpub = new Bitcore.HDPublicKey(item.xPubKey);
return xpub.derive(path).publicKey;
});
var bitcoreAddress;
switch (scriptType) {
case Constants.SCRIPT_TYPES.P2SH:
bitcoreAddress = Address.createMultisig(publicKeys, m, network);
break;
case Constants.SCRIPT_TYPES.P2PKH:
$.checkState(_.isArray(publicKeys) && publicKeys.length == 1);
bitcoreAddress = Address.fromPublicKey(publicKeys[0], network);
break;
}
return {
address: bitcoreAddress.toString(),
path: path,
publicKeys: _.invoke(publicKeys, 'toString'),
};
};
Utils.formatAmount = function(satoshis, unit, opts) {
var UNITS = {
btc: {
toSatoshis: 100000000,
maxDecimals: 6,
minDecimals: 2,
},
bit: {
toSatoshis: 100,
maxDecimals: 0,
minDecimals: 0,
},
};
$.shouldBeNumber(satoshis);
$.checkArgument(_.contains(_.keys(UNITS), unit));
function addSeparators(nStr, thousands, decimal, minDecimals) {
nStr = nStr.replace('.', decimal);
var x = nStr.split(decimal);
var x0 = x[0];
var x1 = x[1];
x1 = _.dropRightWhile(x1, function(n, i) {
return n == '0' && i >= minDecimals;
}).join('');
var x2 = x.length > 1 ? decimal + x1 : '';
x0 = x0.replace(/\B(?=(\d{3})+(?!\d))/g, thousands);
return x0 + x2;
}
opts = opts || {};
var u = UNITS[unit];
var amount = (satoshis / u.toSatoshis).toFixed(u.maxDecimals);
return addSeparators(amount, opts.thousandsSeparator || ',', opts.decimalSeparator || '.', u.minDecimals);
};
module.exports = Utils;

26
package.json

@ -20,12 +20,12 @@
"dependencies": {
"async": "^0.9.0",
"bitcore-lib": "^0.13.7",
"bitcore-wallet-utils": "~1.0.0",
"body-parser": "^1.11.0",
"coveralls": "^2.11.2",
"email-validator": "^1.0.1",
"express": "^4.10.0",
"inherits": "^2.0.1",
"json-stable-stringify": "^1.0.0",
"locker": "^0.1.0",
"locker-server": "^0.1.3",
"lodash": "^3.10.1",
@ -64,14 +64,18 @@
"coveralls": "./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage"
},
"bitcoreNode": "./bitcorenode",
"contributors": [{
"name": "Braydon Fuller",
"email": "braydon@bitpay.com"
}, {
"name": "Ivan Socolsky",
"email": "ivan@bitpay.com"
}, {
"name": "Matias Alejo Garcia",
"email": "ematiu@gmail.com"
}]
"contributors": [
{
"name": "Braydon Fuller",
"email": "braydon@bitpay.com"
},
{
"name": "Ivan Socolsky",
"email": "ivan@bitpay.com"
},
{
"name": "Matias Alejo Garcia",
"email": "ematiu@gmail.com"
}
]
}

141
test/integration/server.js

@ -15,9 +15,10 @@ var tingodb = require('tingodb')({
memStore: true
});
var Bitcore = require('bitcore-lib');
var Constants = require('../../lib/constants');
var Utils = require('../../lib/utils');
var WalletUtils = require('bitcore-wallet-utils');
var Bitcore = WalletUtils.Bitcore;
var Storage = require('../../lib/storage');
var Model = require('../../lib/model');
@ -29,6 +30,18 @@ var TestData = require('../testdata');
var CLIENT_VERSION = 'bwc-0.1.1';
var helpers = {};
helpers.signMessage = function(text, privKey) {
var priv = new Bitcore.PrivateKey(privKey);
var hash = Utils.hashMessage(text);
return Bitcore.crypto.ECDSA.sign(hash, priv, 'little').toString();
};
helpers.signRequestPubKey = function(requestPubKey, xPrivKey) {
var priv = new Bitcore.HDPrivateKey(xPrivKey).derive(Constants.PATHS.REQUEST_KEY_AUTH).privateKey;
return helpers.signMessage(requestPubKey, priv);
};
helpers.getAuthServer = function(copayerId, cb) {
var verifyStub = sinon.stub(WalletService.prototype, '_verifySignature');
verifyStub.returns(true);
@ -52,11 +65,11 @@ helpers._generateCopayersTestData = function(n) {
var xpriv_45H = xpriv.derive(45, true);
var xpub_45H = Bitcore.HDPublicKey(xpriv_45H);
var id45 = WalletUtils.xPubToCopayerId(xpub_45H.toString());
var id45 = Copayer._xPubToCopayerId(xpub_45H.toString());
var xpriv_44H_0H_0H = xpriv.derive(44, true).derive(0, true).derive(0, true);
var xpub_44H_0H_0H = Bitcore.HDPublicKey(xpriv_44H_0H_0H);
var id44 = WalletUtils.xPubToCopayerId(xpub_44H_0H_0H.toString());
var id44 = Copayer._xPubToCopayerId(xpub_44H_0H_0H.toString());
var xpriv_1H = xpriv.derive(1, true);
var xpub_1H = Bitcore.HDPublicKey(xpriv_1H);
@ -80,8 +93,8 @@ helpers._generateCopayersTestData = function(n) {
};
helpers.getSignedCopayerOpts = function(opts) {
var hash = WalletUtils.getCopayerHash(opts.name, opts.xPubKey, opts.requestPubKey);
opts.copayerSignature = WalletUtils.signMessage(hash, TestData.keyPair.priv);
var hash = WalletService._getCopayerHash(opts.name, opts.xPubKey, opts.requestPubKey);
opts.copayerSignature = helpers.signMessage(hash, TestData.keyPair.priv);
return opts;
};
@ -141,7 +154,6 @@ helpers.randomTXID = function() {
return Bitcore.crypto.Hash.sha256(new Buffer(Math.random() * 100000)).toString('hex');;
};
helpers.toSatoshi = function(btc) {
if (_.isArray(btc)) {
return _.map(btc, helpers.toSatoshi);
@ -168,10 +180,10 @@ helpers.stubUtxos = function(server, wallet, amounts, cb) {
var scriptPubKey;
switch (wallet.addressType) {
case WalletUtils.SCRIPT_TYPES.P2SH:
case Constants.SCRIPT_TYPES.P2SH:
scriptPubKey = Bitcore.Script.buildMultisigOut(address.publicKeys, wallet.m).toScriptHashOut();
break;
case WalletUtils.SCRIPT_TYPES.P2PKH:
case Constants.SCRIPT_TYPES.P2PKH:
scriptPubKey = Bitcore.Script.buildPublicKeyHashOut(address.address);
break;
}
@ -242,7 +254,46 @@ helpers.stubAddressActivity = function(activeAddresses) {
};
};
helpers.clientSign = WalletUtils.signTxp;
helpers.clientSign = function(txp, xPrivKey) {
var self = this;
function getBaseAddressDerivationPath(derivationStrategy, network, account) {
if (derivationStrategy == Constants.DERIVATION_STRATEGIES.BIP45) return "m/45'";
return "m/44'/" + (network == 'livenet' ? "0'" : "1'") + "/" + account + "'";
};
function deriveXPrivFromMaster(masterXPriv, derivationStrategy, network, account) {
var path = getBaseAddressDerivationPath(derivationStrategy, network, account || 0);
return new Bitcore.HDPrivateKey(masterXPriv, network).derive(path);
};
//Derive proper key to sign, for each input
var privs = [];
var derived = {};
var network = new Bitcore.Address(txp.changeAddress.address).network.name;
var xpriv = deriveXPrivFromMaster(xPrivKey, txp.derivationStrategy, network);
_.each(txp.inputs, function(i) {
if (!derived[i.path]) {
derived[i.path] = xpriv.derive(i.path).privateKey;
privs.push(derived[i.path]);
}
});
var t = Utils.buildTx(txp);
var signatures = _.map(privs, function(priv, i) {
return t.getSignatures(priv);
});
signatures = _.map(_.sortBy(_.flatten(signatures), 'inputIndex'), function(s) {
return s.signature.toDER().toString('hex');
});
return signatures;
};
helpers.createProposalOptsLegacy = function(toAddress, amount, message, signingKey, feePerKb) {
var opts = {
@ -253,10 +304,10 @@ helpers.createProposalOptsLegacy = function(toAddress, amount, message, signingK
};
if (feePerKb) opts.feePerKb = feePerKb;
var hash = WalletUtils.getProposalHash(toAddress, opts.amount, message);
var hash = WalletService._getProposalHash(toAddress, opts.amount, message);
try {
opts.proposalSignature = WalletUtils.signMessage(hash, signingKey);
opts.proposalSignature = helpers.signMessage(hash, signingKey);
} catch (ex) {}
return opts;
@ -295,7 +346,7 @@ helpers.createProposalOpts = function(type, outputs, signingKey, moreOpts) {
if (type == Model.TxProposal.Types.SIMPLE) {
opts.toAddress = outputs[0].toAddress;
opts.amount = outputs[0].amount;
hash = WalletUtils.getProposalHash(opts.toAddress, opts.amount,
hash = WalletService._getProposalHash(opts.toAddress, opts.amount,
opts.message, opts.payProUrl);
} else if (type == Model.TxProposal.Types.MULTIPLEOUTPUTS) {
opts.outputs = outputs;
@ -304,11 +355,11 @@ helpers.createProposalOpts = function(type, outputs, signingKey, moreOpts) {
message: opts.message,
payProUrl: opts.payProUrl
};
hash = WalletUtils.getProposalHash(header);
hash = WalletService._getProposalHash(header);
}
try {
opts.proposalSignature = WalletUtils.signMessage(hash, signingKey);
opts.proposalSignature = helpers.signMessage(hash, signingKey);
} catch (ex) {}
return opts;
@ -836,7 +887,7 @@ describe('Wallet service', function() {
var xpriv = TestData.copayers[0].xPrivKey;
var priv = TestData.copayers[0].privKey_1H_0;
var sig = WalletUtils.signMessage('hello world', priv);
var sig = helpers.signMessage('hello world', priv);
WalletService.getInstanceWithAuth({
copayerId: wallet.copayers[0].id,
@ -858,7 +909,7 @@ describe('Wallet service', function() {
var opts = {
copayerId: 'dummy',
message: message,
signature: WalletUtils.signMessage(message, TestData.copayers[0].privKey_1H_0),
signature: helpers.signMessage(message, TestData.copayers[0].privKey_1H_0),
};
WalletService.getInstanceWithAuth(opts, function(err, server) {
err.code.should.equal('NOT_AUTHORIZED');
@ -1512,7 +1563,7 @@ describe('Wallet service', function() {
var message = 'hello world';
var opts = {
message: message,
signature: WalletUtils.signMessage(message, TestData.copayers[0].privKey_1H_0),
signature: helpers.signMessage(message, TestData.copayers[0].privKey_1H_0),
};
server.verifyMessageSignature(opts, function(err, isValid) {
should.not.exist(err);
@ -1525,7 +1576,7 @@ describe('Wallet service', function() {
var message = 'hello world';
var opts = {
message: message,
signature: WalletUtils.signMessage(message, TestData.copayers[0].privKey_1H_0),
signature: helpers.signMessage(message, TestData.copayers[0].privKey_1H_0),
};
helpers.getAuthServer(wallet.copayers[1].id, function(server) {
server.verifyMessageSignature(opts, function(err, isValid) {
@ -1719,8 +1770,8 @@ describe('Wallet service', function() {
});
it('should fail to create more consecutive addresses with no activity than allowed', function(done) {
var MAX_MAIN_ADDRESS_GAP_old = WalletService.MAX_MAIN_ADDRESS_GAP;
WalletService.MAX_MAIN_ADDRESS_GAP = 2;
var MAX_MAIN_ADDRESS_GAP_old = Constants.MAX_MAIN_ADDRESS_GAP;
Constants.MAX_MAIN_ADDRESS_GAP = 2;
helpers.stubAddressActivity([]);
async.map(_.range(2), function(i, next) {
server.createAddress({}, next);
@ -1746,7 +1797,7 @@ describe('Wallet service', function() {
should.exist(address);
address.path.should.equal('m/0/3');
WalletService.MAX_MAIN_ADDRESS_GAP = MAX_MAIN_ADDRESS_GAP_old;
Constants.MAX_MAIN_ADDRESS_GAP = MAX_MAIN_ADDRESS_GAP_old;
done();
});
});
@ -1755,8 +1806,8 @@ describe('Wallet service', function() {
});
it('should cache address activity', function(done) {
var MAX_MAIN_ADDRESS_GAP_old = WalletService.MAX_MAIN_ADDRESS_GAP;
WalletService.MAX_MAIN_ADDRESS_GAP = 2;
var MAX_MAIN_ADDRESS_GAP_old = Constants.MAX_MAIN_ADDRESS_GAP;
Constants.MAX_MAIN_ADDRESS_GAP = 2;
helpers.stubAddressActivity([]);
async.map(_.range(2), function(i, next) {
server.createAddress({}, next);
@ -1770,7 +1821,7 @@ describe('Wallet service', function() {
server.createAddress({}, function(err, address) {
should.not.exist(err);
getAddressActivitySpy.callCount.should.equal(1);
WalletService.MAX_MAIN_ADDRESS_GAP = MAX_MAIN_ADDRESS_GAP_old;
Constants.MAX_MAIN_ADDRESS_GAP = MAX_MAIN_ADDRESS_GAP_old;
done();
});
});
@ -2014,7 +2065,7 @@ describe('Wallet service', function() {
var opts, reqPrivKey, ws;
var getAuthServer = function(copayerId, privKey, cb) {
var msg = 'dummy';
var sig = WalletUtils.signMessage(msg, privKey);
var sig = helpers.signMessage(msg, privKey);
WalletService.getInstanceWithAuth({
copayerId: copayerId,
message: msg,
@ -2030,9 +2081,9 @@ describe('Wallet service', function() {
var requestPubKey = reqPrivKey.toPublicKey();
var xPrivKey = TestData.copayers[0].xPrivKey_44H_0H_0H;
var sig = WalletUtils.signRequestPubKey(requestPubKey, xPrivKey);
var sig = helpers.signRequestPubKey(requestPubKey, xPrivKey);
var copayerId = WalletUtils.xPubToCopayerId(TestData.copayers[0].xPubKey_44H_0H_0H);
var copayerId = Model.Copayer._xPubToCopayerId(TestData.copayers[0].xPubKey_44H_0H_0H);
opts = {
copayerId: copayerId,
requestPubKey: requestPubKey,
@ -2424,7 +2475,7 @@ describe('Wallet service', function() {
tx.isAccepted().should.equal.false;
tx.isRejected().should.equal.false;
tx.amount.should.equal(helpers.toSatoshi(80));
var estimatedFee = WalletUtils.DEFAULT_FEE_PER_KB * 400 / 1000; // fully signed tx should have about 400 bytes
var estimatedFee = Constants.DEFAULT_FEE_PER_KB * 400 / 1000; // fully signed tx should have about 400 bytes
tx.fee.should.be.within(0.9 * estimatedFee, 1.1 * estimatedFee);
server.getPendingTxs({}, function(err, txs) {
should.not.exist(err);
@ -2679,7 +2730,7 @@ describe('Wallet service', function() {
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 3.5, TestData.copayers[0].privKey_1H_0);
server.createTx(txOpts, function(err, tx) {
should.not.exist(err);
var estimatedFee = WalletUtils.DEFAULT_FEE_PER_KB * 1300 / 1000; // fully signed tx should have about 1300 bytes
var estimatedFee = Constants.DEFAULT_FEE_PER_KB * 1300 / 1000; // fully signed tx should have about 1300 bytes
tx.fee.should.be.within(0.9 * estimatedFee, 1.1 * estimatedFee);
done();
});
@ -3075,7 +3126,7 @@ describe('Wallet service', function() {
});
},
function(next) {
var clock = sinon.useFakeTimers(Date.now() + (WalletService.BACKOFF_TIME + 2) * 60 * 1000, 'Date');
var clock = sinon.useFakeTimers(Date.now() + (Constants.BACKOFF_TIME + 2) * 60 * 1000, 'Date');
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 1, TestData.copayers[0].privKey_1H_0);
server.createTx(txOpts, function(err, tx) {
clock.restore();
@ -3087,7 +3138,7 @@ describe('Wallet service', function() {
},
function(next) {
// Do not allow a 5th tx before backoff time
var clock = sinon.useFakeTimers(Date.now() + (WalletService.BACKOFF_TIME + 2) * 60 * 1000 + 1, 'Date');
var clock = sinon.useFakeTimers(Date.now() + (Constants.BACKOFF_TIME + 2) * 60 * 1000 + 1, 'Date');
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 1, TestData.copayers[0].privKey_1H_0);
server.createTx(txOpts, function(err, tx) {
clock.restore();
@ -4547,7 +4598,7 @@ describe('Wallet service', function() {
server.getPendingTxs({}, function(err, txs) {
should.not.exist(err);
txs[0].deleteLockTime.should.be.above(WalletService.DELETE_LOCKTIME - 10);
txs[0].deleteLockTime.should.be.above(Constants.DELETE_LOCKTIME - 10);
var clock = sinon.useFakeTimers(Date.now() + 1 + 24 * 3600 * 1000, 'Date');
server.removePendingTx({
@ -4572,7 +4623,7 @@ describe('Wallet service', function() {
}, function(err) {
should.not.exist(err);
var clock = sinon.useFakeTimers(Date.now() + 2000 + WalletService.DELETE_LOCKTIME * 1000, 'Date');
var clock = sinon.useFakeTimers(Date.now() + 2000 + Constants.DELETE_LOCKTIME * 1000, 'Date');
server2.removePendingTx({
txProposalId: txp.id
}, function(err) {
@ -4897,12 +4948,12 @@ describe('Wallet service', function() {
describe('#scan', function() {
var server, wallet;
var scanConfigOld = WalletService.SCAN_CONFIG;
var scanConfigOld = Constants.SCAN_CONFIG;
describe('1-of-1 wallet (BIP44 & P2PKH)', function() {
beforeEach(function(done) {
this.timeout(5000);
WalletService.SCAN_CONFIG.maxGap = 2;
Constants.SCAN_CONFIG.maxGap = 2;
helpers.createAndJoinWallet(1, 1, function(s, w) {
server = s;
@ -4911,7 +4962,7 @@ describe('Wallet service', function() {
});
});
afterEach(function() {
WalletService.SCAN_CONFIG = scanConfigOld;
Constants.SCAN_CONFIG = scanConfigOld;
});
it('should scan main addresses', function(done) {
@ -5094,7 +5145,7 @@ describe('Wallet service', function() {
beforeEach(function(done) {
this.timeout(5000);
WalletService.SCAN_CONFIG.maxGap = 2;
Constants.SCAN_CONFIG.maxGap = 2;
helpers.createAndJoinWallet(1, 2, {
supportBIP44AndP2PKH: false
@ -5105,7 +5156,7 @@ describe('Wallet service', function() {
});
});
afterEach(function() {
WalletService.SCAN_CONFIG = scanConfigOld;
Constants.SCAN_CONFIG = scanConfigOld;
});
it('should scan main addresses', function(done) {
@ -5173,10 +5224,10 @@ describe('Wallet service', function() {
describe('#startScan', function() {
var server, wallet;
var scanConfigOld = WalletService.SCAN_CONFIG;
var scanConfigOld = Constants.SCAN_CONFIG;
beforeEach(function(done) {
this.timeout(5000);
WalletService.SCAN_CONFIG.maxGap = 2;
Constants.SCAN_CONFIG.maxGap = 2;
helpers.createAndJoinWallet(1, 1, {
supportBIP44AndP2PKH: false
@ -5187,7 +5238,7 @@ describe('Wallet service', function() {
});
});
afterEach(function() {
WalletService.SCAN_CONFIG = scanConfigOld;
Constants.SCAN_CONFIG = scanConfigOld;
server.messageBroker.removeAllListeners();
});
@ -5245,7 +5296,7 @@ describe('Wallet service', function() {
});
it('should start multiple asynchronous scans for different wallets', function(done) {
helpers.stubAddressActivity(['3K2VWMXheGZ4qG35DyGjA2dLeKfaSr534A']);
WalletService.SCAN_CONFIG.scanWindow = 1;
Constants.SCAN_CONFIG.scanWindow = 1;
var scans = 0;
server.messageBroker.onMessage(function(n) {
@ -5321,7 +5372,7 @@ describe('Wallet service', function() {
should.not.exist(err);
should.exist(tx);
tx.amount.should.equal(helpers.toSatoshi(80));
tx.fee.should.equal(WalletUtils.DEFAULT_FEE_PER_KB);
tx.fee.should.equal(Constants.DEFAULT_FEE_PER_KB);
done();
});
});
@ -5411,7 +5462,7 @@ describe('Wallet service', function() {
should.not.exist(err);
should.exist(tx);
tx.amount.should.equal(helpers.toSatoshi(80));
tx.fee.should.equal(WalletUtils.DEFAULT_FEE_PER_KB);
tx.fee.should.equal(Constants.DEFAULT_FEE_PER_KB);
helpers.getAuthServer(wallet.copayers[0].id, function(server) {
var signatures = helpers.clientSign(tx, TestData.copayers[0].xPrivKey);
server.signTx({

3
test/models/txproposal.js

@ -5,8 +5,7 @@ var chai = require('chai');
var sinon = require('sinon');
var should = chai.should();
var TxProposal = require('../../lib/model/txproposal');
var Bitcore = require('bitcore-wallet-utils').Bitcore;
var WalletUtils = require('bitcore-wallet-utils');
var Bitcore = require('bitcore-lib');
describe('TXProposal', function() {

Loading…
Cancel
Save