Changes internal serialization to use Buffers instead

This commit is contained in:
Daniel Cousens 2014-05-02 06:36:21 +10:00
parent c8bda6dde6
commit a8cf2fdd9e
5 changed files with 123 additions and 78 deletions

View file

@ -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))
}

View file

@ -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
})
}
/**

View file

@ -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()

View file

@ -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() {

View file

@ -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)
})
})