1 | var bcrypto = require('./crypto')
|
2 | var bscript = require('./script')
|
3 | var bufferutils = require('./bufferutils')
|
4 | var bufferReverse = require('buffer-reverse')
|
5 | var opcodes = require('./opcodes.json')
|
6 | var typeforce = require('typeforce')
|
7 | var types = require('./types')
|
8 |
|
9 | function Transaction () {
|
10 | this.version = 1
|
11 | this.locktime = 0
|
12 | this.ins = []
|
13 | this.outs = []
|
14 | this.joinsplits = []
|
15 | }
|
16 |
|
17 | Transaction.DEFAULT_SEQUENCE = 0xffffffff
|
18 | Transaction.SIGHASH_ALL = 0x01
|
19 | Transaction.SIGHASH_NONE = 0x02
|
20 | Transaction.SIGHASH_SINGLE = 0x03
|
21 | Transaction.SIGHASH_ANYONECANPAY = 0x80
|
22 |
|
23 | Transaction.ZCASH_NUM_JS_INPUTS = 2
|
24 | Transaction.ZCASH_NUM_JS_OUTPUTS = 2
|
25 | Transaction.ZCASH_NOTECIPHERTEXT_SIZE = 1 + 8 + 32 + 32 + 512 + 16
|
26 |
|
27 | Transaction.ZCASH_G1_PREFIX_MASK = 0x02
|
28 | Transaction.ZCASH_G2_PREFIX_MASK = 0x0a
|
29 |
|
30 | Transaction.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 |
|
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 |
|
174 | Transaction.fromHex = function (hex, zcash) {
|
175 | return Transaction.fromBuffer(new Buffer(hex, 'hex'), zcash)
|
176 | }
|
177 |
|
178 | Transaction.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 |
|
186 | Transaction.prototype.isCoinbase = function () {
|
187 | return this.ins.length === 1 && Transaction.isCoinbaseHash(this.ins[0].hash)
|
188 | }
|
189 |
|
190 | var EMPTY_SCRIPT = new Buffer(0)
|
191 |
|
192 | Transaction.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 |
|
205 | return (this.ins.push({
|
206 | hash: hash,
|
207 | index: index,
|
208 | script: scriptSig || EMPTY_SCRIPT,
|
209 | sequence: sequence
|
210 | }) - 1)
|
211 | }
|
212 |
|
213 | Transaction.prototype.addOutput = function (scriptPubKey, value) {
|
214 | typeforce(types.tuple(types.Buffer, types.Satoshi), arguments)
|
215 |
|
216 |
|
217 | return (this.outs.push({
|
218 | script: scriptPubKey,
|
219 | value: value
|
220 | }) - 1)
|
221 | }
|
222 |
|
223 | function scriptSize (someScript) {
|
224 | var length = someScript.length
|
225 |
|
226 | return bufferutils.varIntSize(length) + length
|
227 | }
|
228 |
|
229 | Transaction.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 |
|
257 | Transaction.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 |
|
268 | Transaction.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 |
|
292 | var ONE = new Buffer('0000000000000000000000000000000000000000000000000000000000000001', 'hex')
|
293 | var VALUE_UINT64_MAX = new Buffer('ffffffffffffffff', 'hex')
|
294 | var BLANK_OUTPUT = {
|
295 | script: EMPTY_SCRIPT,
|
296 | valueBuffer: VALUE_UINT64_MAX
|
297 | }
|
298 |
|
299 |
|
300 |
|
301 |
|
302 |
|
303 |
|
304 |
|
305 |
|
306 |
|
307 | Transaction.prototype.hashForSignature = function (inIndex, prevOutScript, hashType) {
|
308 | typeforce(types.tuple(types.UInt32, types.Buffer, types.Number), arguments)
|
309 |
|
310 |
|
311 | if (inIndex >= this.ins.length) return ONE
|
312 |
|
313 |
|
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 |
|
321 | if ((hashType & 0x1f) === Transaction.SIGHASH_NONE) {
|
322 | txTmp.outs = []
|
323 |
|
324 |
|
325 | txTmp.ins.forEach(function (input, i) {
|
326 | if (i === inIndex) return
|
327 |
|
328 | input.sequence = 0
|
329 | })
|
330 |
|
331 |
|
332 | } else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE) {
|
333 |
|
334 | if (inIndex >= this.outs.length) return ONE
|
335 |
|
336 |
|
337 | txTmp.outs.length = inIndex + 1
|
338 |
|
339 |
|
340 | for (var i = 0; i < inIndex; i++) {
|
341 | txTmp.outs[i] = BLANK_OUTPUT
|
342 | }
|
343 |
|
344 |
|
345 | txTmp.ins.forEach(function (input, i) {
|
346 | if (i === inIndex) return
|
347 |
|
348 | input.sequence = 0
|
349 | })
|
350 | }
|
351 |
|
352 |
|
353 | if (hashType & Transaction.SIGHASH_ANYONECANPAY) {
|
354 | txTmp.ins = [txTmp.ins[inIndex]]
|
355 | txTmp.ins[0].script = ourScript
|
356 |
|
357 |
|
358 | } else {
|
359 |
|
360 | txTmp.ins.forEach(function (input) { input.script = EMPTY_SCRIPT })
|
361 | txTmp.ins[inIndex].script = ourScript
|
362 | }
|
363 |
|
364 |
|
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 |
|
372 | Transaction.prototype.getHash = function () {
|
373 | return bcrypto.hash256(this.toBuffer())
|
374 | }
|
375 |
|
376 | Transaction.prototype.getId = function () {
|
377 |
|
378 | return bufferReverse(this.getHash()).toString('hex')
|
379 | }
|
380 |
|
381 | Transaction.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 |
|
463 | if (initialOffset !== undefined) return buffer.slice(initialOffset, offset)
|
464 |
|
465 | return buffer
|
466 | }
|
467 |
|
468 | Transaction.prototype.toHex = function () {
|
469 | return this.toBuffer().toString('hex')
|
470 | }
|
471 |
|
472 | Transaction.prototype.setInputScript = function (index, scriptSig) {
|
473 | typeforce(types.tuple(types.Number, types.Buffer), arguments)
|
474 |
|
475 | this.ins[index].script = scriptSig
|
476 | }
|
477 |
|
478 | module.exports = Transaction
|