diff --git a/src/buffer_writer.js b/src/buffer_writer.js new file mode 100644 index 0000000..19a2332 --- /dev/null +++ b/src/buffer_writer.js @@ -0,0 +1,44 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const bufferutils = require('./bufferutils'); +const types = require('./types'); +const typeforce = require('typeforce'); +const varuint = require('varuint-bitcoin'); +/** + * Helper class for serialization of bitcoin data types into a pre-allocated buffer. + */ +class BufferWriter { + constructor(buffer, offset = 0) { + typeforce(types.tuple(types.Buffer, types.UInt32), [buffer, offset]); + this.buffer = buffer; + this.offset = offset; + } + writeUInt8(i) { + this.offset = this.buffer.writeUInt8(i, this.offset); + } + writeInt32(i) { + this.offset = this.buffer.writeInt32LE(i, this.offset); + } + writeUInt32(i) { + this.offset = this.buffer.writeUInt32LE(i, this.offset); + } + writeUInt64(i) { + this.offset = bufferutils.writeUInt64LE(this.buffer, i, this.offset); + } + writeVarInt(i) { + varuint.encode(i, this.buffer, this.offset); + this.offset += varuint.encode.bytes; + } + writeSlice(slice) { + this.offset += slice.copy(this.buffer, this.offset); + } + writeVarSlice(slice) { + this.writeVarInt(slice.length); + this.writeSlice(slice); + } + writeVector(vector) { + this.writeVarInt(vector.length); + vector.forEach(buf => this.writeVarSlice(buf)); + } +} +exports.BufferWriter = BufferWriter; diff --git a/src/transaction.js b/src/transaction.js index e2d6a6b..fc36a63 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -1,5 +1,6 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); +const buffer_writer_1 = require('./buffer_writer'); const bufferutils = require('./bufferutils'); const bufferutils_1 = require('./bufferutils'); const bcrypto = require('./crypto'); @@ -296,33 +297,16 @@ class Transaction { arguments, ); let tbuffer = Buffer.from([]); - let toffset = 0; - function writeSlice(slice) { - toffset += slice.copy(tbuffer, toffset); - } - function writeUInt32(i) { - toffset = tbuffer.writeUInt32LE(i, toffset); - } - function writeUInt64(i) { - toffset = bufferutils.writeUInt64LE(tbuffer, i, toffset); - } - function writeVarInt(i) { - varuint.encode(i, tbuffer, toffset); - toffset += varuint.encode.bytes; - } - function writeVarSlice(slice) { - writeVarInt(slice.length); - writeSlice(slice); - } + let bufferWriter; let hashOutputs = ZERO; let hashPrevouts = ZERO; let hashSequence = ZERO; if (!(hashType & Transaction.SIGHASH_ANYONECANPAY)) { tbuffer = Buffer.allocUnsafe(36 * this.ins.length); - toffset = 0; + bufferWriter = new buffer_writer_1.BufferWriter(tbuffer, 0); this.ins.forEach(txIn => { - writeSlice(txIn.hash); - writeUInt32(txIn.index); + bufferWriter.writeSlice(txIn.hash); + bufferWriter.writeUInt32(txIn.index); }); hashPrevouts = bcrypto.hash256(tbuffer); } @@ -332,9 +316,9 @@ class Transaction { (hashType & 0x1f) !== Transaction.SIGHASH_NONE ) { tbuffer = Buffer.allocUnsafe(4 * this.ins.length); - toffset = 0; + bufferWriter = new buffer_writer_1.BufferWriter(tbuffer, 0); this.ins.forEach(txIn => { - writeUInt32(txIn.sequence); + bufferWriter.writeUInt32(txIn.sequence); }); hashSequence = bcrypto.hash256(tbuffer); } @@ -346,10 +330,10 @@ class Transaction { return sum + 8 + varSliceSize(output.script); }, 0); tbuffer = Buffer.allocUnsafe(txOutsSize); - toffset = 0; + bufferWriter = new buffer_writer_1.BufferWriter(tbuffer, 0); this.outs.forEach(out => { - writeUInt64(out.value); - writeVarSlice(out.script); + bufferWriter.writeUInt64(out.value); + bufferWriter.writeVarSlice(out.script); }); hashOutputs = bcrypto.hash256(tbuffer); } else if ( @@ -358,25 +342,25 @@ class Transaction { ) { const output = this.outs[inIndex]; tbuffer = Buffer.allocUnsafe(8 + varSliceSize(output.script)); - toffset = 0; - writeUInt64(output.value); - writeVarSlice(output.script); + bufferWriter = new buffer_writer_1.BufferWriter(tbuffer, 0); + bufferWriter.writeUInt64(output.value); + bufferWriter.writeVarSlice(output.script); hashOutputs = bcrypto.hash256(tbuffer); } tbuffer = Buffer.allocUnsafe(156 + varSliceSize(prevOutScript)); - toffset = 0; + bufferWriter = new buffer_writer_1.BufferWriter(tbuffer, 0); const input = this.ins[inIndex]; - writeUInt32(this.version); - writeSlice(hashPrevouts); - writeSlice(hashSequence); - writeSlice(input.hash); - writeUInt32(input.index); - writeVarSlice(prevOutScript); - writeUInt64(value); - writeUInt32(input.sequence); - writeSlice(hashOutputs); - writeUInt32(this.locktime); - writeUInt32(hashType); + bufferWriter.writeUInt32(this.version); + bufferWriter.writeSlice(hashPrevouts); + bufferWriter.writeSlice(hashSequence); + bufferWriter.writeSlice(input.hash); + bufferWriter.writeUInt32(input.index); + bufferWriter.writeVarSlice(prevOutScript); + bufferWriter.writeUInt64(value); + bufferWriter.writeUInt32(input.sequence); + bufferWriter.writeSlice(hashOutputs); + bufferWriter.writeUInt32(this.locktime); + bufferWriter.writeUInt32(hashType); return bcrypto.hash256(tbuffer); } getHash(forWitness) { @@ -404,64 +388,41 @@ class Transaction { } __toBuffer(buffer, initialOffset, _ALLOW_WITNESS = false) { if (!buffer) buffer = Buffer.allocUnsafe(this.byteLength(_ALLOW_WITNESS)); - let offset = initialOffset || 0; - function writeSlice(slice) { - offset += slice.copy(buffer, offset); - } - function writeUInt8(i) { - offset = buffer.writeUInt8(i, offset); - } - function writeUInt32(i) { - offset = buffer.writeUInt32LE(i, offset); - } - function writeInt32(i) { - offset = buffer.writeInt32LE(i, offset); - } - function writeUInt64(i) { - offset = bufferutils.writeUInt64LE(buffer, i, offset); - } - function writeVarInt(i) { - varuint.encode(i, buffer, offset); - offset += varuint.encode.bytes; - } - function writeVarSlice(slice) { - writeVarInt(slice.length); - writeSlice(slice); - } - function writeVector(vector) { - writeVarInt(vector.length); - vector.forEach(writeVarSlice); - } - writeInt32(this.version); + const bufferWriter = new buffer_writer_1.BufferWriter( + buffer, + initialOffset || 0, + ); + bufferWriter.writeInt32(this.version); const hasWitnesses = _ALLOW_WITNESS && this.hasWitnesses(); if (hasWitnesses) { - writeUInt8(Transaction.ADVANCED_TRANSACTION_MARKER); - writeUInt8(Transaction.ADVANCED_TRANSACTION_FLAG); + bufferWriter.writeUInt8(Transaction.ADVANCED_TRANSACTION_MARKER); + bufferWriter.writeUInt8(Transaction.ADVANCED_TRANSACTION_FLAG); } - writeVarInt(this.ins.length); + bufferWriter.writeVarInt(this.ins.length); this.ins.forEach(txIn => { - writeSlice(txIn.hash); - writeUInt32(txIn.index); - writeVarSlice(txIn.script); - writeUInt32(txIn.sequence); + bufferWriter.writeSlice(txIn.hash); + bufferWriter.writeUInt32(txIn.index); + bufferWriter.writeVarSlice(txIn.script); + bufferWriter.writeUInt32(txIn.sequence); }); - writeVarInt(this.outs.length); + bufferWriter.writeVarInt(this.outs.length); this.outs.forEach(txOut => { if (isOutput(txOut)) { - writeUInt64(txOut.value); + bufferWriter.writeUInt64(txOut.value); } else { - writeSlice(txOut.valueBuffer); + bufferWriter.writeSlice(txOut.valueBuffer); } - writeVarSlice(txOut.script); + bufferWriter.writeVarSlice(txOut.script); }); if (hasWitnesses) { this.ins.forEach(input => { - writeVector(input.witness); + bufferWriter.writeVector(input.witness); }); } - writeUInt32(this.locktime); + bufferWriter.writeUInt32(this.locktime); // avoid slicing unless necessary - if (initialOffset !== undefined) return buffer.slice(initialOffset, offset); + if (initialOffset !== undefined) + return buffer.slice(initialOffset, bufferWriter.offset); return buffer; } } diff --git a/test/buffer_writer.spec.ts b/test/buffer_writer.spec.ts new file mode 100644 index 0000000..8b33e2e --- /dev/null +++ b/test/buffer_writer.spec.ts @@ -0,0 +1,231 @@ +import * as assert from 'assert'; +import { describe, it } from 'mocha'; +import { BufferWriter } from '../src/buffer_writer'; + +const varuint = require('varuint-bitcoin'); + +describe('BufferWriter', () => { + function concatToBuffer(values: number[][]): Buffer { + return Buffer.concat(values.map(data => Buffer.from(data))); + } + + function testBuffer( + bufferWriter: BufferWriter, + expectedBuffer: Buffer, + expectedOffset: number = expectedBuffer.length, + ): void { + assert.strictEqual(bufferWriter.offset, expectedOffset); + assert.deepStrictEqual( + bufferWriter.buffer.slice(0, expectedOffset), + expectedBuffer.slice(0, expectedOffset), + ); + } + + it('writeUint8', () => { + const values = [0, 1, 254, 255]; + const expectedBuffer = Buffer.from([0, 1, 0xfe, 0xff]); + const bufferWriter = new BufferWriter( + Buffer.allocUnsafe(expectedBuffer.length), + ); + values.forEach((v: number) => { + const expectedOffset = bufferWriter.offset + 1; + bufferWriter.writeUInt8(v); + testBuffer(bufferWriter, expectedBuffer, expectedOffset); + }); + testBuffer(bufferWriter, expectedBuffer); + }); + + it('writeInt32', () => { + const values = [ + 0, + 1, + Math.pow(2, 31) - 2, + Math.pow(2, 31) - 1, + -1, + -Math.pow(2, 31), + ]; + const expectedBuffer = concatToBuffer([ + [0, 0, 0, 0], + [1, 0, 0, 0], + [0xfe, 0xff, 0xff, 0x7f], + [0xff, 0xff, 0xff, 0x7f], + [0xff, 0xff, 0xff, 0xff], + [0x00, 0x00, 0x00, 0x80], + ]); + const bufferWriter = new BufferWriter( + Buffer.allocUnsafe(expectedBuffer.length), + ); + values.forEach((value: number) => { + const expectedOffset = bufferWriter.offset + 4; + bufferWriter.writeInt32(value); + testBuffer(bufferWriter, expectedBuffer, expectedOffset); + }); + testBuffer(bufferWriter, expectedBuffer); + }); + + it('writeUInt32', () => { + const maxUInt32 = Math.pow(2, 32) - 1; + const values = [0, 1, Math.pow(2, 16), maxUInt32]; + const expectedBuffer = concatToBuffer([ + [0, 0, 0, 0], + [1, 0, 0, 0], + [0, 0, 1, 0], + [0xff, 0xff, 0xff, 0xff], + ]); + const bufferWriter = new BufferWriter( + Buffer.allocUnsafe(expectedBuffer.length), + ); + values.forEach((value: number) => { + const expectedOffset = bufferWriter.offset + 4; + bufferWriter.writeUInt32(value); + testBuffer(bufferWriter, expectedBuffer, expectedOffset); + }); + testBuffer(bufferWriter, expectedBuffer); + }); + + it('writeUInt64', () => { + const values = [ + 0, + 1, + Math.pow(2, 32), + Number.MAX_SAFE_INTEGER /* 2^53 - 1 */, + ]; + const expectedBuffer = concatToBuffer([ + [0, 0, 0, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0, 0], + [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x00], + ]); + const bufferWriter = new BufferWriter( + Buffer.allocUnsafe(expectedBuffer.length), + ); + values.forEach((value: number) => { + const expectedOffset = bufferWriter.offset + 8; + bufferWriter.writeUInt64(value); + testBuffer(bufferWriter, expectedBuffer, expectedOffset); + }); + testBuffer(bufferWriter, expectedBuffer); + }); + + it('writeVarInt', () => { + const values = [ + 0, + 1, + 252, + 253, + 254, + 255, + 256, + Math.pow(2, 16) - 2, + Math.pow(2, 16) - 1, + Math.pow(2, 16), + Math.pow(2, 32) - 2, + Math.pow(2, 32) - 1, + Math.pow(2, 32), + Number.MAX_SAFE_INTEGER, + ]; + const expectedBuffer = concatToBuffer([ + [0x00], + [0x01], + [0xfc], + [0xfd, 0xfd, 0x00], + [0xfd, 0xfe, 0x00], + [0xfd, 0xff, 0x00], + [0xfd, 0x00, 0x01], + [0xfd, 0xfe, 0xff], + [0xfd, 0xff, 0xff], + [0xfe, 0x00, 0x00, 0x01, 0x00], + [0xfe, 0xfe, 0xff, 0xff, 0xff], + [0xfe, 0xff, 0xff, 0xff, 0xff], + [0xff, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00], + [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x00], + ]); + const bufferWriter = new BufferWriter( + Buffer.allocUnsafe(expectedBuffer.length), + ); + values.forEach((value: number) => { + const expectedOffset = + bufferWriter.offset + varuint.encodingLength(value); + bufferWriter.writeVarInt(value); + testBuffer(bufferWriter, expectedBuffer, expectedOffset); + }); + testBuffer(bufferWriter, expectedBuffer); + }); + + it('writeSlice', () => { + const values = [[], [1], [1, 2, 3, 4], [254, 255]]; + const expectedBuffer = concatToBuffer(values); + const bufferWriter = new BufferWriter( + Buffer.allocUnsafe(expectedBuffer.length), + ); + values.forEach((v: number[]) => { + const expectedOffset = bufferWriter.offset + v.length; + bufferWriter.writeSlice(Buffer.from(v)); + testBuffer(bufferWriter, expectedBuffer, expectedOffset); + }); + testBuffer(bufferWriter, expectedBuffer); + }); + + it('writeVarSlice', () => { + const values = [ + Buffer.alloc(1, 1), + Buffer.alloc(252, 2), + Buffer.alloc(253, 3), + ]; + const expectedBuffer = Buffer.concat([ + Buffer.from([0x01, 0x01]), + Buffer.from([0xfc]), + Buffer.alloc(252, 0x02), + Buffer.from([0xfd, 0xfd, 0x00]), + Buffer.alloc(253, 0x03), + ]); + const bufferWriter = new BufferWriter( + Buffer.allocUnsafe(expectedBuffer.length), + ); + values.forEach((value: Buffer) => { + const expectedOffset = + bufferWriter.offset + + varuint.encodingLength(value.length) + + value.length; + bufferWriter.writeVarSlice(value); + testBuffer(bufferWriter, expectedBuffer, expectedOffset); + }); + testBuffer(bufferWriter, expectedBuffer); + }); + + it('writeVector', () => { + const values = [ + [Buffer.alloc(1, 4), Buffer.alloc(253, 5)], + Array(253).fill(Buffer.alloc(1, 6)), + ]; + const expectedBuffer = Buffer.concat([ + Buffer.from([0x02]), + Buffer.from([0x01, 0x04]), + Buffer.from([0xfd, 0xfd, 0x00]), + Buffer.alloc(253, 5), + + Buffer.from([0xfd, 0xfd, 0x00]), + Buffer.concat( + Array(253) + .fill(0) + .map(() => Buffer.from([0x01, 0x06])), + ), + ]); + + const bufferWriter = new BufferWriter( + Buffer.allocUnsafe(expectedBuffer.length), + ); + values.forEach((value: Buffer[]) => { + const expectedOffset = + bufferWriter.offset + + varuint.encodingLength(value.length) + + value.reduce( + (sum: number, v) => sum + varuint.encodingLength(v.length) + v.length, + 0, + ); + bufferWriter.writeVector(value); + testBuffer(bufferWriter, expectedBuffer, expectedOffset); + }); + testBuffer(bufferWriter, expectedBuffer); + }); +}); diff --git a/ts_src/buffer_writer.ts b/ts_src/buffer_writer.ts new file mode 100644 index 0000000..541457d --- /dev/null +++ b/ts_src/buffer_writer.ts @@ -0,0 +1,54 @@ +import * as bufferutils from './bufferutils'; +import * as types from './types'; + +const typeforce = require('typeforce'); +const varuint = require('varuint-bitcoin'); + +/** + * Helper class for serialization of bitcoin data types into a pre-allocated buffer. + */ +export class BufferWriter { + buffer: Buffer; + offset: number; + + constructor(buffer: Buffer, offset: number = 0) { + typeforce(types.tuple(types.Buffer, types.UInt32), [buffer, offset]); + this.buffer = buffer; + this.offset = offset; + } + + writeUInt8(i: number): void { + this.offset = this.buffer.writeUInt8(i, this.offset); + } + + writeInt32(i: number): void { + this.offset = this.buffer.writeInt32LE(i, this.offset); + } + + writeUInt32(i: number): void { + this.offset = this.buffer.writeUInt32LE(i, this.offset); + } + + writeUInt64(i: number): void { + this.offset = bufferutils.writeUInt64LE(this.buffer, i, this.offset); + } + + writeVarInt(i: number): void { + varuint.encode(i, this.buffer, this.offset); + this.offset += varuint.encode.bytes; + } + + writeSlice(slice: Buffer): void { + this.offset += slice.copy(this.buffer, this.offset); + } + + writeVarSlice(slice: Buffer): void { + this.writeVarInt(slice.length); + this.writeSlice(slice); + } + + writeVector(vector: Buffer[]): void { + this.writeVarInt(vector.length); + vector.forEach((buf: Buffer) => this.writeVarSlice(buf)); + } +} diff --git a/ts_src/transaction.ts b/ts_src/transaction.ts index 88e9242..ac5aea4 100644 --- a/ts_src/transaction.ts +++ b/ts_src/transaction.ts @@ -1,3 +1,4 @@ +import { BufferWriter } from './buffer_writer'; import * as bufferutils from './bufferutils'; import { reverseBuffer } from './bufferutils'; import * as bcrypto from './crypto'; @@ -388,29 +389,7 @@ export class Transaction { ); let tbuffer: Buffer = Buffer.from([]); - let toffset: number = 0; - - function writeSlice(slice: Buffer): void { - toffset += slice.copy(tbuffer, toffset); - } - - function writeUInt32(i: number): void { - toffset = tbuffer.writeUInt32LE(i, toffset); - } - - function writeUInt64(i: number): void { - toffset = bufferutils.writeUInt64LE(tbuffer, i, toffset); - } - - function writeVarInt(i: number): void { - varuint.encode(i, tbuffer, toffset); - toffset += varuint.encode.bytes; - } - - function writeVarSlice(slice: Buffer): void { - writeVarInt(slice.length); - writeSlice(slice); - } + let bufferWriter: BufferWriter; let hashOutputs = ZERO; let hashPrevouts = ZERO; @@ -418,11 +397,11 @@ export class Transaction { if (!(hashType & Transaction.SIGHASH_ANYONECANPAY)) { tbuffer = Buffer.allocUnsafe(36 * this.ins.length); - toffset = 0; + bufferWriter = new BufferWriter(tbuffer, 0); this.ins.forEach(txIn => { - writeSlice(txIn.hash); - writeUInt32(txIn.index); + bufferWriter.writeSlice(txIn.hash); + bufferWriter.writeUInt32(txIn.index); }); hashPrevouts = bcrypto.hash256(tbuffer); @@ -434,10 +413,10 @@ export class Transaction { (hashType & 0x1f) !== Transaction.SIGHASH_NONE ) { tbuffer = Buffer.allocUnsafe(4 * this.ins.length); - toffset = 0; + bufferWriter = new BufferWriter(tbuffer, 0); this.ins.forEach(txIn => { - writeUInt32(txIn.sequence); + bufferWriter.writeUInt32(txIn.sequence); }); hashSequence = bcrypto.hash256(tbuffer); @@ -452,11 +431,11 @@ export class Transaction { }, 0); tbuffer = Buffer.allocUnsafe(txOutsSize); - toffset = 0; + bufferWriter = new BufferWriter(tbuffer, 0); this.outs.forEach(out => { - writeUInt64(out.value); - writeVarSlice(out.script); + bufferWriter.writeUInt64(out.value); + bufferWriter.writeVarSlice(out.script); }); hashOutputs = bcrypto.hash256(tbuffer); @@ -467,28 +446,28 @@ export class Transaction { const output = this.outs[inIndex]; tbuffer = Buffer.allocUnsafe(8 + varSliceSize(output.script)); - toffset = 0; - writeUInt64(output.value); - writeVarSlice(output.script); + bufferWriter = new BufferWriter(tbuffer, 0); + bufferWriter.writeUInt64(output.value); + bufferWriter.writeVarSlice(output.script); hashOutputs = bcrypto.hash256(tbuffer); } tbuffer = Buffer.allocUnsafe(156 + varSliceSize(prevOutScript)); - toffset = 0; + bufferWriter = new BufferWriter(tbuffer, 0); const input = this.ins[inIndex]; - writeUInt32(this.version); - writeSlice(hashPrevouts); - writeSlice(hashSequence); - writeSlice(input.hash); - writeUInt32(input.index); - writeVarSlice(prevOutScript); - writeUInt64(value); - writeUInt32(input.sequence); - writeSlice(hashOutputs); - writeUInt32(this.locktime); - writeUInt32(hashType); + bufferWriter.writeUInt32(this.version); + bufferWriter.writeSlice(hashPrevouts); + bufferWriter.writeSlice(hashSequence); + bufferWriter.writeSlice(input.hash); + bufferWriter.writeUInt32(input.index); + bufferWriter.writeVarSlice(prevOutScript); + bufferWriter.writeUInt64(value); + bufferWriter.writeUInt32(input.sequence); + bufferWriter.writeSlice(hashOutputs); + bufferWriter.writeUInt32(this.locktime); + bufferWriter.writeUInt32(hashType); return bcrypto.hash256(tbuffer); } @@ -531,82 +510,48 @@ export class Transaction { if (!buffer) buffer = Buffer.allocUnsafe(this.byteLength(_ALLOW_WITNESS)) as Buffer; - let offset = initialOffset || 0; - - function writeSlice(slice: Buffer): void { - offset += slice.copy(buffer!, offset); - } - - function writeUInt8(i: number): void { - offset = buffer!.writeUInt8(i, offset); - } - - function writeUInt32(i: number): void { - offset = buffer!.writeUInt32LE(i, offset); - } - - function writeInt32(i: number): void { - offset = buffer!.writeInt32LE(i, offset); - } - - function writeUInt64(i: number): void { - offset = bufferutils.writeUInt64LE(buffer!, i, offset); - } - - function writeVarInt(i: number): void { - varuint.encode(i, buffer, offset); - offset += varuint.encode.bytes; - } - - function writeVarSlice(slice: Buffer): void { - writeVarInt(slice.length); - writeSlice(slice); - } - - function writeVector(vector: Buffer[]): void { - writeVarInt(vector.length); - vector.forEach(writeVarSlice); - } + const bufferWriter = new BufferWriter(buffer, initialOffset || 0); - writeInt32(this.version); + bufferWriter.writeInt32(this.version); const hasWitnesses = _ALLOW_WITNESS && this.hasWitnesses(); if (hasWitnesses) { - writeUInt8(Transaction.ADVANCED_TRANSACTION_MARKER); - writeUInt8(Transaction.ADVANCED_TRANSACTION_FLAG); + bufferWriter.writeUInt8(Transaction.ADVANCED_TRANSACTION_MARKER); + bufferWriter.writeUInt8(Transaction.ADVANCED_TRANSACTION_FLAG); } - writeVarInt(this.ins.length); + bufferWriter.writeVarInt(this.ins.length); this.ins.forEach(txIn => { - writeSlice(txIn.hash); - writeUInt32(txIn.index); - writeVarSlice(txIn.script); - writeUInt32(txIn.sequence); + bufferWriter.writeSlice(txIn.hash); + bufferWriter.writeUInt32(txIn.index); + bufferWriter.writeVarSlice(txIn.script); + bufferWriter.writeUInt32(txIn.sequence); }); - writeVarInt(this.outs.length); + bufferWriter.writeVarInt(this.outs.length); this.outs.forEach(txOut => { if (isOutput(txOut)) { - writeUInt64(txOut.value); + bufferWriter.writeUInt64(txOut.value); } else { - writeSlice((txOut as any).valueBuffer); + bufferWriter.writeSlice((txOut as any).valueBuffer); } - writeVarSlice(txOut.script); + bufferWriter.writeVarSlice(txOut.script); }); if (hasWitnesses) { this.ins.forEach(input => { - writeVector(input.witness); + bufferWriter.writeVector(input.witness); }); } - writeUInt32(this.locktime); + bufferWriter.writeUInt32(this.locktime); // avoid slicing unless necessary - if (initialOffset !== undefined) return buffer.slice(initialOffset, offset); + if (initialOffset !== undefined) + return buffer.slice(initialOffset, bufferWriter.offset); return buffer; } } diff --git a/types/buffer_writer.d.ts b/types/buffer_writer.d.ts new file mode 100644 index 0000000..5ff07f9 --- /dev/null +++ b/types/buffer_writer.d.ts @@ -0,0 +1,16 @@ +/** + * Helper class for serialization of bitcoin data types into a pre-allocated buffer. + */ +export declare class BufferWriter { + buffer: Buffer; + offset: number; + constructor(buffer: Buffer, offset?: number); + writeUInt8(i: number): void; + writeInt32(i: number): void; + writeUInt32(i: number): void; + writeUInt64(i: number): void; + writeVarInt(i: number): void; + writeSlice(slice: Buffer): void; + writeVarSlice(slice: Buffer): void; + writeVector(vector: Buffer[]): void; +}