From a8cf2fdd9e3a0f12f2de604f9c10404124b8f0fb Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Fri, 2 May 2014 06:36:21 +1000 Subject: [PATCH] Changes internal serialization to use Buffers instead --- src/script.js | 2 +- src/transaction.js | 190 +++++++++++++++++++++++++++----------------- src/wallet.js | 2 +- test/transaction.js | 2 +- test/wallet.js | 5 +- 5 files changed, 123 insertions(+), 78 deletions(-) diff --git a/src/script.js b/src/script.js index 4cd70f5..8f3cd2c 100644 --- a/src/script.js +++ b/src/script.js @@ -14,7 +14,7 @@ function Script(data) { } Script.fromBuffer = function(buffer) { -// assert(Buffer.isBuffer(buffer)) // FIXME: transitionary + assert(Buffer.isBuffer(buffer)) // FIXME: transitionary return new Script(Array.prototype.slice.call(buffer)) } diff --git a/src/transaction.js b/src/transaction.js index f3b1351..e4c1349 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -3,6 +3,7 @@ var assert = require('assert') var Address = require('./address') var BigInteger = require('bigi') +var BufferExt = require('./buffer') var Script = require('./script') var convert = require('./convert') var crypto = require('./crypto') @@ -122,36 +123,68 @@ Transaction.prototype.addOutput = function (address, value, network) { * accordance with the Bitcoin protocol. */ Transaction.prototype.serialize = function () { - var buffer = [] - buffer = buffer.concat(convert.numToBytes(parseInt(this.version), 4)) - buffer = buffer.concat(convert.numToVarInt(this.ins.length)) + var txInSize = this.ins.reduce(function(a, x) { + return a + (40 + BufferExt.varIntSize(x.script.buffer.length) + x.script.buffer.length) + }, 0) - this.ins.forEach(function(txin) { - // Why do blockchain.info, blockexplorer.com, sx and just about everybody - // else use little-endian hashes? No idea... - buffer = buffer.concat(convert.hexToBytes(txin.outpoint.hash).reverse()) + var txOutSize = this.outs.reduce(function(a, x) { + return a + (8 + BufferExt.varIntSize(x.script.buffer.length) + x.script.buffer.length) + }, 0) - buffer = buffer.concat(convert.numToBytes(parseInt(txin.outpoint.index), 4)) + var buffer = new Buffer( + 8 + + BufferExt.varIntSize(this.ins.length) + + BufferExt.varIntSize(this.outs.length) + + txInSize + + txOutSize + ) - var scriptBytes = txin.script.buffer - buffer = buffer.concat(convert.numToVarInt(scriptBytes.length)) - buffer = buffer.concat(scriptBytes) - buffer = buffer.concat(convert.numToBytes(txin.sequence, 4)) + var offset = 0 + function writeSlice(slice) { + if (Array.isArray(slice)) slice = new Buffer(slice) + slice.copy(buffer, offset) + offset += slice.length + } + function writeUInt32(i) { + buffer.writeUInt32LE(i, offset) + offset += 4 + } + function writeUInt64(i) { + BufferExt.writeUInt64LE(buffer, i, offset) + offset += 8 + } + function writeVI(i) { + var n = BufferExt.writeVarInt(buffer, i, offset) + offset += n + } + + writeUInt32(this.version) + writeVI(this.ins.length) + + this.ins.forEach(function(txin, i) { + var hash = new Buffer(txin.outpoint.hash, 'hex') + + // Hash is big-endian, we want little-endian for the hex + Array.prototype.reverse.call(hash) + + writeSlice(hash) + writeUInt32(txin.outpoint.index) + writeVI(txin.script.buffer.length) + writeSlice(txin.script.buffer) + writeUInt32(txin.sequence) }) - buffer = buffer.concat(convert.numToVarInt(this.outs.length)) - + writeVI(this.outs.length) this.outs.forEach(function(txout) { - buffer = buffer.concat(convert.numToBytes(txout.value,8)) - - var scriptBytes = txout.script.buffer - buffer = buffer.concat(convert.numToVarInt(scriptBytes.length)) - buffer = buffer.concat(scriptBytes) + writeUInt64(txout.value) + writeVI(txout.script.buffer.length) + writeSlice(txout.script.buffer) }) - buffer = buffer.concat(convert.numToBytes(parseInt(this.locktime), 4)) + writeUInt32(this.locktime) + assert.equal(offset, buffer.length, 'Invalid transaction object') - return new Buffer(buffer) + return buffer } Transaction.prototype.serializeHex = function() { @@ -219,17 +252,14 @@ Transaction.prototype.hashTransactionForSignature = return crypto.hash256(buffer) } -/** - * Calculate and return the transaction's hash. - * Reverses hash since blockchain.info, blockexplorer.com and others - * use little-endian hashes for some stupid reason - */ Transaction.prototype.getHash = function () { - var buffer = this.serialize() - var hash = crypto.hash256(buffer) + var buffer = crypto.hash256(this.serialize()) - return Array.prototype.slice.call(hash).reverse() + // Little-endian is used for Transaction hash hex + Array.prototype.reverse.call(buffer) + + return buffer.toString('hex') } Transaction.prototype.clone = function () @@ -250,63 +280,79 @@ Transaction.prototype.clone = function () } Transaction.deserialize = function(buffer) { - if (typeof buffer == "string") { - buffer = convert.hexToBytes(buffer) - } - var pos = 0 - var readAsInt = function(bytes) { - if (bytes === 0) return 0; - pos++; - return buffer[pos-1] + readAsInt(bytes-1) * 256 - } - var readVarInt = function() { - var bytes = buffer.slice(pos, pos + 9) // maximum possible number of bytes to read - var result = convert.varIntToNum(bytes) + if (typeof buffer == "string") buffer = new Buffer(buffer, 'hex') + else if (Array.isArray(buffer)) buffer = new Buffer(buffer) - pos += result.bytes.length - return result.number + var offset = 0 + function readSlice(n) { + offset += n + return buffer.slice(offset - n, offset) } - var readBytes = function(bytes) { - pos += bytes - return buffer.slice(pos - bytes, pos) + function readUInt32() { + var i = buffer.readUInt32LE(offset) + offset += 4 + return i } - var readVarString = function() { - var size = readVarInt() - return readBytes(size) + function readUInt64() { + var i = BufferExt.readUInt64LE(buffer, offset) + offset += 8 + return i } - var obj = { - ins: [], - outs: [] + function readVI() { + var vi = BufferExt.readVarInt(buffer, offset) + offset += vi.size + return vi.number } - obj.version = readAsInt(4) - var ins = readVarInt() - var i - for (i = 0; i < ins; i++) { - var hash = readBytes(32) + var ins = [] + var outs = [] + + var version = readUInt32() + var vinLen = readVI() + + for (var i = 0; i < vinLen; ++i) { + var hash = readSlice(32) + + // Hash is big-endian, we want little-endian for the hex Array.prototype.reverse.call(hash) - obj.ins.push({ + var vout = readUInt32() + var scriptLen = readVI() + var script = readSlice(scriptLen) + var sequence = readUInt32() + + ins.push({ outpoint: { - hash: convert.bytesToHex(hash), - index: readAsInt(4) + hash: hash.toString('hex'), + index: vout, }, - script: Script.fromBuffer(readVarString()), - sequence: readAsInt(4) - }) - } - var outs = readVarInt() - - for (i = 0; i < outs; i++) { - obj.outs.push({ - value: convert.bytesToNum(readBytes(8)), - script: Script.fromBuffer(readVarString()) + script: Script.fromBuffer(script), + sequence: sequence }) } - obj.locktime = readAsInt(4) + var voutLen = readVI() - return new Transaction(obj) + for (i = 0; i < voutLen; ++i) { + var value = readUInt64() + var scriptLen = readVI() + var script = readSlice(scriptLen) + + outs.push({ + value: value, + script: Script.fromBuffer(script) + }) + } + + var locktime = readUInt32() + assert.equal(offset, buffer.length, 'Invalid transaction') + + return new Transaction({ + version: version, + ins: ins, + outs: outs, + locktime: locktime + }) } /** diff --git a/src/wallet.js b/src/wallet.js index 563f931..bd30ae9 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -146,7 +146,7 @@ function Wallet(seed, options) { } this.processTx = function(tx) { - var txhash = convert.bytesToHex(tx.getHash()) + var txhash = tx.getHash() tx.outs.forEach(function(txOut, i){ var address = txOut.address.toString() diff --git a/test/transaction.js b/test/transaction.js index 59a0523..ce831b2 100644 --- a/test/transaction.js +++ b/test/transaction.js @@ -72,7 +72,7 @@ describe('Transaction', function() { it('assigns hash to deserialized object', function(){ var hashHex = "a9d4599e15b53f3eb531608ddb31f48c695c3d0b3538a6bda871e8b34f2f430c" - assert.equal(b2h(tx.hash), hashHex) + assert.equal(tx.hash, hashHex) }) it('decodes large inputs correctly', function() { diff --git a/test/wallet.js b/test/wallet.js index 31131a1..8d65f7f 100644 --- a/test/wallet.js +++ b/test/wallet.js @@ -4,7 +4,6 @@ var T = require('../src/transaction.js') var Transaction = T.Transaction var TransactionOut = T.TransactionOut var Script = require('../src/script.js') -var convert = require('../src/convert.js') var assert = require('assert') var sinon = require('sinon') var crypto = require('../').crypto @@ -338,7 +337,7 @@ describe('Wallet', function() { function verifyOutputAdded(index) { var txOut = tx.outs[index] - var key = convert.bytesToHex(tx.getHash()) + ":" + index + var key = tx.getHash() + ":" + index var output = wallet.outputs[key] assert.equal(output.receive, key) assert.equal(output.value, txOut.value) @@ -367,7 +366,7 @@ describe('Wallet', function() { var key = txIn.outpoint.hash + ":" + txIn.outpoint.index var output = wallet.outputs[key] - assert.equal(output.spend, convert.bytesToHex(tx.getHash()) + ':' + 0) + assert.equal(output.spend, tx.getHash() + ':' + 0) }) })