const { describe, it, beforeEach } = require('mocha') const assert = require('assert') const bscript = require('../src/script') const fixtures = require('./fixtures/transaction') const Transaction = require('../src/transaction') describe('Transaction', function () { function fromRaw (raw, noWitness) { const tx = new Transaction() tx.version = raw.version tx.locktime = raw.locktime raw.ins.forEach(function (txIn, i) { const txHash = Buffer.from(txIn.hash, 'hex') let scriptSig if (txIn.data) { scriptSig = Buffer.from(txIn.data, 'hex') } else if (txIn.script) { scriptSig = bscript.fromASM(txIn.script) } tx.addInput(txHash, txIn.index, txIn.sequence, scriptSig) if (!noWitness && txIn.witness) { const witness = txIn.witness.map(function (x) { return Buffer.from(x, 'hex') }) tx.setWitness(i, witness) } }) raw.outs.forEach(function (txOut) { let script if (txOut.data) { script = Buffer.from(txOut.data, 'hex') } else if (txOut.script) { script = bscript.fromASM(txOut.script) } tx.addOutput(script, txOut.value) }) return tx } describe('fromBuffer/fromHex', function () { function importExport (f) { const id = f.id || f.hash const txHex = f.hex || f.txHex it('imports ' + f.description + ' (' + id + ')', function () { const actual = Transaction.fromHex(txHex) assert.strictEqual(actual.toHex(), txHex) }) if (f.whex) { it('imports ' + f.description + ' (' + id + ') as witness', function () { const actual = Transaction.fromHex(f.whex) assert.strictEqual(actual.toHex(), f.whex) }) } } fixtures.valid.forEach(importExport) fixtures.hashForSignature.forEach(importExport) fixtures.hashForWitnessV0.forEach(importExport) fixtures.invalid.fromBuffer.forEach(function (f) { it('throws on ' + f.exception, function () { assert.throws(function () { Transaction.fromHex(f.hex) }, new RegExp(f.exception)) }) }) it('.version should be interpreted as an int32le', function () { const txHex = 'ffffffff0000ffffffff' const tx = Transaction.fromHex(txHex) assert.equal(-1, tx.version) assert.equal(0xffffffff, tx.locktime) }) }) describe('toBuffer/toHex', function () { fixtures.valid.forEach(function (f) { it('exports ' + f.description + ' (' + f.id + ')', function () { const actual = fromRaw(f.raw, true) assert.strictEqual(actual.toHex(), f.hex) }) if (f.whex) { it('exports ' + f.description + ' (' + f.id + ') as witness', function () { const wactual = fromRaw(f.raw) assert.strictEqual(wactual.toHex(), f.whex) }) } }) it('accepts target Buffer and offset parameters', function () { const f = fixtures.valid[0] const actual = fromRaw(f.raw) const byteLength = actual.byteLength() const target = Buffer.alloc(byteLength * 2) const a = actual.toBuffer(target, 0) const b = actual.toBuffer(target, byteLength) assert.strictEqual(a.length, byteLength) assert.strictEqual(b.length, byteLength) assert.strictEqual(a.toString('hex'), f.hex) assert.strictEqual(b.toString('hex'), f.hex) assert.deepEqual(a, b) assert.deepEqual(a, target.slice(0, byteLength)) assert.deepEqual(b, target.slice(byteLength)) }) }) describe('hasWitnesses', function () { fixtures.valid.forEach(function (f) { it('detects if the transaction has witnesses: ' + (f.whex ? 'true' : 'false'), function () { assert.strictEqual(Transaction.fromHex(f.whex ? f.whex : f.hex).hasWitnesses(), !!f.whex) }) }) }) describe('weight/virtualSize', function () { it('computes virtual size', function () { fixtures.valid.forEach(function (f) { const transaction = Transaction.fromHex(f.whex ? f.whex : f.hex) assert.strictEqual(transaction.virtualSize(), f.virtualSize) }) }) it('computes weight', function () { fixtures.valid.forEach(function (f) { const transaction = Transaction.fromHex(f.whex ? f.whex : f.hex) assert.strictEqual(transaction.weight(), f.weight) }) }) }) describe('addInput', function () { let prevTxHash beforeEach(function () { prevTxHash = Buffer.from('ffffffff00ffff000000000000000000000000000000000000000000101010ff', 'hex') }) it('returns an index', function () { const tx = new Transaction() assert.strictEqual(tx.addInput(prevTxHash, 0), 0) assert.strictEqual(tx.addInput(prevTxHash, 0), 1) }) it('defaults to empty script, witness and 0xffffffff SEQUENCE number', function () { const tx = new Transaction() tx.addInput(prevTxHash, 0) assert.strictEqual(tx.ins[0].script.length, 0) assert.strictEqual(tx.ins[0].witness.length, 0) assert.strictEqual(tx.ins[0].sequence, 0xffffffff) }) fixtures.invalid.addInput.forEach(function (f) { it('throws on ' + f.exception, function () { const tx = new Transaction() const hash = Buffer.from(f.hash, 'hex') assert.throws(function () { tx.addInput(hash, f.index) }, new RegExp(f.exception)) }) }) }) describe('addOutput', function () { it('returns an index', function () { const tx = new Transaction() assert.strictEqual(tx.addOutput(Buffer.alloc(0), 0), 0) assert.strictEqual(tx.addOutput(Buffer.alloc(0), 0), 1) }) }) describe('clone', function () { fixtures.valid.forEach(function (f) { let actual let expected beforeEach(function () { expected = Transaction.fromHex(f.hex) actual = expected.clone() }) it('should have value equality', function () { assert.deepEqual(actual, expected) }) it('should not have reference equality', function () { assert.notEqual(actual, expected) }) }) }) describe('getHash/getId', function () { function verify (f) { it('should return the id for ' + f.id + '(' + f.description + ')', function () { const tx = Transaction.fromHex(f.whex || f.hex) assert.strictEqual(tx.getHash().toString('hex'), f.hash) assert.strictEqual(tx.getId(), f.id) }) } fixtures.valid.forEach(verify) }) describe('isCoinbase', function () { function verify (f) { it('should return ' + f.coinbase + ' for ' + f.id + '(' + f.description + ')', function () { const tx = Transaction.fromHex(f.hex) assert.strictEqual(tx.isCoinbase(), f.coinbase) }) } fixtures.valid.forEach(verify) }) describe('hashForSignature', function () { it('does not use Witness serialization', function () { const randScript = Buffer.from('6a', 'hex') const tx = new Transaction() tx.addInput(Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex'), 0) tx.addOutput(randScript, 5000000000) const original = tx.__toBuffer tx.__toBuffer = function (a, b, c) { if (c !== false) throw new Error('hashForSignature MUST pass false') return original.call(this, a, b, c) } assert.throws(function () { tx.__toBuffer(undefined, undefined, true) }, /hashForSignature MUST pass false/) // assert hashForSignature does not pass false assert.doesNotThrow(function () { tx.hashForSignature(0, randScript, 1) }) }) fixtures.hashForSignature.forEach(function (f) { it('should return ' + f.hash + ' for ' + (f.description ? ('case "' + f.description + '"') : f.script), function () { const tx = Transaction.fromHex(f.txHex) const script = bscript.fromASM(f.script) assert.strictEqual(tx.hashForSignature(f.inIndex, script, f.type).toString('hex'), f.hash) }) }) }) describe('hashForWitnessV0', function () { fixtures.hashForWitnessV0.forEach(function (f) { it('should return ' + f.hash + ' for ' + (f.description ? ('case "' + f.description + '"') : ''), function () { const tx = Transaction.fromHex(f.txHex) const script = bscript.fromASM(f.script) assert.strictEqual(tx.hashForWitnessV0(f.inIndex, script, f.value, f.type).toString('hex'), f.hash) }) }) }) describe('setWitness', function () { it('only accepts a a witness stack (Array of Buffers)', function () { assert.throws(function () { (new Transaction()).setWitness(0, 'foobar') }, /Expected property "1" of type \[Buffer], got String "foobar"/) }) }) })