UNPKG

12.7 kBJavaScriptView Raw
1var bcrypto = require('./crypto')
2var bscript = require('./script')
3var bufferutils = require('./bufferutils')
4var bufferReverse = require('buffer-reverse')
5var opcodes = require('./opcodes.json')
6var typeforce = require('typeforce')
7var types = require('./types')
8
9function Transaction () {
10 this.version = 1
11 this.locktime = 0
12 this.ins = []
13 this.outs = []
14 this.joinsplits = []
15}
16
17Transaction.DEFAULT_SEQUENCE = 0xffffffff
18Transaction.SIGHASH_ALL = 0x01
19Transaction.SIGHASH_NONE = 0x02
20Transaction.SIGHASH_SINGLE = 0x03
21Transaction.SIGHASH_ANYONECANPAY = 0x80
22
23Transaction.ZCASH_NUM_JS_INPUTS = 2
24Transaction.ZCASH_NUM_JS_OUTPUTS = 2
25Transaction.ZCASH_NOTECIPHERTEXT_SIZE = 1 + 8 + 32 + 32 + 512 + 16
26
27Transaction.ZCASH_G1_PREFIX_MASK = 0x02
28Transaction.ZCASH_G2_PREFIX_MASK = 0x0a
29
30Transaction.fromBuffer = function (buffer, zcash) {
31 var offset = 0
32 function readSlice (n) {
33 offset += n
34 return buffer.slice(offset - n, offset)
35 }
36
37 function readUInt8 () {
38 var i = buffer.readUInt8(offset)
39 offset += 1
40 return i
41 }
42
43 function readUInt32 () {
44 var i = buffer.readUInt32LE(offset)
45 offset += 4
46 return i
47 }
48
49 function readInt32 () {
50 var i = buffer.readInt32LE(offset)
51 offset += 4
52 return i
53 }
54
55 function readUInt64 () {
56 var i = bufferutils.readUInt64LE(buffer, offset)
57 offset += 8
58 return i
59 }
60
61 function readVarInt () {
62 var vi = bufferutils.readVarInt(buffer, offset)
63 offset += vi.size
64 return vi.number
65 }
66
67 function readScript () {
68 return readSlice(readVarInt())
69 }
70
71 function readCompressedG1 () {
72 var yLsb = readUInt8() & 1
73 var x = readSlice(32)
74 return {
75 x: x,
76 yLsb: yLsb
77 }
78 }
79
80 function readCompressedG2 () {
81 var yLsb = readUInt8() & 1
82 var x = readSlice(64)
83 return {
84 x: x,
85 yLsb: yLsb
86 }
87 }
88
89 var tx = new Transaction()
90 tx.version = readInt32()
91
92 var vinLen = readVarInt()
93 for (var i = 0; i < vinLen; ++i) {
94 tx.ins.push({
95 hash: readSlice(32),
96 index: readUInt32(),
97 script: readScript(),
98 sequence: readUInt32()
99 })
100 }
101
102 var voutLen = readVarInt()
103 for (i = 0; i < voutLen; ++i) {
104 tx.outs.push({
105 value: readUInt64(),
106 script: readScript()
107 })
108 }
109
110 tx.locktime = readUInt32()
111
112 if (tx.version >= 2 && zcash) {
113 var jsLen = readVarInt()
114 for (i = 0; i < jsLen; ++i) {
115 var vpubOld = readUInt64()
116 var vpubNew = readUInt64()
117 var anchor = readSlice(32)
118 var nullifiers = []
119 for (var j = 0; j < Transaction.ZCASH_NUM_JS_INPUTS; j++) {
120 nullifiers.push(readSlice(32))
121 }
122 var commitments = []
123 for (j = 0; j < Transaction.ZCASH_NUM_JS_OUTPUTS; j++) {
124 commitments.push(readSlice(32))
125 }
126 var ephemeralKey = readSlice(32)
127 var randomSeed = readSlice(32)
128 var macs = []
129 for (j = 0; j < Transaction.ZCASH_NUM_JS_INPUTS; j++) {
130 macs.push(readSlice(32))
131 }
132 // TODO what are those exactly? Can it be expressed by BigNum?
133 var zproof = {
134 gA: readCompressedG1(),
135 gAPrime: readCompressedG1(),
136 gB: readCompressedG2(),
137 gBPrime: readCompressedG1(),
138 gC: readCompressedG1(),
139 gCPrime: readCompressedG1(),
140 gK: readCompressedG1(),
141 gH: readCompressedG1()
142 }
143 var ciphertexts = []
144 for (j = 0; j < Transaction.ZCASH_NUM_JS_OUTPUTS; j++) {
145 ciphertexts.push(readSlice(Transaction.ZCASH_NOTECIPHERTEXT_SIZE))
146 }
147
148 tx.joinsplits.push({
149 vpubOld: vpubOld,
150 vpubNew: vpubNew,
151 anchor: anchor,
152 nullifiers: nullifiers,
153 commitments: commitments,
154 ephemeralKey: ephemeralKey,
155 randomSeed: randomSeed,
156 macs: macs,
157 zproof: zproof,
158 ciphertexts: ciphertexts
159 })
160 }
161 if (jsLen > 0) {
162 tx.joinsplitPubkey = readSlice(32)
163 tx.joinsplitSig = readSlice(64)
164 }
165 }
166
167 tx.zcash = zcash
168
169 if (offset !== buffer.length) throw new Error('Transaction has unexpected data')
170
171 return tx
172}
173
174Transaction.fromHex = function (hex, zcash) {
175 return Transaction.fromBuffer(new Buffer(hex, 'hex'), zcash)
176}
177
178Transaction.isCoinbaseHash = function (buffer) {
179 typeforce(types.Hash256bit, buffer)
180 for (var i = 0; i < 32; ++i) {
181 if (buffer[i] !== 0) return false
182 }
183 return true
184}
185
186Transaction.prototype.isCoinbase = function () {
187 return this.ins.length === 1 && Transaction.isCoinbaseHash(this.ins[0].hash)
188}
189
190var EMPTY_SCRIPT = new Buffer(0)
191
192Transaction.prototype.addInput = function (hash, index, sequence, scriptSig) {
193 typeforce(types.tuple(
194 types.Hash256bit,
195 types.UInt32,
196 types.maybe(types.UInt32),
197 types.maybe(types.Buffer)
198 ), arguments)
199
200 if (types.Null(sequence)) {
201 sequence = Transaction.DEFAULT_SEQUENCE
202 }
203
204 // Add the input and return the input's index
205 return (this.ins.push({
206 hash: hash,
207 index: index,
208 script: scriptSig || EMPTY_SCRIPT,
209 sequence: sequence
210 }) - 1)
211}
212
213Transaction.prototype.addOutput = function (scriptPubKey, value) {
214 typeforce(types.tuple(types.Buffer, types.Satoshi), arguments)
215
216 // Add the output and return the output's index
217 return (this.outs.push({
218 script: scriptPubKey,
219 value: value
220 }) - 1)
221}
222
223function scriptSize (someScript) {
224 var length = someScript.length
225
226 return bufferutils.varIntSize(length) + length
227}
228
229Transaction.prototype.joinsplitByteLength = function () {
230 if (this.version < 2) {
231 return 0
232 }
233
234 if (!this.zcash) {
235 return 0
236 }
237
238 var pubkeySigLength = (this.joinsplits.length > 0) ? (32 + 64) : 0;
239 return (
240 bufferutils.varIntSize(this.joinsplits.length) +
241 this.joinsplits.reduce(function (sum, joinsplit) {
242 return (
243 sum +
244 8 + 8 + 32 +
245 joinsplit.nullifiers.length * 32 +
246 joinsplit.commitments.length * 32 +
247 32 + 32 +
248 joinsplit.macs.length * 32 +
249 65 + 33 * 7 +
250 joinsplit.ciphertexts.length * Transaction.ZCASH_NOTECIPHERTEXT_SIZE
251 )
252 }, 0) +
253 pubkeySigLength
254 )
255}
256
257Transaction.prototype.byteLength = function () {
258 return (
259 8 +
260 bufferutils.varIntSize(this.ins.length) +
261 bufferutils.varIntSize(this.outs.length) +
262 this.ins.reduce(function (sum, input) { return sum + 40 + scriptSize(input.script) }, 0) +
263 this.outs.reduce(function (sum, output) { return sum + 8 + scriptSize(output.script) }, 0) +
264 this.joinsplitByteLength()
265 )
266}
267
268Transaction.prototype.clone = function () {
269 var newTx = new Transaction()
270 newTx.version = this.version
271 newTx.locktime = this.locktime
272
273 newTx.ins = this.ins.map(function (txIn) {
274 return {
275 hash: txIn.hash,
276 index: txIn.index,
277 script: txIn.script,
278 sequence: txIn.sequence
279 }
280 })
281
282 newTx.outs = this.outs.map(function (txOut) {
283 return {
284 script: txOut.script,
285 value: txOut.value
286 }
287 })
288
289 return newTx
290}
291
292var ONE = new Buffer('0000000000000000000000000000000000000000000000000000000000000001', 'hex')
293var VALUE_UINT64_MAX = new Buffer('ffffffffffffffff', 'hex')
294var BLANK_OUTPUT = {
295 script: EMPTY_SCRIPT,
296 valueBuffer: VALUE_UINT64_MAX
297}
298
299/**
300 * Hash transaction for signing a specific input.
301 *
302 * Bitcoin uses a different hash for each signed transaction input.
303 * This method copies the transaction, makes the necessary changes based on the
304 * hashType, and then hashes the result.
305 * This hash can then be used to sign the provided transaction input.
306 */
307Transaction.prototype.hashForSignature = function (inIndex, prevOutScript, hashType) {
308 typeforce(types.tuple(types.UInt32, types.Buffer, /* types.UInt8 */ types.Number), arguments)
309
310 // https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L29
311 if (inIndex >= this.ins.length) return ONE
312
313 // ignore OP_CODESEPARATOR
314 var ourScript = bscript.compile(bscript.decompile(prevOutScript).filter(function (x) {
315 return x !== opcodes.OP_CODESEPARATOR
316 }))
317
318 var txTmp = this.clone()
319
320 // SIGHASH_NONE: ignore all outputs? (wildcard payee)
321 if ((hashType & 0x1f) === Transaction.SIGHASH_NONE) {
322 txTmp.outs = []
323
324 // ignore sequence numbers (except at inIndex)
325 txTmp.ins.forEach(function (input, i) {
326 if (i === inIndex) return
327
328 input.sequence = 0
329 })
330
331 // SIGHASH_SINGLE: ignore all outputs, except at the same index?
332 } else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE) {
333 // https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L60
334 if (inIndex >= this.outs.length) return ONE
335
336 // truncate outputs after
337 txTmp.outs.length = inIndex + 1
338
339 // "blank" outputs before
340 for (var i = 0; i < inIndex; i++) {
341 txTmp.outs[i] = BLANK_OUTPUT
342 }
343
344 // ignore sequence numbers (except at inIndex)
345 txTmp.ins.forEach(function (input, i) {
346 if (i === inIndex) return
347
348 input.sequence = 0
349 })
350 }
351
352 // SIGHASH_ANYONECANPAY: ignore inputs entirely?
353 if (hashType & Transaction.SIGHASH_ANYONECANPAY) {
354 txTmp.ins = [txTmp.ins[inIndex]]
355 txTmp.ins[0].script = ourScript
356
357 // SIGHASH_ALL: only ignore input scripts
358 } else {
359 // "blank" others input scripts
360 txTmp.ins.forEach(function (input) { input.script = EMPTY_SCRIPT })
361 txTmp.ins[inIndex].script = ourScript
362 }
363
364 // serialize and hash
365 var buffer = new Buffer(txTmp.byteLength() + 4)
366 buffer.writeInt32LE(hashType, buffer.length - 4)
367 txTmp.toBuffer(buffer, 0)
368
369 return bcrypto.hash256(buffer)
370}
371
372Transaction.prototype.getHash = function () {
373 return bcrypto.hash256(this.toBuffer())
374}
375
376Transaction.prototype.getId = function () {
377 // transaction hash's are displayed in reverse order
378 return bufferReverse(this.getHash()).toString('hex')
379}
380
381Transaction.prototype.toBuffer = function (buffer, initialOffset) {
382 if (!buffer) buffer = new Buffer(this.byteLength())
383
384 var offset = initialOffset || 0
385 function writeSlice (slice) { offset += slice.copy(buffer, offset) }
386 function writeUInt8 (i) { offset = buffer.writeUInt8(i, offset) }
387 function writeUInt32 (i) { offset = buffer.writeUInt32LE(i, offset) }
388 function writeInt32 (i) { offset = buffer.writeInt32LE(i, offset) }
389 function writeUInt64 (i) { offset = bufferutils.writeUInt64LE(buffer, i, offset) }
390 function writeVarInt (i) { offset += bufferutils.writeVarInt(buffer, i, offset) }
391
392 function writeCompressedG1 (i) {
393 writeUInt8(Transaction.ZCASH_G1_PREFIX_MASK | i.yLsb)
394 writeSlice(i.x)
395 }
396
397 function writeCompressedG2 (i) {
398 writeUInt8(Transaction.ZCASH_G2_PREFIX_MASK | i.yLsb)
399 writeSlice(i.x)
400 }
401
402 writeInt32(this.version)
403 writeVarInt(this.ins.length)
404
405 this.ins.forEach(function (txIn) {
406 writeSlice(txIn.hash)
407 writeUInt32(txIn.index)
408 writeVarInt(txIn.script.length)
409 writeSlice(txIn.script)
410 writeUInt32(txIn.sequence)
411 })
412
413 writeVarInt(this.outs.length)
414 this.outs.forEach(function (txOut) {
415 if (!txOut.valueBuffer) {
416 writeUInt64(txOut.value)
417 } else {
418 writeSlice(txOut.valueBuffer)
419 }
420
421 writeVarInt(txOut.script.length)
422 writeSlice(txOut.script)
423 })
424
425 writeUInt32(this.locktime)
426
427 if (this.version >= 2 && this.zcash) {
428 writeVarInt(this.joinsplits.length)
429 this.joinsplits.forEach(function (joinsplit) {
430 writeUInt64(joinsplit.vpubOld)
431 writeUInt64(joinsplit.vpubNew)
432 writeSlice(joinsplit.anchor)
433 joinsplit.nullifiers.forEach(function (nullifier) {
434 writeSlice(nullifier)
435 })
436 joinsplit.commitments.forEach(function (nullifier) {
437 writeSlice(nullifier)
438 })
439 writeSlice(joinsplit.ephemeralKey)
440 writeSlice(joinsplit.randomSeed)
441 joinsplit.macs.forEach(function (nullifier) {
442 writeSlice(nullifier)
443 })
444 writeCompressedG1(joinsplit.zproof.gA)
445 writeCompressedG1(joinsplit.zproof.gAPrime)
446 writeCompressedG2(joinsplit.zproof.gB)
447 writeCompressedG1(joinsplit.zproof.gBPrime)
448 writeCompressedG1(joinsplit.zproof.gC)
449 writeCompressedG1(joinsplit.zproof.gCPrime)
450 writeCompressedG1(joinsplit.zproof.gK)
451 writeCompressedG1(joinsplit.zproof.gH)
452 joinsplit.ciphertexts.forEach(function (ciphertext) {
453 writeSlice(ciphertext)
454 })
455 })
456 if (this.joinsplits.length > 0) {
457 writeSlice(this.joinsplitPubkey)
458 writeSlice(this.joinsplitSig)
459 }
460 }
461
462 // avoid slicing unless necessary
463 if (initialOffset !== undefined) return buffer.slice(initialOffset, offset)
464
465 return buffer
466}
467
468Transaction.prototype.toHex = function () {
469 return this.toBuffer().toString('hex')
470}
471
472Transaction.prototype.setInputScript = function (index, scriptSig) {
473 typeforce(types.tuple(types.Number, types.Buffer), arguments)
474
475 this.ins[index].script = scriptSig
476}
477
478module.exports = Transaction