UNPKG

20.1 kBJavaScriptView Raw
1/**
2 * Transaction Builder
3 * ===================
4 */
5'use strict'
6
7import { Address } from './address'
8import { Constants as Cst } from './constants'
9import { Bn } from './bn'
10import { HashCache } from './hash-cache'
11import { Script } from './script'
12import { SigOperations } from './sig-operations'
13import { Sig } from './sig'
14import { Struct } from './struct'
15import { Tx } from './tx'
16import { TxIn } from './tx-in'
17import { TxOut } from './tx-out'
18import { TxOutMap } from './tx-out-map'
19import { VarInt } from './var-int'
20
21const Constants = Cst.Default.TxBuilder
22
23class TxBuilder extends Struct {
24 constructor (
25 tx = new Tx(),
26 txIns = [],
27 txOuts = [],
28 uTxOutMap = new TxOutMap(),
29 sigOperations = new SigOperations(),
30 changeScript,
31 changeAmountBn,
32 feeAmountBn,
33 feePerKbNum = Constants.feePerKbNum,
34 nLockTime = 0,
35 versionBytesNum = 1,
36 sigsPerInput = 1,
37 dust = Constants.dust,
38 dustChangeToFees = false,
39 hashCache = new HashCache()
40 ) {
41 super({
42 tx,
43 txIns,
44 txOuts,
45 uTxOutMap,
46 sigOperations,
47 changeScript,
48 changeAmountBn,
49 feeAmountBn,
50 feePerKbNum,
51 nLockTime,
52 versionBytesNum,
53 sigsPerInput,
54 dust,
55 dustChangeToFees,
56 hashCache
57 })
58 }
59
60 toJSON () {
61 const json = {}
62 json.tx = this.tx.toHex()
63 json.txIns = this.txIns.map(txIn => txIn.toHex())
64 json.txOuts = this.txOuts.map(txOut => txOut.toHex())
65 json.uTxOutMap = this.uTxOutMap.toJSON()
66 json.sigOperations = this.sigOperations.toJSON()
67 json.changeScript = this.changeScript ? this.changeScript.toHex() : undefined
68 json.changeAmountBn = this.changeAmountBn ? this.changeAmountBn.toNumber() : undefined
69 json.feeAmountBn = this.feeAmountBn ? this.feeAmountBn.toNumber() : undefined
70 json.feePerKbNum = this.feePerKbNum
71 json.sigsPerInput = this.sigsPerInput
72 json.dust = this.dust
73 json.dustChangeToFees = this.dustChangeToFees
74 json.hashCache = this.hashCache.toJSON()
75 return json
76 }
77
78 fromJSON (json) {
79 this.tx = new Tx().fromHex(json.tx)
80 this.txIns = json.txIns.map(txIn => TxIn.fromHex(txIn))
81 this.txOuts = json.txOuts.map(txOut => TxOut.fromHex(txOut))
82 this.uTxOutMap = new TxOutMap().fromJSON(json.uTxOutMap)
83 this.sigOperations = new SigOperations().fromJSON(json.sigOperations)
84 this.changeScript = json.changeScript ? new Script().fromHex(json.changeScript) : undefined
85 this.changeAmountBn = json.changeAmountBn ? new Bn(json.changeAmountBn) : undefined
86 this.feeAmountBn = json.feeAmountBn ? new Bn(json.feeAmountBn) : undefined
87 this.feePerKbNum = json.feePerKbNum || this.feePerKbNum
88 this.sigsPerInput = json.sigsPerInput || this.sigsPerInput
89 this.dust = json.dust || this.dust
90 this.dustChangeToFees =
91 json.dustChangeToFees || this.dustChangeToFees
92 this.hashCache = HashCache.fromJSON(json.hashCache)
93 return this
94 }
95
96 setFeePerKbNum (feePerKbNum) {
97 if (typeof feePerKbNum !== 'number' || feePerKbNum <= 0) {
98 throw new Error('cannot set a fee of zero or less')
99 }
100 this.feePerKbNum = feePerKbNum
101 return this
102 }
103
104 setChangeAddress (changeAddress) {
105 this.changeScript = changeAddress.toTxOutScript()
106 return this
107 }
108
109 setChangeScript (changeScript) {
110 this.changeScript = changeScript
111 return this
112 }
113
114 /**
115 * nLockTime is an unsigned integer.
116 */
117 setNLocktime (nLockTime) {
118 this.nLockTime = nLockTime
119 return this
120 }
121
122 setVersion (versionBytesNum) {
123 this.versionBytesNum = versionBytesNum
124 return this
125 }
126
127 /**
128 * Sometimes one of your outputs or the change output will be less than
129 * dust. Values less than dust cannot be broadcast. If you are OK with
130 * sending dust amounts to fees, then set this value to true.
131 */
132 setDust (dust = Constants.dust) {
133 this.dust = dust
134 return this
135 }
136
137 /**
138 * Sometimes one of your outputs or the change output will be less than
139 * dust. Values less than dust cannot be broadcast. If you are OK with
140 * sending dust amounts to fees, then set this value to true. We
141 * preferentially send all dust to the change if possible. However, that
142 * might not be possible if the change itself is less than dust, in which
143 * case all dust goes to fees.
144 */
145 sendDustChangeToFees (dustChangeToFees = false) {
146 this.dustChangeToFees = dustChangeToFees
147 return this
148 }
149
150 /**
151 * Import a transaction partially signed by someone else. The only thing you
152 * can do after this is sign one or more inputs. Usually used for multisig
153 * transactions. uTxOutMap is optional. It is not necessary so long as you
154 * pass in the txOut when you sign. You need to know the output when signing
155 * an input, including the script in the output, which is why this is
156 * necessary when signing an input.
157 */
158 importPartiallySignedTx (tx, uTxOutMap = this.uTxOutMap, sigOperations = this.sigOperations) {
159 this.tx = tx
160 this.uTxOutMap = uTxOutMap
161 this.sigOperations = sigOperations
162 return this
163 }
164
165 /**
166 * Pay "from" a script - in other words, add an input to the transaction.
167 */
168 inputFromScript (txHashBuf, txOutNum, txOut, script, nSequence) {
169 if (
170 !Buffer.isBuffer(txHashBuf) ||
171 !(typeof txOutNum === 'number') ||
172 !(txOut instanceof TxOut) ||
173 !(script instanceof Script)
174 ) {
175 throw new Error('invalid one of: txHashBuf, txOutNum, txOut, script')
176 }
177 this.txIns.push(
178 TxIn.fromProperties(txHashBuf, txOutNum, script, nSequence)
179 )
180 this.uTxOutMap.set(txHashBuf, txOutNum, txOut)
181 return this
182 }
183
184 addSigOperation (txHashBuf, txOutNum, nScriptChunk, type, addressStr, nHashType) {
185 this.sigOperations.addOne(txHashBuf, txOutNum, nScriptChunk, type, addressStr, nHashType)
186 return this
187 }
188
189 /**
190 * Pay "from" a pubKeyHash output - in other words, add an input to the
191 * transaction.
192 */
193 inputFromPubKeyHash (txHashBuf, txOutNum, txOut, pubKey, nSequence, nHashType) {
194 if (
195 !Buffer.isBuffer(txHashBuf) ||
196 typeof txOutNum !== 'number' ||
197 !(txOut instanceof TxOut)
198 ) {
199 throw new Error('invalid one of: txHashBuf, txOutNum, txOut')
200 }
201 this.txIns.push(
202 new TxIn()
203 .fromObject({ nSequence })
204 .fromPubKeyHashTxOut(txHashBuf, txOutNum, txOut, pubKey)
205 )
206 this.uTxOutMap.set(txHashBuf, txOutNum, txOut)
207 const addressStr = Address.fromTxOutScript(txOut.script).toString()
208 this.addSigOperation(txHashBuf, txOutNum, 0, 'sig', addressStr, nHashType)
209 this.addSigOperation(txHashBuf, txOutNum, 1, 'pubKey', addressStr)
210 return this
211 }
212
213 /**
214 * An address to send funds to, along with the amount. The amount should be
215 * denominated in satoshis, not bitcoins.
216 */
217 outputToAddress (valueBn, addr) {
218 if (!(addr instanceof Address) || !(valueBn instanceof Bn)) {
219 throw new Error('addr must be an Address, and valueBn must be a Bn')
220 }
221 const script = new Script().fromPubKeyHash(addr.hashBuf)
222 this.outputToScript(valueBn, script)
223 return this
224 }
225
226 /**
227 * A script to send funds to, along with the amount. The amount should be
228 * denominated in satoshis, not bitcoins.
229 */
230 outputToScript (valueBn, script) {
231 if (!(script instanceof Script) || !(valueBn instanceof Bn)) {
232 throw new Error('script must be a Script, and valueBn must be a Bn')
233 }
234 const txOut = TxOut.fromProperties(valueBn, script)
235 this.txOuts.push(txOut)
236 return this
237 }
238
239 buildOutputs () {
240 let outAmountBn = new Bn(0)
241 this.txOuts.forEach(txOut => {
242 if (txOut.valueBn.lt(this.dust) && !txOut.script.isOpReturn() && !txOut.script.isSafeDataOut()) {
243 throw new Error('cannot create output lesser than dust')
244 }
245 outAmountBn = outAmountBn.add(txOut.valueBn)
246 this.tx.addTxOut(txOut)
247 })
248 return outAmountBn
249 }
250
251 buildInputs (outAmountBn, extraInputsNum = 0) {
252 let inAmountBn = new Bn(0)
253 for (const txIn of this.txIns) {
254 const txOut = this.uTxOutMap.get(txIn.txHashBuf, txIn.txOutNum)
255 inAmountBn = inAmountBn.add(txOut.valueBn)
256 this.tx.addTxIn(txIn)
257 if (inAmountBn.geq(outAmountBn)) {
258 if (extraInputsNum <= 0) {
259 break
260 }
261 extraInputsNum--
262 }
263 }
264 if (inAmountBn.lt(outAmountBn)) {
265 throw new Error(
266 'not enough funds for outputs: inAmountBn ' +
267 inAmountBn.toNumber() +
268 ' outAmountBn ' +
269 outAmountBn.toNumber()
270 )
271 }
272 return inAmountBn
273 }
274
275 // Thanks to SigOperations, if those are accurately used, then we can
276 // accurately estimate what the size of the transaction is going to be once
277 // all the signatures and public keys are inserted.
278 estimateSize () {
279 // largest possible sig size. final 1 is for pushdata at start. second to
280 // final is sighash byte. the rest are DER encoding.
281 const sigSize = 1 + 1 + 1 + 1 + 32 + 1 + 1 + 32 + 1 + 1
282 // length of script, y odd, x value - assumes compressed public key
283 const pubKeySize = 1 + 1 + 33
284
285 let size = this.tx.toBuffer().length
286
287 this.tx.txIns.forEach((txIn) => {
288 const { txHashBuf, txOutNum } = txIn
289 const sigOperations = this.sigOperations.get(txHashBuf, txOutNum)
290 sigOperations.forEach((obj) => {
291 const { nScriptChunk, type } = obj
292 const script = new Script([txIn.script.chunks[nScriptChunk]])
293 const scriptSize = script.toBuffer().length
294 size -= scriptSize
295 if (type === 'sig') {
296 size += sigSize
297 } else if (obj.type === 'pubKey') {
298 size += pubKeySize
299 } else {
300 throw new Error('unsupported sig operations type')
301 }
302 })
303 })
304
305 // size = size + sigSize * this.tx.txIns.length
306 size = size + 1 // assume txInsVi increases by 1 byte
307 return Math.round(size)
308 }
309
310 estimateFee (extraFeeAmount = new Bn(0)) {
311 // old style rounding up per kb - pays too high fees:
312 // const fee = Math.ceil(this.estimateSize() / 1000) * this.feePerKbNum
313
314 // new style pays lower fees - rounds up to satoshi, not per kb:
315 const fee = Math.ceil(this.estimateSize() / 1000 * this.feePerKbNum)
316
317 return new Bn(fee).add(extraFeeAmount)
318 }
319
320 /**
321 * Builds the transaction and adds the appropriate fee by subtracting from
322 * the change output. Note that by default the TxBuilder will use as many
323 * inputs as necessary to pay the output amounts and the required fee. The
324 * TxBuilder will not necessarily us all the inputs. To force the TxBuilder
325 * to use all the inputs (such as if you wish to spend the entire balance
326 * of a wallet), set the argument useAllInputs = true.
327 */
328 build (opts = { useAllInputs: false }) {
329 let minFeeAmountBn
330 if (this.txIns.length <= 0) {
331 throw Error('tx-builder number of inputs must be greater than 0')
332 }
333 if (!this.changeScript) {
334 throw new Error('must specify change script to use build method')
335 }
336 for (
337 let extraInputsNum = opts.useAllInputs ? this.txIns.length - 1 : 0;
338 extraInputsNum < this.txIns.length;
339 extraInputsNum++
340 ) {
341 this.tx = new Tx()
342 const outAmountBn = this.buildOutputs()
343 const changeTxOut = TxOut.fromProperties(new Bn(0), this.changeScript)
344 this.tx.addTxOut(changeTxOut)
345
346 let inAmountBn
347 try {
348 inAmountBn = this.buildInputs(outAmountBn, extraInputsNum)
349 } catch (err) {
350 if (err.message.includes('not enough funds for outputs')) {
351 throw new Error('unable to gather enough inputs for outputs and fee')
352 } else {
353 throw err
354 }
355 }
356
357 // Set change amount from inAmountBn - outAmountBn
358 this.changeAmountBn = inAmountBn.sub(outAmountBn)
359 changeTxOut.valueBn = this.changeAmountBn
360
361 minFeeAmountBn = this.estimateFee()
362 if (
363 this.changeAmountBn.geq(minFeeAmountBn) &&
364 this.changeAmountBn.sub(minFeeAmountBn).gt(this.dust)
365 ) {
366 break
367 }
368 }
369 if (this.changeAmountBn.geq(minFeeAmountBn)) {
370 // Subtract fee from change
371 this.feeAmountBn = minFeeAmountBn
372 this.changeAmountBn = this.changeAmountBn.sub(this.feeAmountBn)
373 this.tx.txOuts[this.tx.txOuts.length - 1].valueBn = this.changeAmountBn
374
375 if (this.changeAmountBn.lt(this.dust)) {
376 if (this.dustChangeToFees) {
377 // Remove the change amount since it is less than dust and the
378 // builder has requested dust be sent to fees.
379 this.tx.txOuts.pop()
380 this.tx.txOutsVi = VarInt.fromNumber(this.tx.txOutsVi.toNumber() - 1)
381 this.feeAmountBn = this.feeAmountBn.add(this.changeAmountBn)
382 this.changeAmountBn = new Bn(0)
383 } else {
384 throw new Error('unable to create change amount greater than dust')
385 }
386 }
387
388 this.tx.nLockTime = this.nLockTime
389 this.tx.versionBytesNum = this.versionBytesNum
390 if (this.tx.txOuts.length === 0) {
391 throw new Error(
392 'outputs length is zero - unable to create any outputs greater than dust'
393 )
394 }
395 return this
396 } else {
397 throw new Error('unable to gather enough inputs for outputs and fee')
398 }
399 }
400
401 // BIP 69 sorting. call after build() but before sign()
402 sort () {
403 this.tx.sort()
404 return this
405 }
406
407 /**
408 * Check if all signatures are present in a multisig input script.
409 */
410 static allSigsPresent (m, script) {
411 // The first element is a Famous MultiSig Bug OP_0, and last element is the
412 // redeemScript. The rest are signatures.
413 let present = 0
414 for (let i = 1; i < script.chunks.length - 1; i++) {
415 if (script.chunks[i].buf) {
416 present++
417 }
418 }
419 return present === m
420 }
421
422 /**
423 * Remove blank signatures in a multisig input script.
424 */
425 static removeBlankSigs (script) {
426 // The first element is a Famous MultiSig Bug OP_0, and last element is the
427 // redeemScript. The rest are signatures.
428 script = new Script(script.chunks.slice()) // copy the script
429 for (let i = 1; i < script.chunks.length - 1; i++) {
430 if (!script.chunks[i].buf) {
431 script.chunks.splice(i, 1) // remove ith element
432 }
433 }
434 return script
435 }
436
437 fillSig (nIn, nScriptChunk, sig) {
438 const txIn = this.tx.txIns[nIn]
439 txIn.script.chunks[nScriptChunk] = new Script().writeBuffer(
440 sig.toTxFormat()
441 ).chunks[0]
442 txIn.scriptVi = VarInt.fromNumber(txIn.script.toBuffer().length)
443 return this
444 }
445
446 /**
447 * Sign an input, but do not fill the signature into the transaction. Return
448 * the signature.
449 *
450 * For a normal transaction, subScript is usually the scriptPubKey. If
451 * you're not normal because you're using OP_CODESEPARATORs, you know what
452 * to do.
453 */
454 getSig (keyPair, nHashType = Sig.SIGHASH_ALL | Sig.SIGHASH_FORKID, nIn, subScript, flags = Tx.SCRIPT_ENABLE_SIGHASH_FORKID) {
455 let valueBn
456 if (
457 nHashType & Sig.SIGHASH_FORKID &&
458 flags & Tx.SCRIPT_ENABLE_SIGHASH_FORKID
459 ) {
460 const txHashBuf = this.tx.txIns[nIn].txHashBuf
461 const txOutNum = this.tx.txIns[nIn].txOutNum
462 const txOut = this.uTxOutMap.get(txHashBuf, txOutNum)
463 if (!txOut) {
464 throw new Error('for SIGHASH_FORKID must provide UTXOs')
465 }
466 valueBn = txOut.valueBn
467 }
468 return this.tx.sign(keyPair, nHashType, nIn, subScript, valueBn, flags, this.hashCache)
469 }
470
471 /**
472 * Asynchronously sign an input in a worker, but do not fill the signature
473 * into the transaction. Return the signature.
474 */
475 asyncGetSig (keyPair, nHashType = Sig.SIGHASH_ALL | Sig.SIGHASH_FORKID, nIn, subScript, flags = Tx.SCRIPT_ENABLE_SIGHASH_FORKID) {
476 let valueBn
477 if (
478 nHashType & Sig.SIGHASH_FORKID &&
479 flags & Tx.SCRIPT_ENABLE_SIGHASH_FORKID
480 ) {
481 const txHashBuf = this.tx.txIns[nIn].txHashBuf
482 const txOutNum = this.tx.txIns[nIn].txOutNum
483 const txOut = this.uTxOutMap.get(txHashBuf, txOutNum)
484 if (!txOut) {
485 throw new Error('for SIGHASH_FORKID must provide UTXOs')
486 }
487 valueBn = txOut.valueBn
488 }
489 return this.tx.asyncSign(
490 keyPair,
491 nHashType,
492 nIn,
493 subScript,
494 valueBn,
495 flags,
496 this.hashCache
497 )
498 }
499
500 /**
501 * Sign ith input with keyPair and insert the signature into the transaction.
502 * This method only works for some standard transaction types. For
503 * non-standard transaction types, use getSig.
504 */
505 signTxIn (nIn, keyPair, txOut, nScriptChunk, nHashType = Sig.SIGHASH_ALL | Sig.SIGHASH_FORKID, flags = Tx.SCRIPT_ENABLE_SIGHASH_FORKID) {
506 const txIn = this.tx.txIns[nIn]
507 const script = txIn.script
508 if (nScriptChunk === undefined && script.isPubKeyHashIn()) {
509 nScriptChunk = 0
510 }
511 if (nScriptChunk === undefined) {
512 throw new Error('cannot sign unknown script type for input ' + nIn)
513 }
514 const txHashBuf = txIn.txHashBuf
515 const txOutNum = txIn.txOutNum
516 if (!txOut) {
517 txOut = this.uTxOutMap.get(txHashBuf, txOutNum)
518 }
519 const outScript = txOut.script
520 const subScript = outScript // true for standard script types
521 const sig = this.getSig(keyPair, nHashType, nIn, subScript, flags, this.hashCache)
522 this.fillSig(nIn, nScriptChunk, sig)
523 return this
524 }
525
526 /**
527 * Asynchronously sign ith input with keyPair in a worker and insert the
528 * signature into the transaction. This method only works for some standard
529 * transaction types. For non-standard transaction types, use asyncGetSig.
530 */
531 async asyncSignTxIn (nIn, keyPair, txOut, nScriptChunk, nHashType = Sig.SIGHASH_ALL | Sig.SIGHASH_FORKID, flags = Tx.SCRIPT_ENABLE_SIGHASH_FORKID) {
532 const txIn = this.tx.txIns[nIn]
533 const script = txIn.script
534 if (nScriptChunk === undefined && script.isPubKeyHashIn()) {
535 nScriptChunk = 0
536 }
537 if (nScriptChunk === undefined) {
538 throw new Error('cannot sign unknown script type for input ' + nIn)
539 }
540 const txHashBuf = txIn.txHashBuf
541 const txOutNum = txIn.txOutNum
542 if (!txOut) {
543 txOut = this.uTxOutMap.get(txHashBuf, txOutNum)
544 }
545 const outScript = txOut.script
546 const subScript = outScript // true for standard script types
547 const sig = await this.asyncGetSig(keyPair, nHashType, nIn, subScript, flags, this.hashCache)
548 this.fillSig(nIn, nScriptChunk, sig)
549 return this
550 }
551
552 signWithKeyPairs (keyPairs) {
553 // produce map of addresses to private keys
554 const addressStrMap = {}
555 for (const keyPair of keyPairs) {
556 const addressStr = Address.fromPubKey(keyPair.pubKey).toString()
557 addressStrMap[addressStr] = keyPair
558 }
559 // loop through all inputs
560 for (const nIn in this.tx.txIns) {
561 const txIn = this.tx.txIns[nIn]
562 // for each input, use sigOperations to get list of signatures and pubkeys
563 // to be produced and inserted
564 const arr = this.sigOperations.get(txIn.txHashBuf, txIn.txOutNum)
565 for (const obj of arr) {
566 // for each pubkey, get the privkey from the privkey map and sign the input
567 const { nScriptChunk, type, addressStr, nHashType } = obj
568 const keyPair = addressStrMap[addressStr]
569 if (!keyPair) {
570 obj.log = `cannot find keyPair for addressStr ${addressStr}`
571 continue
572 }
573 const txOut = this.uTxOutMap.get(txIn.txHashBuf, txIn.txOutNum)
574 if (type === 'sig') {
575 this.signTxIn(nIn, keyPair, txOut, nScriptChunk, nHashType)
576 obj.log = 'successfully inserted signature'
577 } else if (type === 'pubKey') {
578 txIn.script.chunks[nScriptChunk] = new Script().writeBuffer(
579 keyPair.pubKey.toBuffer()
580 ).chunks[0]
581 txIn.setScript(txIn.script)
582 obj.log = 'successfully inserted public key'
583 } else {
584 obj.log = `cannot perform operation of type ${type}`
585 continue
586 }
587 }
588 }
589 return this
590 }
591}
592
593export { TxBuilder }