UNPKG

37.3 kBPlain TextView Raw
1/**
2 * @hidden
3 */
4
5/**
6 */
7//
8// TransactionBuilder
9// A utility for building and signing transactions
10//
11// Copyright 2014, BitGo, Inc. All Rights Reserved.
12//
13
14import * as bip32 from 'bip32';
15import * as Bluebird from 'bluebird';
16import * as utxolib from '@bitgo/utxo-lib';
17import * as _ from 'lodash';
18import { VirtualSizes } from '@bitgo/unspents';
19import { getAddressP2PKH, getNetwork } from './bitcoin';
20import debugLib = require('debug');
21const debug = debugLib('bitgo:v1:txb');
22import { common } from '@bitgo/sdk-core';
23import { sanitizeLegacyPath } from '@bitgo/sdk-api';
24
25interface BaseOutput {
26 amount: number;
27 travelInfo?: any;
28}
29
30interface AddressOutput extends BaseOutput {
31 address: string;
32}
33
34interface ScriptOutput extends BaseOutput {
35 script: Buffer;
36}
37
38type Output = AddressOutput | ScriptOutput;
39
40interface BitGoUnspent {
41 value: number;
42 tx_hash: Buffer;
43 tx_output_n: number;
44}
45
46//
47// TransactionBuilder
48// @params:
49// wallet: a wallet object to send from
50// recipients: array of recipient objects and the amount to send to each e.g. [{address: '38BKDNZbPcLogvVbcx2ekJ9E6Vv94DqDqw', amount: 1500}, {address: '36eL8yQqCn1HMRmVFFo49t2PJ3pai8wQam', amount: 2000}]
51// fee: the fee to use with this transaction. if not provided, a default, minimum fee will be used.
52// feeRate: the amount of fee per kilobyte - optional - specify either fee, feeRate, or feeTxConfirmTarget but not more than one
53// feeTxConfirmTarget: calculate the fees per kilobyte such that the transaction will be confirmed in this number of blocks
54// maxFeeRate: The maximum fee per kb to use in satoshis, for safety purposes when using dynamic fees
55// minConfirms: the minimum confirmations an output must have before spending
56// forceChangeAtEnd: force the change address to be the last output
57// changeAddress: specify the change address rather than generate a new one
58// noSplitChange: set to true to disable automatic change splitting for purposes of unspent management
59// targetWalletUnspents: specify a number of target unspents to maintain in the wallet (currently defaulted to 8 by the server)
60// validate: extra verification of the change addresses, which is always done server-side and is redundant client-side (defaults true)
61// minUnspentSize: The minimum size in satoshis of unspent to use (to prevent spending unspents worth less than fee added). Defaults to 0.
62// feeSingleKeySourceAddress: Use this single key address to pay fees
63// feeSingleKeyWIF: Use the address based on this private key to pay fees
64// unspentsFetchParams: Extra parameters to use for fetching unspents for this transaction
65exports.createTransaction = function (params) {
66 const minConfirms = params.minConfirms || 0;
67 const validate = params.validate === undefined ? true : params.validate;
68 let recipients: { address: string; amount: number; script?: string; travelInfo?: any; }[] = [];
69 let opReturns: { message: string; amount: number; }[] = [];
70 let extraChangeAmounts: number[] = [];
71 let estTxSize: number;
72 let travelInfos;
73
74 // Sanity check the arguments passed in
75 if (!_.isObject(params.wallet) ||
76 (params.fee && !_.isNumber(params.fee)) ||
77 (params.feeRate && !_.isNumber(params.feeRate)) ||
78 !_.isInteger(minConfirms) ||
79 (params.forceChangeAtEnd && !_.isBoolean(params.forceChangeAtEnd)) ||
80 (params.changeAddress && !_.isString(params.changeAddress)) ||
81 (params.noSplitChange && !_.isBoolean(params.noSplitChange)) ||
82 (params.targetWalletUnspents && !_.isInteger(params.targetWalletUnspents)) ||
83 (validate && !_.isBoolean(validate)) ||
84 (params.enforceMinConfirmsForChange && !_.isBoolean(params.enforceMinConfirmsForChange)) ||
85 (params.minUnspentSize && !_.isNumber(params.minUnspentSize)) ||
86 (params.maxFeeRate && !_.isNumber(params.maxFeeRate)) ||
87 // this should be an array and its length must be at least 1
88 (params.unspents && (!Array.isArray(params.unspents) || params.unspents.length < 1)) ||
89 (params.feeTxConfirmTarget && !_.isInteger(params.feeTxConfirmTarget)) ||
90 (params.instant && !_.isBoolean(params.instant)) ||
91 (params.bitgoFee && !_.isObject(params.bitgoFee)) ||
92 (params.unspentsFetchParams && !_.isObject(params.unspentsFetchParams))
93 ) {
94 throw new Error('invalid argument');
95 }
96
97 const bitgo = params.wallet.bitgo;
98 const constants = bitgo.getConstants();
99 const network = getNetwork(common.Environments[bitgo.getEnv()].network);
100
101 // The user can specify a seperate, single-key wallet for the purposes of paying miner's fees
102 // When creating a transaction this can be specified as an input address or the private key in WIF
103 let feeSingleKeySourceAddress;
104 let feeSingleKeyInputAmount = 0;
105 if (params.feeSingleKeySourceAddress) {
106 try {
107 utxolib.address.fromBase58Check(params.feeSingleKeySourceAddress, network);
108 feeSingleKeySourceAddress = params.feeSingleKeySourceAddress;
109 } catch (e) {
110 throw new Error('invalid bitcoin address: ' + params.feeSingleKeySourceAddress);
111 }
112 }
113
114 if (params.feeSingleKeyWIF) {
115 const feeSingleKey = utxolib.ECPair.fromWIF(params.feeSingleKeyWIF, network as utxolib.BitcoinJSNetwork);
116 feeSingleKeySourceAddress = getAddressP2PKH(feeSingleKey);
117 // If the user specifies both, check to make sure the feeSingleKeySourceAddress corresponds to the address of feeSingleKeyWIF
118 if (params.feeSingleKeySourceAddress &&
119 params.feeSingleKeySourceAddress !== feeSingleKeySourceAddress) {
120 throw new Error('feeSingleKeySourceAddress: ' + params.feeSingleKeySourceAddress +
121 ' did not correspond to address of feeSingleKeyWIF: ' + feeSingleKeySourceAddress);
122 }
123 }
124
125 if (!_.isObject(params.recipients)) {
126 throw new Error('recipients must be array of { address: abc, amount: 100000 } objects');
127 }
128
129 let feeParamsDefined = 0;
130 if (!_.isUndefined(params.fee)) {
131 feeParamsDefined++;
132 }
133
134 if (!_.isUndefined(params.feeRate)) {
135 feeParamsDefined++;
136 }
137
138 if (!_.isUndefined(params.feeTxConfirmTarget)) {
139 feeParamsDefined++;
140 }
141
142 if (feeParamsDefined > 1) {
143 throw new Error('cannot specify more than one of fee, feeRate and feeTxConfirmTarget');
144 }
145
146 if (_.isUndefined(params.maxFeeRate)) {
147 params.maxFeeRate = constants.maxFeeRate;
148 }
149
150 // Convert the old format of params.recipients (dictionary of address:amount) to new format: { destinationAddress, amount }
151 if (!(params.recipients instanceof Array)) {
152 recipients = [];
153 Object.keys(params.recipients).forEach(function (destinationAddress) {
154 const amount = params.recipients[destinationAddress];
155 recipients.push({ address: destinationAddress, amount: amount });
156 });
157 } else {
158 recipients = params.recipients;
159 }
160
161 if (params.opReturns) {
162 if (!(params.opReturns instanceof Array)) {
163 opReturns = [];
164 Object.keys(params.opReturns).forEach(function (message) {
165 const amount = params.opReturns[message];
166 opReturns.push({ message, amount });
167 });
168 } else {
169 opReturns = params.opReturns;
170 }
171 }
172
173 if (recipients.length === 0 && opReturns.length === 0) {
174 throw new Error('must have at least one recipient');
175 }
176
177 let fee = params.fee;
178 let feeRate = params.feeRate;
179
180 // Flag indicating whether this class will compute the fee
181 const shouldComputeBestFee = (_.isUndefined(fee));
182
183 let totalOutputAmount = 0;
184
185 recipients.forEach(function (recipient) {
186 if (_.isString(recipient.address)) {
187 try {
188 utxolib.address.fromBase58Check(recipient.address, network);
189 } catch (e) {
190 throw new Error('invalid bitcoin address: ' + recipient.address);
191 }
192 if (!!recipient.script) {
193 // A script was provided as well - validate that the address corresponds to that
194 if (utxolib.address.toOutputScript(recipient.address, network).toString('hex') !== recipient.script) {
195 throw new Error('both script and address provided but they did not match: ' + recipient.address + ' ' + recipient.script);
196 }
197 }
198 }
199 if (!_.isInteger(recipient.amount) || recipient.amount < 0) {
200 throw new Error('invalid amount for ' + recipient.address + ': ' + recipient.amount);
201 }
202 totalOutputAmount += recipient.amount;
203 });
204
205 opReturns.forEach(function (opReturn) {
206 totalOutputAmount += opReturn.amount;
207 });
208
209 let bitgoFeeInfo = params.bitgoFee;
210 if (bitgoFeeInfo &&
211 (!_.isInteger(bitgoFeeInfo.amount) || !_.isString(bitgoFeeInfo.address))) {
212 throw new Error('invalid bitgoFeeInfo');
213 }
214
215 // The total amount needed for this transaction.
216 let totalAmount = totalOutputAmount + (fee || 0);
217
218 // The list of unspent transactions being used in this transaction.
219 let unspents;
220
221 // the total number of unspents on this wallet
222 let totalUnspentsCount;
223
224 // the number of unspents we fetched from the server, before filtering
225 let fetchedUnspentsCount;
226
227 // The list of unspent transactions being used with zero-confirmations
228 let zeroConfUnspentTxIds;
229
230 // The sum of the input values for this transaction.
231 let inputAmount;
232
233 let changeOutputs: Output[] = [];
234
235 // The transaction.
236 let transaction = utxolib.bitgo.createTransactionBuilderForNetwork(network);
237
238 const getBitGoFee = function () {
239 return Bluebird.try(function () {
240 if (bitgoFeeInfo) {
241 return;
242 }
243 return params.wallet.getBitGoFee({ amount: totalOutputAmount, instant: params.instant })
244 .then(function (result) {
245 if (result && result.fee > 0) {
246 bitgoFeeInfo = {
247 amount: result.fee,
248 };
249 }
250 });
251 })
252 .then(function () {
253 if (bitgoFeeInfo && bitgoFeeInfo.amount > 0) {
254 totalAmount += bitgoFeeInfo.amount;
255 }
256 });
257 };
258
259 const getBitGoFeeAddress = function () {
260 return Bluebird.try(function () {
261 // If we don't have bitgoFeeInfo, or address is already set, don't get a new one
262 if (!bitgoFeeInfo || bitgoFeeInfo.address) {
263 return;
264 }
265 return bitgo.getBitGoFeeAddress()
266 .then(function (result) {
267 bitgoFeeInfo.address = result.address;
268 });
269 });
270 };
271
272 // Get a dynamic fee estimate from the BitGo server if feeTxConfirmTarget
273 // is specified or if no fee-related params are specified
274 const getDynamicFeeRateEstimate = function () {
275 if (params.feeTxConfirmTarget || !feeParamsDefined) {
276 return bitgo.estimateFee({
277 numBlocks: params.feeTxConfirmTarget,
278 maxFee: params.maxFeeRate,
279 inputs: zeroConfUnspentTxIds,
280 txSize: estTxSize,
281 cpfpAware: true,
282 })
283 .then(function (result) {
284 const estimatedFeeRate = result.cpfpFeePerKb;
285 const minimum = params.instant ? Math.max(constants.minFeeRate, constants.minInstantFeeRate) : constants.minFeeRate;
286 // 5 satoshis per byte
287 // it is worth noting that the padding only applies when the threshold is crossed, but not when the delta is less than the padding
288 const padding = 5000;
289 if (estimatedFeeRate < minimum) {
290 console.log(new Date() + ': Error when estimating fee for send from ' + params.wallet.id() + ', it was too low - ' + estimatedFeeRate);
291 feeRate = minimum + padding;
292 } else if (estimatedFeeRate > params.maxFeeRate) {
293 feeRate = params.maxFeeRate - padding;
294 } else {
295 feeRate = estimatedFeeRate;
296 }
297 return feeRate;
298 })
299 .catch(function (e) {
300 // sanity check failed on tx size
301 if (_.includes(e.message, 'invalid txSize')) {
302 return Bluebird.reject(e);
303 } else {
304 // couldn't estimate the fee, proceed using the default
305 feeRate = constants.fallbackFeeRate;
306 console.log('Error estimating fee for send from ' + params.wallet.id() + ': ' + e.message);
307 return Bluebird.resolve();
308 }
309 });
310 }
311 };
312
313
314 // Get the unspents for the sending wallet.
315 const getUnspents = function () {
316
317 if (params.unspents) { // we just wanna use custom unspents
318 unspents = params.unspents;
319 return;
320 }
321
322 // Get enough unspents for the requested amount
323 const options = _.merge({}, params.unspentsFetchParams || {}, {
324 target: totalAmount,
325 minSize: params.minUnspentSize || 0,
326 instant: params.instant, // insist on instant unspents only
327 targetWalletUnspents: params.targetWalletUnspents,
328 });
329 if (params.instant) {
330 options.instant = params.instant; // insist on instant unspents only
331 }
332
333 return params.wallet.unspentsPaged(options)
334 .then(function (results) {
335 totalUnspentsCount = results.total;
336 fetchedUnspentsCount = results.count;
337 unspents = results.unspents.filter(function (u) {
338 const confirms = u.confirmations || 0;
339 if (!params.enforceMinConfirmsForChange && u.isChange) {
340 return true;
341 }
342 return confirms >= minConfirms;
343 });
344
345 // abort early if there's no viable unspents, because it won't be possible to create the txn later
346 if (unspents.length === 0) {
347 throw Error('0 unspents available for transaction creation');
348 }
349
350 // create array of unconfirmed unspent ID strings of the form "txHash:outputIndex"
351 zeroConfUnspentTxIds = _(results.unspents).filter(function (u) {
352 return !u.confirmations;
353 }).map(function (u) {
354 return u.tx_hash + ':' + u.tx_output_n;
355 }).value();
356 if (_.isEmpty(zeroConfUnspentTxIds)) {
357 // we don't want to pass an empty array of inputs to the server, because it assumes if the
358 // inputs arguments exists, it contains values
359 zeroConfUnspentTxIds = undefined;
360 }
361
362 // For backwards compatibility, respect the old splitChangeSize=0 parameter
363 if (!params.noSplitChange && params.splitChangeSize !== 0) {
364 extraChangeAmounts = results.extraChangeAmounts || [];
365 }
366 });
367 };
368
369 // Get the unspents for the single key fee address
370 let feeSingleKeyUnspents: BitGoUnspent[] = [];
371 const getUnspentsForSingleKey = function () {
372 if (feeSingleKeySourceAddress) {
373 let feeTarget = 0.01e8;
374 if (params.instant) {
375 feeTarget += totalAmount * 0.001;
376 }
377 return bitgo.get(bitgo.url('/address/' + feeSingleKeySourceAddress + '/unspents?target=' + feeTarget))
378 .then(function (response) {
379 if (response.body.total <= 0) {
380 throw new Error('No unspents available in single key fee source');
381 }
382 feeSingleKeyUnspents = response.body.unspents;
383 });
384 }
385 };
386
387 let minerFeeInfo: any = {};
388 let txInfo: any = {};
389
390 // Iterate unspents, sum the inputs, and save _inputs with the total
391 // input amount and final list of inputs to use with the transaction.
392 let feeSingleKeyUnspentsUsed: BitGoUnspent[] = [];
393
394 const collectInputs = function () {
395 if (!unspents.length) {
396 throw new Error('no unspents available on wallet');
397 }
398 inputAmount = 0;
399
400 // Calculate the cost of spending a single input, i.e. the smallest economical unspent value
401 return Bluebird.try(function () {
402
403 if (_.isNumber(params.feeRate) || _.isNumber(params.originalFeeRate)) {
404 return (!_.isUndefined(params.feeRate) ? params.feeRate : params.originalFeeRate);
405 } else {
406 return bitgo.estimateFee({
407 numBlocks: params.feeTxConfirmTarget,
408 maxFee: params.maxFeeRate,
409 })
410 .then(function (feeRateEstimate) {
411 return feeRateEstimate.feePerKb;
412 });
413 }
414 }).then(function (feeRate) {
415 // Don't spend inputs that cannot pay for their own cost.
416 let minInputValue = 0;
417 if (_.isInteger(params.minUnspentSize)) {
418 minInputValue = params.minUnspentSize;
419 }
420
421 let prunedUnspentCount = 0;
422 const originalUnspentCount = unspents.length;
423 unspents = _.filter(unspents, function (unspent) {
424 const isSegwitInput = !!unspent.witnessScript;
425 const currentInputSize = isSegwitInput ? VirtualSizes.txP2shP2wshInputSize : VirtualSizes.txP2shInputSize;
426 const feeBasedMinInputValue = (feeRate * currentInputSize) / 1000;
427 const currentMinInputValue = Math.max(minInputValue, feeBasedMinInputValue);
428 if (currentMinInputValue > unspent.value) {
429 // pruning unspent
430 const pruneDetails = {
431 generalMinInputValue: minInputValue,
432 feeBasedMinInputValue,
433 currentMinInputValue,
434 feeRate,
435 inputSize: currentInputSize,
436 unspent: unspent,
437 };
438 console.log(`pruning unspent: ${JSON.stringify(pruneDetails, null, 4)}`);
439 prunedUnspentCount++;
440 return false;
441 }
442 return true;
443 });
444
445 if (prunedUnspentCount > 0) {
446 console.log(`pruned ${prunedUnspentCount} out of ${originalUnspentCount} unspents`);
447 }
448
449 if (unspents.length === 0) {
450 throw new Error('insufficient funds');
451 }
452 let segwitInputCount = 0;
453 unspents.every(function (unspent) {
454 if (unspent.witnessScript) {
455 segwitInputCount++;
456 }
457 inputAmount += unspent.value;
458 transaction.addInput(unspent.tx_hash, unspent.tx_output_n, 0xffffffff);
459
460 return (inputAmount < (feeSingleKeySourceAddress ? totalOutputAmount : totalAmount));
461 });
462
463 // if paying fees from an external single key wallet, add the inputs
464 if (feeSingleKeySourceAddress) {
465 // collect the amount used in the fee inputs so we can get change later
466 feeSingleKeyInputAmount = 0;
467 feeSingleKeyUnspentsUsed = [];
468 feeSingleKeyUnspents.every(function (unspent) {
469 feeSingleKeyInputAmount += unspent.value;
470 inputAmount += unspent.value;
471 transaction.addInput(unspent.tx_hash, unspent.tx_output_n);
472 feeSingleKeyUnspentsUsed.push(unspent);
473 // use the fee wallet to pay miner fees and potentially instant fees
474 return (feeSingleKeyInputAmount < (fee + (bitgoFeeInfo ? bitgoFeeInfo.amount : 0)));
475 });
476 }
477
478 txInfo = {
479 nP2shInputs: transaction.tx.ins.length - (feeSingleKeySourceAddress ? 1 : 0) - segwitInputCount,
480 nP2shP2wshInputs: segwitInputCount,
481 nP2pkhInputs: feeSingleKeySourceAddress ? 1 : 0,
482 nOutputs: (
483 recipients.length + 1 + // recipients and change
484 extraChangeAmounts.length + // extra change splitting
485 (bitgoFeeInfo && bitgoFeeInfo.amount > 0 ? 1 : 0) + // add output for bitgo fee
486 (feeSingleKeySourceAddress ? 1 : 0) // add single key source address change
487 ),
488 };
489
490 estTxSize = estimateTransactionSize({
491 nP2shInputs: txInfo.nP2shInputs,
492 nP2shP2wshInputs: txInfo.nP2shP2wshInputs,
493 nP2pkhInputs: txInfo.nP2pkhInputs,
494 nOutputs: txInfo.nOutputs,
495 });
496 }).then(getDynamicFeeRateEstimate)
497 .then(function () {
498 minerFeeInfo = exports.calculateMinerFeeInfo({
499 bitgo: params.wallet.bitgo,
500 feeRate: feeRate,
501 nP2shInputs: txInfo.nP2shInputs,
502 nP2shP2wshInputs: txInfo.nP2shP2wshInputs,
503 nP2pkhInputs: txInfo.nP2pkhInputs,
504 nOutputs: txInfo.nOutputs,
505 });
506
507 if (shouldComputeBestFee) {
508 const approximateFee = minerFeeInfo.fee;
509 const shouldRecurse = _.isUndefined(fee) || approximateFee > fee;
510 fee = approximateFee;
511 // Recompute totalAmount from scratch
512 totalAmount = fee + totalOutputAmount;
513 if (bitgoFeeInfo) {
514 totalAmount += bitgoFeeInfo.amount;
515 }
516 if (shouldRecurse) {
517 // if fee changed, re-collect inputs
518 inputAmount = 0;
519 transaction = utxolib.bitgo.createTransactionBuilderForNetwork(network);
520 return collectInputs();
521 }
522 }
523
524 const totalFee = fee + (bitgoFeeInfo ? bitgoFeeInfo.amount : 0);
525
526 if (feeSingleKeySourceAddress) {
527 const summedSingleKeyUnspents = _.sumBy(feeSingleKeyUnspents, 'value');
528 if (totalFee > summedSingleKeyUnspents) {
529 const err: any = new Error('Insufficient fee amount available in single key fee source: ' + summedSingleKeyUnspents);
530 err.result = {
531 fee: fee,
532 feeRate: feeRate,
533 estimatedSize: minerFeeInfo.size,
534 available: inputAmount,
535 bitgoFee: bitgoFeeInfo,
536 txInfo: txInfo,
537 };
538 return Bluebird.reject(err);
539 }
540 }
541
542 if (inputAmount < (feeSingleKeySourceAddress ? totalOutputAmount : totalAmount)) {
543 // The unspents we're using for inputs do not have sufficient value on them to
544 // satisfy the user's requested spend amount. That may be because the wallet's balance
545 // is simply too low, or it might be that the wallet's balance is sufficient but
546 // we didn't fetch enough unspents. Too few unspents could result from the wallet
547 // having many small unspents and we hit our limit on the number of inputs we can use
548 // in a txn, or it might have been that the filters the user passed in (like minConfirms)
549 // disqualified too many of the unspents
550 let err;
551 if (totalUnspentsCount === fetchedUnspentsCount) {
552 // we fetched every unspent the wallet had, but it still wasn't enough
553 err = new Error('Insufficient funds');
554 } else {
555 // we weren't able to fetch all the unspents on the wallet
556 err = new Error(`Transaction size too large due to too many unspents. Can send only ${inputAmount} satoshis in this transaction`);
557 }
558 err.result = {
559 fee: fee,
560 feeRate: feeRate,
561 estimatedSize: minerFeeInfo.size,
562 available: inputAmount,
563 bitgoFee: bitgoFeeInfo,
564 txInfo: txInfo,
565 };
566 return Bluebird.reject(err);
567 }
568 });
569 };
570
571 // Add the outputs for this transaction.
572 const collectOutputs = function () {
573 if (minerFeeInfo.size >= 90000) {
574 throw new Error('transaction too large: estimated size ' + minerFeeInfo.size + ' bytes');
575 }
576
577 const outputs: Output[] = [];
578
579 recipients.forEach(function (recipient) {
580 let script;
581 if (_.isString(recipient.address)) {
582 script = utxolib.address.toOutputScript(recipient.address, network);
583 } else if (_.isObject(recipient.script)) {
584 script = recipient.script;
585 } else {
586 throw new Error('neither recipient address nor script was provided');
587 }
588
589 // validate travelInfo if it exists
590 let travelInfo;
591 if (!_.isEmpty(recipient.travelInfo)) {
592 travelInfo = recipient.travelInfo;
593 // Better to avoid trouble now, before tx is created
594 bitgo.travelRule().validateTravelInfo(travelInfo);
595 }
596
597 outputs.push({
598 script: script,
599 amount: recipient.amount,
600 travelInfo: travelInfo,
601 });
602 });
603
604 opReturns.forEach(function ({ message, amount }) {
605 const script = utxolib.script.fromASM('OP_RETURN ' + Buffer.from(message).toString('hex'));
606 outputs.push({ script, amount });
607 });
608
609 const getChangeOutputs = function (changeAmount: number): Output[] | Bluebird<Output[]> {
610 if (changeAmount < 0) {
611 throw new Error('negative change amount: ' + changeAmount);
612 }
613
614 const result: Output[] = [];
615 // if we paid fees from a single key wallet, return the fee change first
616 if (feeSingleKeySourceAddress) {
617 const feeSingleKeyWalletChangeAmount = feeSingleKeyInputAmount - (fee + (bitgoFeeInfo ? bitgoFeeInfo.amount : 0));
618 if (feeSingleKeyWalletChangeAmount >= constants.minOutputSize) {
619 result.push({ address: feeSingleKeySourceAddress, amount: feeSingleKeyWalletChangeAmount });
620 changeAmount = changeAmount - feeSingleKeyWalletChangeAmount;
621 }
622 }
623
624 if (changeAmount < constants.minOutputSize) {
625 // Give it to the miners
626 return result;
627 }
628
629 if (params.wallet.type() === 'safe') {
630 return params.wallet.addresses()
631 .then(function (response) {
632 result.push({ address: response.addresses[0].address, amount: changeAmount });
633 return result;
634 });
635 }
636
637 let extraChangeTotal = _.sum(extraChangeAmounts);
638 // Sanity check
639 if (extraChangeTotal > changeAmount) {
640 extraChangeAmounts = [];
641 extraChangeTotal = 0;
642 }
643
644 // copy and add remaining change amount
645 const allChangeAmounts = extraChangeAmounts.slice(0);
646 allChangeAmounts.push(changeAmount - extraChangeTotal);
647
648 // Recursive async func to add all change outputs
649 const addChangeOutputs = function (): Output[] | Bluebird<Output[]> {
650 const thisAmount = allChangeAmounts.shift();
651 if (!thisAmount) {
652 return result;
653 }
654 return Bluebird.try(function () {
655 if (params.changeAddress) {
656 // If user passed a change address, use it for all outputs
657 return params.changeAddress;
658 } else {
659 // Otherwise create a new address per output, for privacy
660 // determine if segwit or not
661 const changeChain = params.wallet.getChangeChain(params);
662 return params.wallet.createAddress({ chain: changeChain, validate: validate })
663 .then(function (result) {
664 return result.address;
665 });
666 }
667 })
668 .then(function (address) {
669 result.push({ address: address, amount: thisAmount });
670 return addChangeOutputs();
671 });
672 };
673
674 return addChangeOutputs();
675 };
676
677 // Add change output(s) and instant fee output if applicable
678 return Bluebird.try(function () {
679 return getChangeOutputs(inputAmount - totalAmount);
680 })
681 .then(function (result) {
682 changeOutputs = result;
683 const extraOutputs = changeOutputs.concat([]); // copy the array
684 if (bitgoFeeInfo && bitgoFeeInfo.amount > 0) {
685 extraOutputs.push(bitgoFeeInfo);
686 }
687 extraOutputs.forEach(function (output) {
688 if ((output as AddressOutput).address) {
689 (output as ScriptOutput).script =
690 utxolib.address.toOutputScript((output as AddressOutput).address, network);
691 }
692
693 // decide where to put the outputs - default is to randomize unless forced to end
694 const outputIndex = params.forceChangeAtEnd ? outputs.length : _.random(0, outputs.length);
695 outputs.splice(outputIndex, 0, output);
696 });
697
698 // Add all outputs to the transaction
699 outputs.forEach(function (output) {
700 transaction.addOutput((output as ScriptOutput).script, output.amount);
701 });
702
703 travelInfos = _(outputs).map(function (output, index) {
704 const result = output.travelInfo;
705 if (!result) {
706 return undefined;
707 }
708 result.outputIndex = index;
709 return result;
710 })
711 .filter()
712 .value();
713 });
714 };
715
716 // Serialize the transaction, returning what is needed to sign it
717 const serialize = function () {
718 // only need to return the unspents that were used and just the chainPath, redeemScript, and instant flag
719 const pickedUnspents: any = _.map(unspents, function (unspent) {
720 return _.pick(unspent, ['chainPath', 'redeemScript', 'instant', 'witnessScript', 'script', 'value']);
721 });
722 const prunedUnspents = _.slice(pickedUnspents, 0, transaction.tx.ins.length - feeSingleKeyUnspentsUsed.length);
723 _.each(feeSingleKeyUnspentsUsed, function (feeUnspent) {
724 prunedUnspents.push({ redeemScript: false, chainPath: false }); // mark as false to signify a non-multisig address
725 });
726 const result: any = {
727 transactionHex: transaction.buildIncomplete().toHex(),
728 unspents: prunedUnspents,
729 fee: fee,
730 changeAddresses: changeOutputs.map(function (co) {
731 return _.pick(co, ['address', 'path', 'amount']);
732 }),
733 walletId: params.wallet.id(),
734 walletKeychains: params.wallet.keychains,
735 feeRate: feeRate,
736 instant: params.instant,
737 bitgoFee: bitgoFeeInfo,
738 estimatedSize: minerFeeInfo.size,
739 txInfo: txInfo,
740 travelInfos: travelInfos,
741 };
742
743 // Add for backwards compatibility
744 if (result.instant && bitgoFeeInfo) {
745 result.instantFee = _.pick(bitgoFeeInfo, ['amount', 'address']);
746 }
747
748 return result;
749 };
750
751 return Bluebird.try(function () {
752 return getBitGoFee();
753 })
754 .then(function () {
755 return Bluebird.all([getBitGoFeeAddress(), getUnspents(), getUnspentsForSingleKey()]);
756 })
757 .then(collectInputs)
758 .then(collectOutputs)
759 .then(serialize);
760};
761
762
763/**
764 * Estimate the size of a transaction in bytes based on the number of
765 * inputs and outputs present.
766 * @params params {
767 * nP2shInputs: number of P2SH (multisig) inputs
768 * nP2pkhInputs: number of P2PKH (single sig) inputs
769 * nOutputs: number of outputs
770 * }
771 *
772 * @returns size: estimated size of the transaction in bytes
773 */
774const estimateTransactionSize = function (params) {
775 if (!_.isInteger(params.nP2shInputs) || params.nP2shInputs < 0) {
776 throw new Error('expecting positive nP2shInputs');
777 }
778 if (!_.isInteger(params.nP2pkhInputs) || params.nP2pkhInputs < 0) {
779 throw new Error('expecting positive nP2pkhInputs to be numeric');
780 }
781 if (!_.isInteger(params.nP2shP2wshInputs) || params.nP2shP2wshInputs < 0) {
782 throw new Error('expecting positive nP2shP2wshInputs to be numeric');
783 }
784 if ((params.nP2shInputs + params.nP2shP2wshInputs) < 1) {
785 throw new Error('expecting at least one nP2shInputs or nP2shP2wshInputs');
786 }
787 if (!_.isInteger(params.nOutputs) || params.nOutputs < 1) {
788 throw new Error('expecting positive nOutputs');
789 }
790
791
792 const estimatedSize = VirtualSizes.txP2shInputSize * params.nP2shInputs +
793 VirtualSizes.txP2shP2wshInputSize * (params.nP2shP2wshInputs || 0) +
794 VirtualSizes.txP2pkhInputSizeUncompressedKey * (params.nP2pkhInputs || 0) +
795 VirtualSizes.txP2pkhOutputSize * params.nOutputs +
796 // if the tx contains at least one segwit input, the tx overhead is increased by 1
797 VirtualSizes.txOverheadSize + (params.nP2shP2wshInputs > 0 ? 1 : 0);
798
799 return estimatedSize;
800};
801
802
803/**
804 * Calculate the fee and estimated size in bytes for a transaction.
805 * @params params {
806 * bitgo: bitgo object
807 * feeRate: satoshis per kilobyte
808 * nP2shInputs: number of P2SH (multisig) inputs
809 * nP2pkhInputs: number of P2PKH (single sig) inputs
810 * nOutputs: number of outputs
811 * }
812 *
813 * @returns {
814 * size: estimated size of the transaction in bytes
815 * fee: estimated fee in satoshis for the transaction
816 * feeRate: fee rate that was used to estimate the fee for the transaction
817 * }
818 */
819exports.calculateMinerFeeInfo = function (params) {
820 const feeRateToUse = params.feeRate || params.bitgo.getConstants().fallbackFeeRate;
821 const estimatedSize = estimateTransactionSize(params);
822
823 return {
824 size: estimatedSize,
825 fee: Math.ceil(estimatedSize * feeRateToUse / 1000),
826 feeRate: feeRateToUse,
827 };
828};
829
830/*
831 * Given a transaction hex, unspent information (chain path and redeem scripts), and the keychain xprv,
832 * perform key derivation and sign the inputs in the transaction based on the unspent information provided
833 *
834 * @params:
835 * transactionHex serialized form of the transaction in hex
836 * unspents array of unspent information, where each unspent is a chainPath and redeemScript with the same
837 * index as the inputs in the transactionHex
838 * keychain Keychain containing the xprv to sign with. For legacy support of safe wallets, keychain can
839 also be a WIF private key.
840 * signingKey private key in WIF for safe wallets, when keychain is unavailable
841 * validate client-side signature verification - can be disabled for improved performance (signatures
842 * are still validated server-side).
843 * feeSingleKeyWIF Use the address based on this private key to pay fees
844 * @returns {*}
845 */
846exports.signTransaction = function (params) {
847 let keychain = params.keychain; // duplicate so as to not mutate below
848
849 const validate = (params.validate === undefined) ? true : params.validate;
850 let privKey;
851 if (!_.isString(params.transactionHex)) {
852 throw new Error('expecting the transaction hex as a string');
853 }
854 if (!Array.isArray(params.unspents)) {
855 throw new Error('expecting the unspents array');
856 }
857 if (!_.isBoolean(validate)) {
858 throw new Error('expecting validate to be a boolean');
859 }
860 let network = getNetwork();
861 const enableBCH = (_.isBoolean(params.forceBCH) && params.forceBCH === true);
862
863 if (!_.isObject(keychain) || !_.isString((keychain as any).xprv)) {
864 if (_.isString(params.signingKey)) {
865 privKey = utxolib.ECPair.fromWIF(params.signingKey, network as utxolib.BitcoinJSNetwork);
866 keychain = undefined;
867 } else {
868 throw new Error('expecting the keychain object with xprv');
869 }
870 }
871
872 let feeSingleKey;
873 if (params.feeSingleKeyWIF) {
874 feeSingleKey = utxolib.ECPair.fromWIF(params.feeSingleKeyWIF, network as utxolib.BitcoinJSNetwork);
875 }
876
877 debug('Network: %O', network);
878
879 if (enableBCH) {
880 debug('Enabling BCH…');
881 network = utxolib.networks.bitcoincash;
882 debug('New network: %O', network);
883 }
884
885 const transaction = utxolib.bitgo.createTransactionFromHex(params.transactionHex, network);
886 if (transaction.ins.length !== params.unspents.length) {
887 throw new Error('length of unspents array should equal to the number of transaction inputs');
888 }
889
890 // decorate transaction with input values for TransactionBuilder instantiation
891 const isUtxoTx = _.isObject(transaction) && Array.isArray((transaction as any).ins);
892 const areValidUnspents = _.isObject(params) && Array.isArray((params as any).unspents);
893 if (isUtxoTx && areValidUnspents) {
894 // extend the transaction inputs with the values
895 const inputValues = _.map((params as any).unspents, (u => _.pick(u, 'value')));
896 transaction.ins.map((currentItem, index) => _.extend(currentItem, inputValues[index]));
897 }
898
899 let rootExtKey;
900 if (keychain) {
901 rootExtKey = bip32.fromBase58(keychain.xprv);
902 }
903
904 const txb = utxolib.bitgo.createTransactionBuilderFromTransaction(transaction);
905
906 for (let index = 0; index < txb.tx.ins.length; ++index) {
907 const currentUnspent = params.unspents[index];
908 if (currentUnspent.redeemScript === false) {
909 // this is the input from a single key fee address
910 if (!feeSingleKey) {
911 throw new Error('single key address used in input but feeSingleKeyWIF not provided');
912 }
913
914 if (enableBCH) {
915 feeSingleKey.network = network;
916 }
917
918 txb.sign(index, feeSingleKey);
919 continue;
920 }
921
922 if (currentUnspent.witnessScript && enableBCH) {
923 throw new Error('BCH does not support segwit inputs');
924 }
925
926 const chainPath = currentUnspent.chainPath;
927 if (rootExtKey) {
928 const { walletSubPath = '/0/0' } = keychain;
929 const path = sanitizeLegacyPath(keychain.path + walletSubPath + chainPath);
930 privKey = rootExtKey.derivePath(path);
931 }
932
933 privKey.network = network;
934
935 // subscript is the part of the output script after the OP_CODESEPARATOR.
936 // Since we are only ever signing p2sh outputs, which do not have
937 // OP_CODESEPARATORS, it is always the output script.
938 const subscript = Buffer.from(currentUnspent.redeemScript, 'hex');
939 currentUnspent.validationScript = subscript;
940
941 // In order to sign with bitcoinjs-lib, we must use its transaction
942 // builder, confusingly named the same exact thing as our transaction
943 // builder, but with inequivalent behavior.
944 try {
945 const witnessScript = currentUnspent.witnessScript ? Buffer.from(currentUnspent.witnessScript, 'hex') : undefined;
946 const sigHash = utxolib.bitgo.getDefaultSigHash(network);
947 txb.sign(index, privKey, subscript, sigHash, currentUnspent.value, witnessScript);
948 } catch (e) {
949 // we need to know what's causing this
950 e.result = {
951 unspent: currentUnspent,
952 };
953 e.message = `Failed to sign input #${index} - ${e.message} - ${JSON.stringify(e.result, null, 4)} - \n${e.stack}`;
954 debug('input sign failed: %s', e.message);
955 return Bluebird.reject(e);
956 }
957 }
958
959 const partialTransaction = txb.buildIncomplete();
960
961 if (validate) {
962 partialTransaction.ins.forEach((input, index) => {
963 const signatureCount = utxolib.bitgo.getSignatureVerifications(
964 partialTransaction, index, params.unspents[index].value
965 ).filter(v => v.signedBy !== undefined).length;
966 if (signatureCount < 1) {
967 throw new Error('expected at least one valid signature');
968 }
969 if (params.fullLocalSigning && signatureCount < 2) {
970 throw new Error('fullLocalSigning set: expected at least two valid signatures');
971 }
972 });
973 }
974
975 return Bluebird.resolve({
976 transactionHex: partialTransaction.toHex(),
977 });
978};