diff --git a/src/block.js b/src/block.js index 8ec6c29..cb3ee4b 100644 --- a/src/block.js +++ b/src/block.js @@ -26,43 +26,24 @@ class Block { } static fromBuffer(buffer) { if (buffer.length < 80) throw new Error('Buffer too small (< 80 bytes)'); - let offset = 0; - const readSlice = n => { - offset += n; - return buffer.slice(offset - n, offset); - }; - const readUInt32 = () => { - const i = buffer.readUInt32LE(offset); - offset += 4; - return i; - }; - const readInt32 = () => { - const i = buffer.readInt32LE(offset); - offset += 4; - return i; - }; + const bufferReader = new bufferutils_1.BufferReader(buffer); const block = new Block(); - block.version = readInt32(); - block.prevHash = readSlice(32); - block.merkleRoot = readSlice(32); - block.timestamp = readUInt32(); - block.bits = readUInt32(); - block.nonce = readUInt32(); + block.version = bufferReader.readInt32(); + block.prevHash = bufferReader.readSlice(32); + block.merkleRoot = bufferReader.readSlice(32); + block.timestamp = bufferReader.readUInt32(); + block.bits = bufferReader.readUInt32(); + block.nonce = bufferReader.readUInt32(); if (buffer.length === 80) return block; - const readVarInt = () => { - const vi = varuint.decode(buffer, offset); - offset += varuint.decode.bytes; - return vi; - }; const readTransaction = () => { const tx = transaction_1.Transaction.fromBuffer( - buffer.slice(offset), + bufferReader.buffer.slice(bufferReader.offset), true, ); - offset += tx.byteLength(); + bufferReader.offset += tx.byteLength(); return tx; }; - const nTransactions = readVarInt(); + const nTransactions = bufferReader.readVarInt(); block.transactions = []; for (let i = 0; i < nTransactions; ++i) { const tx = readTransaction(); @@ -154,32 +135,20 @@ class Block { // TODO: buffer, offset compatibility toBuffer(headersOnly) { const buffer = Buffer.allocUnsafe(this.byteLength(headersOnly)); - let offset = 0; - const writeSlice = slice => { - slice.copy(buffer, offset); - offset += slice.length; - }; - const writeInt32 = i => { - buffer.writeInt32LE(i, offset); - offset += 4; - }; - const writeUInt32 = i => { - buffer.writeUInt32LE(i, offset); - offset += 4; - }; - writeInt32(this.version); - writeSlice(this.prevHash); - writeSlice(this.merkleRoot); - writeUInt32(this.timestamp); - writeUInt32(this.bits); - writeUInt32(this.nonce); + const bufferWriter = new bufferutils_1.BufferWriter(buffer); + bufferWriter.writeInt32(this.version); + bufferWriter.writeSlice(this.prevHash); + bufferWriter.writeSlice(this.merkleRoot); + bufferWriter.writeUInt32(this.timestamp); + bufferWriter.writeUInt32(this.bits); + bufferWriter.writeUInt32(this.nonce); if (headersOnly || !this.transactions) return buffer; - varuint.encode(this.transactions.length, buffer, offset); - offset += varuint.encode.bytes; + varuint.encode(this.transactions.length, buffer, bufferWriter.offset); + bufferWriter.offset += varuint.encode.bytes; this.transactions.forEach(tx => { const txSize = tx.byteLength(); // TODO: extract from toBuffer? - tx.toBuffer(buffer, offset); - offset += txSize; + tx.toBuffer(buffer, bufferWriter.offset); + bufferWriter.offset += txSize; }); return buffer; } diff --git a/src/bufferutils.js b/src/bufferutils.js index 54ce1c9..103ca5e 100644 --- a/src/bufferutils.js +++ b/src/bufferutils.js @@ -1,5 +1,8 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); +const types = require('./types'); +const typeforce = require('typeforce'); +const varuint = require('varuint-bitcoin'); // https://github.com/feross/buffer/blob/master/index.js#L1127 function verifuint(value, max) { if (typeof value !== 'number') @@ -38,3 +41,97 @@ function reverseBuffer(buffer) { return buffer; } exports.reverseBuffer = reverseBuffer; +/** + * Helper class for serialization of bitcoin data types into a pre-allocated buffer. + */ +class BufferWriter { + constructor(buffer, offset = 0) { + this.buffer = buffer; + this.offset = offset; + typeforce(types.tuple(types.Buffer, types.UInt32), [buffer, 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 = writeUInt64LE(this.buffer, i, this.offset); + } + writeVarInt(i) { + varuint.encode(i, this.buffer, this.offset); + this.offset += varuint.encode.bytes; + } + writeSlice(slice) { + if (this.buffer.length < this.offset + slice.length) { + throw new Error('Cannot write slice out of bounds'); + } + 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; +/** + * Helper class for reading of bitcoin data types from a buffer. + */ +class BufferReader { + constructor(buffer, offset = 0) { + this.buffer = buffer; + this.offset = offset; + typeforce(types.tuple(types.Buffer, types.UInt32), [buffer, offset]); + } + readUInt8() { + const result = this.buffer.readUInt8(this.offset); + this.offset++; + return result; + } + readInt32() { + const result = this.buffer.readInt32LE(this.offset); + this.offset += 4; + return result; + } + readUInt32() { + const result = this.buffer.readUInt32LE(this.offset); + this.offset += 4; + return result; + } + readUInt64() { + const result = readUInt64LE(this.buffer, this.offset); + this.offset += 8; + return result; + } + readVarInt() { + const vi = varuint.decode(this.buffer, this.offset); + this.offset += varuint.decode.bytes; + return vi; + } + readSlice(n) { + if (this.buffer.length < this.offset + n) { + throw new Error('Cannot read slice out of bounds'); + } + const result = this.buffer.slice(this.offset, this.offset + n); + this.offset += n; + return result; + } + readVarSlice() { + return this.readSlice(this.readVarInt()); + } + readVector() { + const count = this.readVarInt(); + const vector = []; + for (let i = 0; i < count; i++) vector.push(this.readVarSlice()); + return vector; + } +} +exports.BufferReader = BufferReader; diff --git a/src/transaction.js b/src/transaction.js index e2d6a6b..b47cfe9 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -1,6 +1,5 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -const bufferutils = require('./bufferutils'); const bufferutils_1 = require('./bufferutils'); const bcrypto = require('./crypto'); const bscript = require('./script'); @@ -47,80 +46,48 @@ class Transaction { this.outs = []; } static fromBuffer(buffer, _NO_STRICT) { - let offset = 0; - function readSlice(n) { - offset += n; - return buffer.slice(offset - n, offset); - } - function readUInt32() { - const i = buffer.readUInt32LE(offset); - offset += 4; - return i; - } - function readInt32() { - const i = buffer.readInt32LE(offset); - offset += 4; - return i; - } - function readUInt64() { - const i = bufferutils.readUInt64LE(buffer, offset); - offset += 8; - return i; - } - function readVarInt() { - const vi = varuint.decode(buffer, offset); - offset += varuint.decode.bytes; - return vi; - } - function readVarSlice() { - return readSlice(readVarInt()); - } - function readVector() { - const count = readVarInt(); - const vector = []; - for (let i = 0; i < count; i++) vector.push(readVarSlice()); - return vector; - } + const bufferReader = new bufferutils_1.BufferReader(buffer); const tx = new Transaction(); - tx.version = readInt32(); - const marker = buffer.readUInt8(offset); - const flag = buffer.readUInt8(offset + 1); + tx.version = bufferReader.readInt32(); + const marker = bufferReader.readUInt8(); + const flag = bufferReader.readUInt8(); let hasWitnesses = false; if ( marker === Transaction.ADVANCED_TRANSACTION_MARKER && flag === Transaction.ADVANCED_TRANSACTION_FLAG ) { - offset += 2; hasWitnesses = true; + } else { + bufferReader.offset -= 2; } - const vinLen = readVarInt(); + const vinLen = bufferReader.readVarInt(); for (let i = 0; i < vinLen; ++i) { tx.ins.push({ - hash: readSlice(32), - index: readUInt32(), - script: readVarSlice(), - sequence: readUInt32(), + hash: bufferReader.readSlice(32), + index: bufferReader.readUInt32(), + script: bufferReader.readVarSlice(), + sequence: bufferReader.readUInt32(), witness: EMPTY_WITNESS, }); } - const voutLen = readVarInt(); + const voutLen = bufferReader.readVarInt(); for (let i = 0; i < voutLen; ++i) { tx.outs.push({ - value: readUInt64(), - script: readVarSlice(), + value: bufferReader.readUInt64(), + script: bufferReader.readVarSlice(), }); } if (hasWitnesses) { for (let i = 0; i < vinLen; ++i) { - tx.ins[i].witness = readVector(); + tx.ins[i].witness = bufferReader.readVector(); } // was this pointless? if (!tx.hasWitnesses()) throw new Error('Transaction has superfluous witness data'); } - tx.locktime = readUInt32(); + tx.locktime = bufferReader.readUInt32(); if (_NO_STRICT) return tx; - if (offset !== buffer.length) + if (bufferReader.offset !== buffer.length) throw new Error('Transaction has unexpected data'); return tx; } @@ -296,33 +263,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 bufferutils_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 +282,9 @@ class Transaction { (hashType & 0x1f) !== Transaction.SIGHASH_NONE ) { tbuffer = Buffer.allocUnsafe(4 * this.ins.length); - toffset = 0; + bufferWriter = new bufferutils_1.BufferWriter(tbuffer, 0); this.ins.forEach(txIn => { - writeUInt32(txIn.sequence); + bufferWriter.writeUInt32(txIn.sequence); }); hashSequence = bcrypto.hash256(tbuffer); } @@ -346,10 +296,10 @@ class Transaction { return sum + 8 + varSliceSize(output.script); }, 0); tbuffer = Buffer.allocUnsafe(txOutsSize); - toffset = 0; + bufferWriter = new bufferutils_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 +308,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 bufferutils_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 bufferutils_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 +354,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 bufferutils_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/bufferutils.spec.ts b/test/bufferutils.spec.ts index 33ad8f5..b8f86ab 100644 --- a/test/bufferutils.spec.ts +++ b/test/bufferutils.spec.ts @@ -1,10 +1,16 @@ import * as assert from 'assert'; import { describe, it } from 'mocha'; import * as bufferutils from '../src/bufferutils'; +import { BufferReader, BufferWriter } from '../src/bufferutils'; import * as fixtures from './fixtures/bufferutils.json'; +const varuint = require('varuint-bitcoin'); describe('bufferutils', () => { + function concatToBuffer(values: number[][]): Buffer { + return Buffer.concat(values.map(data => Buffer.from(data))); + } + describe('readUInt64LE', () => { fixtures.valid.forEach(f => { it('decodes ' + f.hex, () => { @@ -46,4 +52,444 @@ describe('bufferutils', () => { }); }); }); + + describe('BufferWriter', () => { + 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); + assert.throws(() => { + bufferWriter.writeSlice(Buffer.from([0, 0])); + }, /^Error: Cannot write slice out of bounds$/); + }); + + 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); + }); + }); + + describe('BufferReader', () => { + function testValue( + bufferReader: BufferReader, + value: Buffer | number, + expectedValue: Buffer | number, + expectedOffset: number = Buffer.isBuffer(expectedValue) + ? expectedValue.length + : 0, + ): void { + assert.strictEqual(bufferReader.offset, expectedOffset); + if (Buffer.isBuffer(expectedValue)) { + assert.deepStrictEqual( + (value as Buffer).slice(0, expectedOffset), + expectedValue.slice(0, expectedOffset), + ); + } else { + assert.strictEqual(value as number, expectedValue); + } + } + + it('readUint8', () => { + const values = [0, 1, 0xfe, 0xff]; + const buffer = Buffer.from([0, 1, 0xfe, 0xff]); + const bufferReader = new BufferReader(buffer); + values.forEach((v: number) => { + const expectedOffset = bufferReader.offset + 1; + const val = bufferReader.readUInt8(); + testValue(bufferReader, val, v, expectedOffset); + }); + }); + + it('readInt32', () => { + const values = [ + 0, + 1, + Math.pow(2, 31) - 2, + Math.pow(2, 31) - 1, + -1, + -Math.pow(2, 31), + ]; + const buffer = 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 bufferReader = new BufferReader(buffer); + values.forEach((value: number) => { + const expectedOffset = bufferReader.offset + 4; + const val = bufferReader.readInt32(); + testValue(bufferReader, val, value, expectedOffset); + }); + }); + + it('readUInt32', () => { + const maxUInt32 = Math.pow(2, 32) - 1; + const values = [0, 1, Math.pow(2, 16), maxUInt32]; + const buffer = concatToBuffer([ + [0, 0, 0, 0], + [1, 0, 0, 0], + [0, 0, 1, 0], + [0xff, 0xff, 0xff, 0xff], + ]); + const bufferReader = new BufferReader(buffer); + values.forEach((value: number) => { + const expectedOffset = bufferReader.offset + 4; + const val = bufferReader.readUInt32(); + testValue(bufferReader, val, value, expectedOffset); + }); + }); + + it('readUInt64', () => { + const values = [ + 0, + 1, + Math.pow(2, 32), + Number.MAX_SAFE_INTEGER /* 2^53 - 1 */, + ]; + const buffer = 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 bufferReader = new BufferReader(buffer); + values.forEach((value: number) => { + const expectedOffset = bufferReader.offset + 8; + const val = bufferReader.readUInt64(); + testValue(bufferReader, val, value, expectedOffset); + }); + }); + + it('readVarInt', () => { + 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 buffer = 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 bufferReader = new BufferReader(buffer); + values.forEach((value: number) => { + const expectedOffset = + bufferReader.offset + varuint.encodingLength(value); + const val = bufferReader.readVarInt(); + testValue(bufferReader, val, value, expectedOffset); + }); + }); + + it('readSlice', () => { + const values = [[1], [1, 2, 3, 4], [254, 255]]; + const buffer = concatToBuffer(values); + const bufferReader = new BufferReader(buffer); + values.forEach((v: number[]) => { + const expectedOffset = bufferReader.offset + v.length; + const val = bufferReader.readSlice(v.length); + testValue(bufferReader, val, Buffer.from(v), expectedOffset); + }); + assert.throws(() => { + bufferReader.readSlice(2); + }, /^Error: Cannot read slice out of bounds$/); + }); + + it('readVarSlice', () => { + const values = [ + Buffer.alloc(1, 1), + Buffer.alloc(252, 2), + Buffer.alloc(253, 3), + ]; + const buffer = Buffer.concat([ + Buffer.from([0x01, 0x01]), + Buffer.from([0xfc]), + Buffer.alloc(252, 0x02), + Buffer.from([0xfd, 0xfd, 0x00]), + Buffer.alloc(253, 0x03), + ]); + const bufferReader = new BufferReader(buffer); + values.forEach((value: Buffer) => { + const expectedOffset = + bufferReader.offset + + varuint.encodingLength(value.length) + + value.length; + const val = bufferReader.readVarSlice(); + testValue(bufferReader, val, value, expectedOffset); + }); + }); + + it('readVector', () => { + const values = [ + [Buffer.alloc(1, 4), Buffer.alloc(253, 5)], + Array(253).fill(Buffer.alloc(1, 6)), + ]; + const buffer = 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 bufferReader = new BufferReader(buffer); + values.forEach((value: Buffer[]) => { + const expectedOffset = + bufferReader.offset + + varuint.encodingLength(value.length) + + value.reduce( + (sum: number, v) => + sum + varuint.encodingLength(v.length) + v.length, + 0, + ); + const val = bufferReader.readVector(); + testValue( + bufferReader, + Buffer.concat(val), + Buffer.concat(value), + expectedOffset, + ); + }); + }); + }); }); diff --git a/ts_src/block.ts b/ts_src/block.ts index 9a4d675..2760e5e 100644 --- a/ts_src/block.ts +++ b/ts_src/block.ts @@ -1,4 +1,4 @@ -import { reverseBuffer } from './bufferutils'; +import { BufferReader, BufferWriter, reverseBuffer } from './bufferutils'; import * as bcrypto from './crypto'; import { Transaction } from './transaction'; import * as types from './types'; @@ -18,47 +18,28 @@ export class Block { static fromBuffer(buffer: Buffer): Block { if (buffer.length < 80) throw new Error('Buffer too small (< 80 bytes)'); - let offset: number = 0; - const readSlice = (n: number): Buffer => { - offset += n; - return buffer.slice(offset - n, offset); - }; - - const readUInt32 = (): number => { - const i = buffer.readUInt32LE(offset); - offset += 4; - return i; - }; - - const readInt32 = (): number => { - const i = buffer.readInt32LE(offset); - offset += 4; - return i; - }; + const bufferReader = new BufferReader(buffer); const block = new Block(); - block.version = readInt32(); - block.prevHash = readSlice(32); - block.merkleRoot = readSlice(32); - block.timestamp = readUInt32(); - block.bits = readUInt32(); - block.nonce = readUInt32(); + block.version = bufferReader.readInt32(); + block.prevHash = bufferReader.readSlice(32); + block.merkleRoot = bufferReader.readSlice(32); + block.timestamp = bufferReader.readUInt32(); + block.bits = bufferReader.readUInt32(); + block.nonce = bufferReader.readUInt32(); if (buffer.length === 80) return block; - const readVarInt = (): number => { - const vi = varuint.decode(buffer, offset); - offset += varuint.decode.bytes; - return vi; - }; - const readTransaction = (): any => { - const tx = Transaction.fromBuffer(buffer.slice(offset), true); - offset += tx.byteLength(); + const tx = Transaction.fromBuffer( + bufferReader.buffer.slice(bufferReader.offset), + true, + ); + bufferReader.offset += tx.byteLength(); return tx; }; - const nTransactions = readVarInt(); + const nTransactions = bufferReader.readVarInt(); block.transactions = []; for (let i = 0; i < nTransactions; ++i) { @@ -183,37 +164,24 @@ export class Block { toBuffer(headersOnly?: boolean): Buffer { const buffer: Buffer = Buffer.allocUnsafe(this.byteLength(headersOnly)); - let offset: number = 0; - const writeSlice = (slice: Buffer): void => { - slice.copy(buffer, offset); - offset += slice.length; - }; + const bufferWriter = new BufferWriter(buffer); - const writeInt32 = (i: number): void => { - buffer.writeInt32LE(i, offset); - offset += 4; - }; - const writeUInt32 = (i: number): void => { - buffer.writeUInt32LE(i, offset); - offset += 4; - }; - - writeInt32(this.version); - writeSlice(this.prevHash!); - writeSlice(this.merkleRoot!); - writeUInt32(this.timestamp); - writeUInt32(this.bits); - writeUInt32(this.nonce); + bufferWriter.writeInt32(this.version); + bufferWriter.writeSlice(this.prevHash!); + bufferWriter.writeSlice(this.merkleRoot!); + bufferWriter.writeUInt32(this.timestamp); + bufferWriter.writeUInt32(this.bits); + bufferWriter.writeUInt32(this.nonce); if (headersOnly || !this.transactions) return buffer; - varuint.encode(this.transactions.length, buffer, offset); - offset += varuint.encode.bytes; + varuint.encode(this.transactions.length, buffer, bufferWriter.offset); + bufferWriter.offset += varuint.encode.bytes; this.transactions.forEach(tx => { const txSize = tx.byteLength(); // TODO: extract from toBuffer? - tx.toBuffer(buffer, offset); - offset += txSize; + tx.toBuffer(buffer, bufferWriter.offset); + bufferWriter.offset += txSize; }); return buffer; diff --git a/ts_src/bufferutils.ts b/ts_src/bufferutils.ts index adb6060..23e1c0d 100644 --- a/ts_src/bufferutils.ts +++ b/ts_src/bufferutils.ts @@ -1,3 +1,8 @@ +import * as types from './types'; + +const typeforce = require('typeforce'); +const varuint = require('varuint-bitcoin'); + // https://github.com/feross/buffer/blob/master/index.js#L1127 function verifuint(value: number, max: number): void { if (typeof value !== 'number') @@ -42,3 +47,109 @@ export function reverseBuffer(buffer: Buffer): Buffer { } return buffer; } + +/** + * Helper class for serialization of bitcoin data types into a pre-allocated buffer. + */ +export class BufferWriter { + constructor(public buffer: Buffer, public offset: number = 0) { + typeforce(types.tuple(types.Buffer, types.UInt32), [buffer, 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 = 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 { + if (this.buffer.length < this.offset + slice.length) { + throw new Error('Cannot write slice out of bounds'); + } + 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)); + } +} + +/** + * Helper class for reading of bitcoin data types from a buffer. + */ +export class BufferReader { + constructor(public buffer: Buffer, public offset: number = 0) { + typeforce(types.tuple(types.Buffer, types.UInt32), [buffer, offset]); + } + + readUInt8(): number { + const result = this.buffer.readUInt8(this.offset); + this.offset++; + return result; + } + + readInt32(): number { + const result = this.buffer.readInt32LE(this.offset); + this.offset += 4; + return result; + } + + readUInt32(): number { + const result = this.buffer.readUInt32LE(this.offset); + this.offset += 4; + return result; + } + + readUInt64(): number { + const result = readUInt64LE(this.buffer, this.offset); + this.offset += 8; + return result; + } + + readVarInt(): number { + const vi = varuint.decode(this.buffer, this.offset); + this.offset += varuint.decode.bytes; + return vi; + } + + readSlice(n: number): Buffer { + if (this.buffer.length < this.offset + n) { + throw new Error('Cannot read slice out of bounds'); + } + const result = this.buffer.slice(this.offset, this.offset + n); + this.offset += n; + return result; + } + + readVarSlice(): Buffer { + return this.readSlice(this.readVarInt()); + } + + readVector(): Buffer[] { + const count = this.readVarInt(); + const vector: Buffer[] = []; + for (let i = 0; i < count; i++) vector.push(this.readVarSlice()); + return vector; + } +} diff --git a/ts_src/transaction.ts b/ts_src/transaction.ts index 88e9242..561ee8a 100644 --- a/ts_src/transaction.ts +++ b/ts_src/transaction.ts @@ -1,5 +1,4 @@ -import * as bufferutils from './bufferutils'; -import { reverseBuffer } from './bufferutils'; +import { BufferReader, BufferWriter, reverseBuffer } from './bufferutils'; import * as bcrypto from './crypto'; import * as bscript from './script'; import { OPS as opcodes } from './script'; @@ -68,85 +67,46 @@ export class Transaction { static readonly ADVANCED_TRANSACTION_FLAG = 0x01; static fromBuffer(buffer: Buffer, _NO_STRICT?: boolean): Transaction { - let offset: number = 0; - - function readSlice(n: number): Buffer { - offset += n; - return buffer.slice(offset - n, offset); - } - - function readUInt32(): number { - const i = buffer.readUInt32LE(offset); - offset += 4; - return i; - } - - function readInt32(): number { - const i = buffer.readInt32LE(offset); - offset += 4; - return i; - } - - function readUInt64(): number { - const i = bufferutils.readUInt64LE(buffer, offset); - offset += 8; - return i; - } - - function readVarInt(): number { - const vi = varuint.decode(buffer, offset); - offset += varuint.decode.bytes; - return vi; - } - - function readVarSlice(): Buffer { - return readSlice(readVarInt()); - } - - function readVector(): Buffer[] { - const count = readVarInt(); - const vector: Buffer[] = []; - for (let i = 0; i < count; i++) vector.push(readVarSlice()); - return vector; - } + const bufferReader = new BufferReader(buffer); const tx = new Transaction(); - tx.version = readInt32(); + tx.version = bufferReader.readInt32(); - const marker = buffer.readUInt8(offset); - const flag = buffer.readUInt8(offset + 1); + const marker = bufferReader.readUInt8(); + const flag = bufferReader.readUInt8(); let hasWitnesses = false; if ( marker === Transaction.ADVANCED_TRANSACTION_MARKER && flag === Transaction.ADVANCED_TRANSACTION_FLAG ) { - offset += 2; hasWitnesses = true; + } else { + bufferReader.offset -= 2; } - const vinLen = readVarInt(); + const vinLen = bufferReader.readVarInt(); for (let i = 0; i < vinLen; ++i) { tx.ins.push({ - hash: readSlice(32), - index: readUInt32(), - script: readVarSlice(), - sequence: readUInt32(), + hash: bufferReader.readSlice(32), + index: bufferReader.readUInt32(), + script: bufferReader.readVarSlice(), + sequence: bufferReader.readUInt32(), witness: EMPTY_WITNESS, }); } - const voutLen = readVarInt(); + const voutLen = bufferReader.readVarInt(); for (let i = 0; i < voutLen; ++i) { tx.outs.push({ - value: readUInt64(), - script: readVarSlice(), + value: bufferReader.readUInt64(), + script: bufferReader.readVarSlice(), }); } if (hasWitnesses) { for (let i = 0; i < vinLen; ++i) { - tx.ins[i].witness = readVector(); + tx.ins[i].witness = bufferReader.readVector(); } // was this pointless? @@ -154,10 +114,10 @@ export class Transaction { throw new Error('Transaction has superfluous witness data'); } - tx.locktime = readUInt32(); + tx.locktime = bufferReader.readUInt32(); if (_NO_STRICT) return tx; - if (offset !== buffer.length) + if (bufferReader.offset !== buffer.length) throw new Error('Transaction has unexpected data'); return tx; @@ -388,29 +348,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 +356,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 +372,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 +390,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 +405,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 +469,48 @@ export class Transaction { if (!buffer) buffer = Buffer.allocUnsafe(this.byteLength(_ALLOW_WITNESS)) as Buffer; - let offset = initialOffset || 0; + const bufferWriter = new BufferWriter(buffer, 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); - } - - 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/bufferutils.d.ts b/types/bufferutils.d.ts index 1eff78d..36e31fe 100644 --- a/types/bufferutils.d.ts +++ b/types/bufferutils.d.ts @@ -1,3 +1,35 @@ export declare function readUInt64LE(buffer: Buffer, offset: number): number; export declare function writeUInt64LE(buffer: Buffer, value: number, offset: number): number; export declare function reverseBuffer(buffer: Buffer): Buffer; +/** + * 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; +} +/** + * Helper class for reading of bitcoin data types from a buffer. + */ +export declare class BufferReader { + buffer: Buffer; + offset: number; + constructor(buffer: Buffer, offset?: number); + readUInt8(): number; + readInt32(): number; + readUInt32(): number; + readUInt64(): number; + readVarInt(): number; + readSlice(n: number): Buffer; + readVarSlice(): Buffer; + readVector(): Buffer[]; +}