Ivan Socolsky
9 years ago
2 changed files with 383 additions and 352 deletions
@ -0,0 +1,380 @@ |
|||
'use strict'; |
|||
|
|||
var _ = require('lodash'); |
|||
var async = require('async'); |
|||
|
|||
var chai = require('chai'); |
|||
var sinon = require('sinon'); |
|||
var should = chai.should(); |
|||
var log = require('npmlog'); |
|||
log.debug = log.verbose; |
|||
|
|||
var Bitcore = require('bitcore-lib'); |
|||
|
|||
var Common = require('../../lib/common'); |
|||
var Utils = Common.Utils; |
|||
var Constants = Common.Constants; |
|||
var Defaults = Common.Defaults; |
|||
|
|||
var Model = require('../../lib/model'); |
|||
var WalletService = require('../../lib/server'); |
|||
var TestData = require('../testdata'); |
|||
|
|||
var blockchainExplorer; |
|||
|
|||
var helpers = {}; |
|||
|
|||
helpers.setBlockchainExplorer = function(bce) { |
|||
blockchainExplorer = bce; |
|||
}; |
|||
|
|||
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); |
|||
WalletService.getInstanceWithAuth({ |
|||
copayerId: copayerId, |
|||
message: 'dummy', |
|||
signature: 'dummy', |
|||
clientVersion: 'bwc-0.1.0', |
|||
}, function(err, server) { |
|||
verifyStub.restore(); |
|||
if (err || !server) throw new Error('Could not login as copayerId ' + copayerId); |
|||
return cb(server); |
|||
}); |
|||
}; |
|||
|
|||
helpers._generateCopayersTestData = function(n) { |
|||
console.log('var copayers = ['); |
|||
_.each(_.range(n), function(c) { |
|||
var xpriv = new Bitcore.HDPrivateKey(); |
|||
var xpub = Bitcore.HDPublicKey(xpriv); |
|||
|
|||
var xpriv_45H = xpriv.derive(45, true); |
|||
var xpub_45H = Bitcore.HDPublicKey(xpriv_45H); |
|||
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 = Copayer._xPubToCopayerId(xpub_44H_0H_0H.toString()); |
|||
|
|||
var xpriv_1H = xpriv.derive(1, true); |
|||
var xpub_1H = Bitcore.HDPublicKey(xpriv_1H); |
|||
var priv = xpriv_1H.derive(0).privateKey; |
|||
var pub = xpub_1H.derive(0).publicKey; |
|||
|
|||
console.log('{id44: ', "'" + id44 + "',"); |
|||
console.log('id45: ', "'" + id45 + "',"); |
|||
console.log('xPrivKey: ', "'" + xpriv.toString() + "',"); |
|||
console.log('xPubKey: ', "'" + xpub.toString() + "',"); |
|||
console.log('xPrivKey_45H: ', "'" + xpriv_45H.toString() + "',"); |
|||
console.log('xPubKey_45H: ', "'" + xpub_45H.toString() + "',"); |
|||
console.log('xPrivKey_44H_0H_0H: ', "'" + xpriv_44H_0H_0H.toString() + "',"); |
|||
console.log('xPubKey_44H_0H_0H: ', "'" + xpub_44H_0H_0H.toString() + "',"); |
|||
console.log('xPrivKey_1H: ', "'" + xpriv_1H.toString() + "',"); |
|||
console.log('xPubKey_1H: ', "'" + xpub_1H.toString() + "',"); |
|||
console.log('privKey_1H_0: ', "'" + priv.toString() + "',"); |
|||
console.log('pubKey_1H_0: ', "'" + pub.toString() + "'},"); |
|||
}); |
|||
console.log('];'); |
|||
}; |
|||
|
|||
helpers.getSignedCopayerOpts = function(opts) { |
|||
var hash = WalletService._getCopayerHash(opts.name, opts.xPubKey, opts.requestPubKey); |
|||
opts.copayerSignature = helpers.signMessage(hash, TestData.keyPair.priv); |
|||
return opts; |
|||
}; |
|||
|
|||
helpers.createAndJoinWallet = function(m, n, opts, cb) { |
|||
if (_.isFunction(opts)) { |
|||
cb = opts; |
|||
opts = {}; |
|||
} |
|||
opts = opts || {}; |
|||
|
|||
var server = new WalletService(); |
|||
var copayerIds = []; |
|||
var offset = opts.offset || 0; |
|||
|
|||
var walletOpts = { |
|||
name: 'a wallet', |
|||
m: m, |
|||
n: n, |
|||
pubKey: TestData.keyPair.pub, |
|||
}; |
|||
if (_.isBoolean(opts.supportBIP44AndP2PKH)) |
|||
walletOpts.supportBIP44AndP2PKH = opts.supportBIP44AndP2PKH; |
|||
|
|||
server.createWallet(walletOpts, function(err, walletId) { |
|||
if (err) return cb(err); |
|||
|
|||
async.each(_.range(n), function(i, cb) { |
|||
var copayerData = TestData.copayers[i + offset]; |
|||
var copayerOpts = helpers.getSignedCopayerOpts({ |
|||
walletId: walletId, |
|||
name: 'copayer ' + (i + 1), |
|||
xPubKey: (_.isBoolean(opts.supportBIP44AndP2PKH) && !opts.supportBIP44AndP2PKH) ? copayerData.xPubKey_45H : copayerData.xPubKey_44H_0H_0H, |
|||
requestPubKey: copayerData.pubKey_1H_0, |
|||
customData: 'custom data ' + (i + 1), |
|||
}); |
|||
if (_.isBoolean(opts.supportBIP44AndP2PKH)) |
|||
copayerOpts.supportBIP44AndP2PKH = opts.supportBIP44AndP2PKH; |
|||
|
|||
server.joinWallet(copayerOpts, function(err, result) { |
|||
should.not.exist(err); |
|||
copayerIds.push(result.copayerId); |
|||
return cb(err); |
|||
}); |
|||
}, function(err) { |
|||
if (err) return new Error('Could not generate wallet'); |
|||
helpers.getAuthServer(copayerIds[0], function(s) { |
|||
s.getWallet({}, function(err, w) { |
|||
cb(s, w); |
|||
}); |
|||
}); |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
|
|||
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); |
|||
} else { |
|||
return Utils.strip(btc * 1e8); |
|||
} |
|||
}; |
|||
|
|||
helpers.stubUtxos = function(server, wallet, amounts, cb) { |
|||
async.mapSeries(_.range(0, amounts.length > 2 ? 2 : 1), function(i, next) { |
|||
server.createAddress({}, next); |
|||
}, function(err, addresses) { |
|||
should.not.exist(err); |
|||
addresses.should.not.be.empty; |
|||
var utxos = _.map([].concat(amounts), function(amount, i) { |
|||
var address = addresses[i % addresses.length]; |
|||
var confirmations; |
|||
if (_.isString(amount) && _.startsWith(amount, 'u')) { |
|||
amount = parseFloat(amount.substring(1)); |
|||
confirmations = 0; |
|||
} else { |
|||
confirmations = Math.floor(Math.random() * 100 + 1); |
|||
} |
|||
|
|||
var scriptPubKey; |
|||
switch (wallet.addressType) { |
|||
case Constants.SCRIPT_TYPES.P2SH: |
|||
scriptPubKey = Bitcore.Script.buildMultisigOut(address.publicKeys, wallet.m).toScriptHashOut(); |
|||
break; |
|||
case Constants.SCRIPT_TYPES.P2PKH: |
|||
scriptPubKey = Bitcore.Script.buildPublicKeyHashOut(address.address); |
|||
break; |
|||
} |
|||
should.exist(scriptPubKey); |
|||
|
|||
return { |
|||
txid: helpers.randomTXID(), |
|||
vout: Math.floor(Math.random() * 10 + 1), |
|||
satoshis: helpers.toSatoshi(amount).toString(), |
|||
scriptPubKey: scriptPubKey.toBuffer().toString('hex'), |
|||
address: address.address, |
|||
confirmations: confirmations, |
|||
}; |
|||
}); |
|||
blockchainExplorer.getUnspentUtxos = function(addresses, cb) { |
|||
var selected = _.filter(utxos, function(utxo) { |
|||
return _.contains(addresses, utxo.address); |
|||
}); |
|||
return cb(null, selected); |
|||
}; |
|||
|
|||
return cb(utxos); |
|||
}); |
|||
}; |
|||
|
|||
helpers.stubBroadcast = function(thirdPartyBroadcast) { |
|||
blockchainExplorer.broadcast = sinon.stub().callsArgWith(1, null, '112233'); |
|||
blockchainExplorer.getTransaction = sinon.stub().callsArgWith(1, null, null); |
|||
}; |
|||
|
|||
helpers.stubHistory = function(txs) { |
|||
blockchainExplorer.getTransactions = function(addresses, from, to, cb) { |
|||
var MAX_BATCH_SIZE = 100; |
|||
var nbTxs = txs.length; |
|||
|
|||
if (_.isUndefined(from) && _.isUndefined(to)) { |
|||
from = 0; |
|||
to = MAX_BATCH_SIZE; |
|||
} |
|||
if (!_.isUndefined(from) && _.isUndefined(to)) |
|||
to = from + MAX_BATCH_SIZE; |
|||
|
|||
if (!_.isUndefined(from) && !_.isUndefined(to) && to - from > MAX_BATCH_SIZE) |
|||
to = from + MAX_BATCH_SIZE; |
|||
|
|||
if (from < 0) from = 0; |
|||
if (to < 0) to = 0; |
|||
if (from > nbTxs) from = nbTxs; |
|||
if (to > nbTxs) to = nbTxs; |
|||
|
|||
var page = txs.slice(from, to); |
|||
return cb(null, page); |
|||
}; |
|||
}; |
|||
|
|||
helpers.stubFeeLevels = function(levels) { |
|||
blockchainExplorer.estimateFee = function(nbBlocks, cb) { |
|||
var result = _.zipObject(_.map(_.pick(levels, nbBlocks), function(fee, n) { |
|||
return [+n, fee > 0 ? fee / 1e8 : fee]; |
|||
})); |
|||
return cb(null, result); |
|||
}; |
|||
}; |
|||
|
|||
helpers.stubAddressActivity = function(activeAddresses) { |
|||
blockchainExplorer.getAddressActivity = function(address, cb) { |
|||
return cb(null, _.contains(activeAddresses, address)); |
|||
}; |
|||
}; |
|||
|
|||
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 = txp.getBitcoreTx(); |
|||
|
|||
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 = { |
|||
toAddress: toAddress, |
|||
amount: helpers.toSatoshi(amount), |
|||
message: message, |
|||
proposalSignature: null, |
|||
}; |
|||
if (feePerKb) opts.feePerKb = feePerKb; |
|||
|
|||
var hash = WalletService._getProposalHash(toAddress, opts.amount, message); |
|||
|
|||
try { |
|||
opts.proposalSignature = helpers.signMessage(hash, signingKey); |
|||
} catch (ex) {} |
|||
|
|||
return opts; |
|||
}; |
|||
|
|||
helpers.createSimpleProposalOpts = function(toAddress, amount, signingKey, opts) { |
|||
var outputs = [{ |
|||
toAddress: toAddress, |
|||
amount: amount, |
|||
}]; |
|||
return helpers.createProposalOpts(Model.TxProposal.Types.SIMPLE, outputs, signingKey, opts); |
|||
}; |
|||
|
|||
helpers.createProposalOpts = function(type, outputs, signingKey, moreOpts) { |
|||
_.each(outputs, function(output) { |
|||
output.amount = helpers.toSatoshi(output.amount); |
|||
}); |
|||
|
|||
var opts = { |
|||
type: type, |
|||
proposalSignature: null |
|||
}; |
|||
|
|||
if (moreOpts) { |
|||
moreOpts = _.chain(moreOpts) |
|||
.pick(['feePerKb', 'customData', 'message']) |
|||
.value(); |
|||
opts = _.assign(opts, moreOpts); |
|||
} |
|||
|
|||
opts = _.defaults(opts, { |
|||
message: null |
|||
}); |
|||
|
|||
var hash; |
|||
if (type == Model.TxProposal.Types.SIMPLE) { |
|||
opts.toAddress = outputs[0].toAddress; |
|||
opts.amount = outputs[0].amount; |
|||
hash = WalletService._getProposalHash(opts.toAddress, opts.amount, |
|||
opts.message, opts.payProUrl); |
|||
} else if (type == Model.TxProposal.Types.MULTIPLEOUTPUTS) { |
|||
opts.outputs = outputs; |
|||
var header = { |
|||
outputs: outputs, |
|||
message: opts.message, |
|||
payProUrl: opts.payProUrl |
|||
}; |
|||
hash = WalletService._getProposalHash(header); |
|||
} |
|||
|
|||
try { |
|||
opts.proposalSignature = helpers.signMessage(hash, signingKey); |
|||
} catch (ex) {} |
|||
|
|||
return opts; |
|||
}; |
|||
|
|||
helpers.createAddresses = function(server, wallet, main, change, cb) { |
|||
var clock = sinon.useFakeTimers(Date.now(), 'Date'); |
|||
async.map(_.range(main + change), function(i, next) { |
|||
clock.tick(1000); |
|||
var address = wallet.createAddress(i >= main); |
|||
server.storage.storeAddressAndWallet(wallet, address, function(err) { |
|||
next(err, address); |
|||
}); |
|||
}, function(err, addresses) { |
|||
if (err) throw new Error('Could not generate addresses'); |
|||
clock.restore(); |
|||
return cb(_.take(addresses, main), _.takeRight(addresses, change)); |
|||
}); |
|||
}; |
|||
|
|||
module.exports = helpers; |
Loading…
Reference in new issue