UNPKG

14.7 kBJavaScriptView Raw
1var baddress = require('./address')
2var bcrypto = require('./crypto')
3var bscript = require('./script')
4var bufferReverse = require('buffer-reverse')
5var networks = require('./networks')
6var ops = require('./opcodes.json')
7var typeforce = require('typeforce')
8var types = require('./types')
9
10var ECPair = require('./ecpair')
11var ECSignature = require('./ecsignature')
12var Transaction = require('./transaction')
13
14// inspects a scriptSig w/ optional redeemScript and
15// derives any input information required
16function expandInput (scriptSig, redeemScript) {
17 var scriptSigChunks = bscript.decompile(scriptSig)
18 var prevOutType = bscript.classifyInput(scriptSigChunks, true)
19 var pubKeys, signatures, prevOutScript
20
21 switch (prevOutType) {
22 case 'scripthash':
23 // FIXME: maybe depth limit instead, how possible is this anyway?
24 if (redeemScript) throw new Error('Recursive P2SH script')
25
26 var redeemScriptSig = scriptSigChunks.slice(0, -1)
27 redeemScript = scriptSigChunks[scriptSigChunks.length - 1]
28
29 var result = expandInput(redeemScriptSig, redeemScript)
30 result.redeemScript = redeemScript
31 result.redeemScriptType = result.prevOutType
32 result.prevOutScript = bscript.scriptHashOutput(bcrypto.hash160(redeemScript))
33 result.prevOutType = 'scripthash'
34 return result
35
36 case 'pubkeyhash':
37 // if (redeemScript) throw new Error('Nonstandard... P2SH(P2PKH)')
38 pubKeys = scriptSigChunks.slice(1)
39 signatures = scriptSigChunks.slice(0, 1)
40
41 if (redeemScript) break
42 prevOutScript = bscript.pubKeyHashOutput(bcrypto.hash160(pubKeys[0]))
43 break
44
45 case 'pubkey':
46 if (redeemScript) {
47 pubKeys = bscript.decompile(redeemScript).slice(0, 1)
48 }
49
50 signatures = scriptSigChunks.slice(0, 1)
51 break
52
53 case 'multisig':
54 if (redeemScript) {
55 pubKeys = bscript.decompile(redeemScript).slice(1, -2)
56 }
57
58 signatures = scriptSigChunks.slice(1).map(function (chunk) {
59 return chunk === ops.OP_0 ? undefined : chunk
60 })
61 break
62 }
63
64 return {
65 pubKeys: pubKeys,
66 signatures: signatures,
67 prevOutScript: prevOutScript,
68 prevOutType: prevOutType
69 }
70}
71
72// could be done in expandInput, but requires the original Transaction for hashForSignature
73function fixMultisigOrder (input, transaction, vin) {
74 if (input.redeemScriptType !== 'multisig' || !input.redeemScript) return
75 if (input.pubKeys.length === input.signatures.length) return
76
77 var unmatched = input.signatures.concat()
78
79 input.signatures = input.pubKeys.map(function (pubKey, y) {
80 var keyPair = ECPair.fromPublicKeyBuffer(pubKey)
81 var match
82
83 // check for a signature
84 unmatched.some(function (signature, i) {
85 // skip if undefined || OP_0
86 if (!signature) return false
87
88 // TODO: avoid O(n) hashForSignature
89 var parsed = ECSignature.parseScriptSignature(signature)
90 var hash = transaction.hashForSignature(vin, input.redeemScript, parsed.hashType)
91
92 // skip if signature does not match pubKey
93 if (!keyPair.verify(hash, parsed.signature)) return false
94
95 // remove matched signature from unmatched
96 unmatched[i] = undefined
97 match = signature
98
99 return true
100 })
101
102 return match
103 })
104}
105
106function expandOutput (script, scriptType, ourPubKey) {
107 typeforce(types.Buffer, script)
108
109 var scriptChunks = bscript.decompile(script)
110 if (!scriptType) {
111 scriptType = bscript.classifyOutput(scriptChunks)
112 }
113
114 var pubKeys = []
115
116 switch (scriptType) {
117 // does our hash160(pubKey) match the output scripts?
118 case 'pubkeyhash':
119 if (!ourPubKey) break
120
121 var pkh1 = scriptChunks[2]
122 var pkh2 = bcrypto.hash160(ourPubKey)
123 if (pkh1.equals(pkh2)) pubKeys = [ourPubKey]
124 break
125
126 case 'pubkey':
127 pubKeys = scriptChunks.slice(0, 1)
128 break
129
130 case 'multisig':
131 pubKeys = scriptChunks.slice(1, -2)
132 break
133
134 default: return { scriptType: scriptType }
135 }
136
137 return {
138 pubKeys: pubKeys,
139 scriptType: scriptType,
140 signatures: pubKeys.map(function () { return undefined })
141 }
142}
143
144function prepareInput (input, kpPubKey, redeemScript) {
145 if (redeemScript) {
146 var redeemScriptHash = bcrypto.hash160(redeemScript)
147
148 // if redeemScript exists, it is pay-to-scriptHash
149 // if we have a prevOutScript, enforce hash160(redeemScriptequality) to the redeemScript
150 if (input.prevOutType) {
151 if (input.prevOutType !== 'scripthash') throw new Error('PrevOutScript must be P2SH')
152
153 var prevOutScriptScriptHash = bscript.decompile(input.prevOutScript)[1]
154 if (!prevOutScriptScriptHash.equals(redeemScriptHash)) throw new Error('Inconsistent hash160(RedeemScript)')
155 }
156
157 var expanded = expandOutput(redeemScript, undefined, kpPubKey)
158 if (!expanded.pubKeys) throw new Error('RedeemScript not supported "' + bscript.toASM(redeemScript) + '"')
159
160 input.pubKeys = expanded.pubKeys
161 input.signatures = expanded.signatures
162 input.redeemScript = redeemScript
163 input.redeemScriptType = expanded.scriptType
164 input.prevOutScript = input.prevOutScript || bscript.scriptHashOutput(redeemScriptHash)
165 input.prevOutType = 'scripthash'
166
167 // maybe we have some prevOut knowledge
168 } else if (input.prevOutType) {
169 // pay-to-scriptHash is not possible without a redeemScript
170 if (input.prevOutType === 'scripthash') throw new Error('PrevOutScript is P2SH, missing redeemScript')
171
172 // try to derive missing information using our kpPubKey
173 expanded = expandOutput(input.prevOutScript, input.prevOutType, kpPubKey)
174 if (!expanded.pubKeys) return
175
176 input.pubKeys = expanded.pubKeys
177 input.signatures = expanded.signatures
178
179 // no prior knowledge, assume pubKeyHash
180 } else {
181 input.prevOutScript = bscript.pubKeyHashOutput(bcrypto.hash160(kpPubKey))
182 input.prevOutType = 'pubkeyhash'
183 input.pubKeys = [kpPubKey]
184 input.signatures = [undefined]
185 }
186}
187
188function buildInput (input, allowIncomplete) {
189 var signatures = input.signatures
190 var scriptType = input.redeemScriptType || input.prevOutType
191 var scriptSig
192
193 switch (scriptType) {
194 case 'pubkeyhash':
195 case 'pubkey':
196 if (signatures.length < 1 || !signatures[0]) throw new Error('Not enough signatures provided')
197 if (scriptType === 'pubkeyhash') {
198 scriptSig = bscript.pubKeyHashInput(signatures[0], input.pubKeys[0])
199 } else {
200 scriptSig = bscript.pubKeyInput(signatures[0])
201 }
202
203 break
204
205 // ref https://github.com/bitcoin/bitcoin/blob/d612837814020ae832499d18e6ee5eb919a87907/src/script/sign.cpp#L232
206 case 'multisig':
207 signatures = signatures.map(function (signature) {
208 return signature || ops.OP_0
209 })
210
211 if (!allowIncomplete) {
212 // remove blank signatures
213 signatures = signatures.filter(function (x) { return x !== ops.OP_0 })
214 }
215
216 scriptSig = bscript.multisigInput(signatures, allowIncomplete ? undefined : input.redeemScript)
217 break
218
219 default: return
220 }
221
222 // wrap as scriptHash if necessary
223 if (input.prevOutType === 'scripthash') {
224 scriptSig = bscript.scriptHashInput(scriptSig, input.redeemScript)
225 }
226
227 return scriptSig
228}
229
230function TransactionBuilder (network) {
231 this.prevTxMap = {}
232 this.network = network || networks.bitcoin
233
234 this.inputs = []
235 this.tx = new Transaction()
236}
237
238TransactionBuilder.prototype.setLockTime = function (locktime) {
239 typeforce(types.UInt32, locktime)
240
241 // if any signatures exist, throw
242 if (this.inputs.some(function (input) {
243 if (!input.signatures) return false
244
245 return input.signatures.some(function (s) { return s })
246 })) {
247 throw new Error('No, this would invalidate signatures')
248 }
249
250 this.tx.locktime = locktime
251}
252
253TransactionBuilder.prototype.setVersion = function (version) {
254 typeforce(types.UInt32, version)
255
256 // XXX: this might eventually become more complex depending on what the versions represent
257 this.tx.version = version
258}
259
260TransactionBuilder.fromTransaction = function (transaction, network) {
261 var txb = new TransactionBuilder(network)
262
263 // Copy transaction fields
264 txb.setVersion(transaction.version)
265 txb.setLockTime(transaction.locktime)
266
267 // Copy outputs (done first to avoid signature invalidation)
268 transaction.outs.forEach(function (txOut) {
269 txb.addOutput(txOut.script, txOut.value)
270 })
271
272 // Copy inputs
273 transaction.ins.forEach(function (txIn) {
274 txb.__addInputUnsafe(txIn.hash, txIn.index, txIn.sequence, txIn.script)
275 })
276
277 // fix some things not possible through the public API
278 txb.inputs.forEach(function (input, i) {
279 fixMultisigOrder(input, transaction, i)
280 })
281
282 return txb
283}
284
285TransactionBuilder.prototype.addInput = function (txHash, vout, sequence, prevOutScript) {
286 if (!this.__canModifyInputs()) {
287 throw new Error('No, this would invalidate signatures')
288 }
289
290 // is it a hex string?
291 if (typeof txHash === 'string') {
292 // transaction hashs's are displayed in reverse order, un-reverse it
293 txHash = bufferReverse(new Buffer(txHash, 'hex'))
294
295 // is it a Transaction object?
296 } else if (txHash instanceof Transaction) {
297 prevOutScript = txHash.outs[vout].script
298 txHash = txHash.getHash()
299 }
300
301 return this.__addInputUnsafe(txHash, vout, sequence, null, prevOutScript)
302}
303
304TransactionBuilder.prototype.__addInputUnsafe = function (txHash, vout, sequence, scriptSig, prevOutScript) {
305 if (Transaction.isCoinbaseHash(txHash)) {
306 throw new Error('coinbase inputs not supported')
307 }
308
309 var prevTxOut = txHash.toString('hex') + ':' + vout
310 if (this.prevTxMap[prevTxOut]) throw new Error('Duplicate TxOut: ' + prevTxOut)
311
312 var input = {}
313
314 // derive what we can from the scriptSig
315 if (scriptSig) {
316 input = expandInput(scriptSig)
317 }
318
319 // derive what we can from the previous transactions output script
320 if (!input.prevOutScript && prevOutScript) {
321 var prevOutType
322
323 if (!input.pubKeys && !input.signatures) {
324 var expanded = expandOutput(prevOutScript)
325
326 if (expanded.pubKeys) {
327 input.pubKeys = expanded.pubKeys
328 input.signatures = expanded.signatures
329 }
330
331 prevOutType = expanded.scriptType
332 }
333
334 input.prevOutScript = prevOutScript
335 input.prevOutType = prevOutType || bscript.classifyOutput(prevOutScript)
336 }
337
338 var vin = this.tx.addInput(txHash, vout, sequence, scriptSig)
339 this.inputs[vin] = input
340 this.prevTxMap[prevTxOut] = true
341
342 return vin
343}
344
345TransactionBuilder.prototype.addOutput = function (scriptPubKey, value) {
346 if (!this.__canModifyOutputs()) {
347 throw new Error('No, this would invalidate signatures')
348 }
349
350 // Attempt to get a script if it's a base58 address string
351 if (typeof scriptPubKey === 'string') {
352 scriptPubKey = baddress.toOutputScript(scriptPubKey, this.network)
353 }
354
355 return this.tx.addOutput(scriptPubKey, value)
356}
357
358TransactionBuilder.prototype.build = function () {
359 return this.__build(false)
360}
361TransactionBuilder.prototype.buildIncomplete = function () {
362 return this.__build(true)
363}
364
365TransactionBuilder.prototype.__build = function (allowIncomplete) {
366 if (!allowIncomplete) {
367 if (!this.tx.ins.length) throw new Error('Transaction has no inputs')
368 if (!this.tx.outs.length) throw new Error('Transaction has no outputs')
369 }
370
371 var tx = this.tx.clone()
372
373 // Create script signatures from inputs
374 this.inputs.forEach(function (input, i) {
375 var scriptType = input.redeemScriptType || input.prevOutType
376 if (!scriptType && !allowIncomplete) throw new Error('Transaction is not complete')
377
378 // build a scriptSig
379 var scriptSig = buildInput(input, allowIncomplete)
380
381 // skip if no scriptSig exists
382 if (!scriptSig) {
383 if (!allowIncomplete) throw new Error(scriptType + ' not supported')
384 return
385 }
386
387 tx.setInputScript(i, scriptSig)
388 })
389
390 return tx
391}
392
393function canSign (input) {
394 return input.prevOutScript !== undefined &&
395 input.pubKeys !== undefined &&
396 input.signatures !== undefined &&
397 input.signatures.length === input.pubKeys.length &&
398 input.pubKeys.length > 0
399}
400
401TransactionBuilder.prototype.sign = function (vin, keyPair, redeemScript, hashType) {
402 if (keyPair.network !== this.network) throw new Error('Inconsistent network')
403 if (!this.inputs[vin]) throw new Error('No input at index: ' + vin)
404 hashType = hashType || Transaction.SIGHASH_ALL
405
406 var input = this.inputs[vin]
407
408 // if redeemScript was previously provided, enforce consistency
409 if (input.redeemScript !== undefined &&
410 redeemScript &&
411 !input.redeemScript.equals(redeemScript)) {
412 throw new Error('Inconsistent redeemScript')
413 }
414
415 var kpPubKey = keyPair.getPublicKeyBuffer()
416 if (!canSign(input)) {
417 prepareInput(input, kpPubKey, redeemScript)
418
419 if (!canSign(input)) throw Error(input.prevOutType + ' not supported')
420 }
421
422 // ready to sign
423 var hashScript = input.redeemScript || input.prevOutScript
424 var signatureHash = this.tx.hashForSignature(vin, hashScript, hashType)
425
426 // enforce in order signing of public keys
427 var signed = input.pubKeys.some(function (pubKey, i) {
428 if (!kpPubKey.equals(pubKey)) return false
429 if (input.signatures[i]) throw new Error('Signature already exists')
430
431 input.signatures[i] = keyPair.sign(signatureHash).toScriptSignature(hashType)
432 return true
433 })
434
435 if (!signed) throw new Error('Key pair cannot sign for this input')
436}
437
438function signatureHashType (buffer) {
439 return buffer.readUInt8(buffer.length - 1)
440}
441
442TransactionBuilder.prototype.__canModifyInputs = function () {
443 return this.inputs.every(function (input) {
444 // any signatures?
445 if (input.signatures === undefined) return true
446
447 return input.signatures.every(function (signature) {
448 if (!signature) return true
449 var hashType = signatureHashType(signature)
450
451 // if SIGHASH_ANYONECANPAY is set, signatures would not
452 // be invalidated by more inputs
453 return hashType & Transaction.SIGHASH_ANYONECANPAY
454 })
455 })
456}
457
458TransactionBuilder.prototype.__canModifyOutputs = function () {
459 var nInputs = this.tx.ins.length
460 var nOutputs = this.tx.outs.length
461
462 return this.inputs.every(function (input) {
463 if (input.signatures === undefined) return true
464
465 return input.signatures.every(function (signature) {
466 if (!signature) return true
467 var hashType = signatureHashType(signature)
468
469 var hashTypeMod = hashType & 0x1f
470 if (hashTypeMod === Transaction.SIGHASH_NONE) return true
471 if (hashTypeMod === Transaction.SIGHASH_SINGLE) {
472 // if SIGHASH_SINGLE is set, and nInputs > nOutputs
473 // some signatures would be invalidated by the addition
474 // of more outputs
475 return nInputs <= nOutputs
476 }
477 })
478 })
479}
480
481module.exports = TransactionBuilder