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) { Script.fromBuffer = function(buffer) {
// assert(Buffer.isBuffer(buffer)) // FIXME: transitionary assert(Buffer.isBuffer(buffer)) // FIXME: transitionary
return new Script(Array.prototype.slice.call(buffer)) return new Script(Array.prototype.slice.call(buffer))
} }

View file

@ -3,6 +3,7 @@
var assert = require('assert') var assert = require('assert')
var Address = require('./address') var Address = require('./address')
var BigInteger = require('bigi') var BigInteger = require('bigi')
var BufferExt = require('./buffer')
var Script = require('./script') var Script = require('./script')
var convert = require('./convert') var convert = require('./convert')
var crypto = require('./crypto') var crypto = require('./crypto')
@ -122,36 +123,68 @@ Transaction.prototype.addOutput = function (address, value, network) {
* accordance with the Bitcoin protocol. * accordance with the Bitcoin protocol.
*/ */
Transaction.prototype.serialize = function () { Transaction.prototype.serialize = function () {
var buffer = [] var txInSize = this.ins.reduce(function(a, x) {
buffer = buffer.concat(convert.numToBytes(parseInt(this.version), 4)) return a + (40 + BufferExt.varIntSize(x.script.buffer.length) + x.script.buffer.length)
buffer = buffer.concat(convert.numToVarInt(this.ins.length)) }, 0)
this.ins.forEach(function(txin) { var txOutSize = this.outs.reduce(function(a, x) {
// Why do blockchain.info, blockexplorer.com, sx and just about everybody return a + (8 + BufferExt.varIntSize(x.script.buffer.length) + x.script.buffer.length)
// else use little-endian hashes? No idea... }, 0)
buffer = buffer.concat(convert.hexToBytes(txin.outpoint.hash).reverse())
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 var offset = 0
buffer = buffer.concat(convert.numToVarInt(scriptBytes.length)) function writeSlice(slice) {
buffer = buffer.concat(scriptBytes) if (Array.isArray(slice)) slice = new Buffer(slice)
buffer = buffer.concat(convert.numToBytes(txin.sequence, 4)) 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) { this.outs.forEach(function(txout) {
buffer = buffer.concat(convert.numToBytes(txout.value,8)) writeUInt64(txout.value)
writeVI(txout.script.buffer.length)
var scriptBytes = txout.script.buffer writeSlice(txout.script.buffer)
buffer = buffer.concat(convert.numToVarInt(scriptBytes.length))
buffer = buffer.concat(scriptBytes)
}) })
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() { Transaction.prototype.serializeHex = function() {
@ -219,17 +252,14 @@ Transaction.prototype.hashTransactionForSignature =
return crypto.hash256(buffer) 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 () Transaction.prototype.getHash = function ()
{ {
var buffer = this.serialize() var buffer = crypto.hash256(this.serialize())
var hash = crypto.hash256(buffer)
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 () Transaction.prototype.clone = function ()
@ -250,63 +280,79 @@ Transaction.prototype.clone = function ()
} }
Transaction.deserialize = function(buffer) { Transaction.deserialize = function(buffer) {
if (typeof buffer == "string") { if (typeof buffer == "string") buffer = new Buffer(buffer, 'hex')
buffer = convert.hexToBytes(buffer) else if (Array.isArray(buffer)) buffer = new Buffer(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)
pos += result.bytes.length var offset = 0
return result.number function readSlice(n) {
offset += n
return buffer.slice(offset - n, offset)
} }
var readBytes = function(bytes) { function readUInt32() {
pos += bytes var i = buffer.readUInt32LE(offset)
return buffer.slice(pos - bytes, pos) offset += 4
return i
} }
var readVarString = function() { function readUInt64() {
var size = readVarInt() var i = BufferExt.readUInt64LE(buffer, offset)
return readBytes(size) offset += 8
return i
} }
var obj = { function readVI() {
ins: [], var vi = BufferExt.readVarInt(buffer, offset)
outs: [] offset += vi.size
return vi.number
} }
obj.version = readAsInt(4)
var ins = readVarInt()
var i
for (i = 0; i < ins; i++) { var ins = []
var hash = readBytes(32) 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) Array.prototype.reverse.call(hash)
obj.ins.push({ var vout = readUInt32()
var scriptLen = readVI()
var script = readSlice(scriptLen)
var sequence = readUInt32()
ins.push({
outpoint: { outpoint: {
hash: convert.bytesToHex(hash), hash: hash.toString('hex'),
index: readAsInt(4) index: vout,
}, },
script: Script.fromBuffer(readVarString()), script: Script.fromBuffer(script),
sequence: readAsInt(4) sequence: sequence
})
}
var outs = readVarInt()
for (i = 0; i < outs; i++) {
obj.outs.push({
value: convert.bytesToNum(readBytes(8)),
script: Script.fromBuffer(readVarString())
}) })
} }
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) { this.processTx = function(tx) {
var txhash = convert.bytesToHex(tx.getHash()) var txhash = tx.getHash()
tx.outs.forEach(function(txOut, i){ tx.outs.forEach(function(txOut, i){
var address = txOut.address.toString() var address = txOut.address.toString()

View file

@ -72,7 +72,7 @@ describe('Transaction', function() {
it('assigns hash to deserialized object', function(){ it('assigns hash to deserialized object', function(){
var hashHex = "a9d4599e15b53f3eb531608ddb31f48c695c3d0b3538a6bda871e8b34f2f430c" var hashHex = "a9d4599e15b53f3eb531608ddb31f48c695c3d0b3538a6bda871e8b34f2f430c"
assert.equal(b2h(tx.hash), hashHex) assert.equal(tx.hash, hashHex)
}) })
it('decodes large inputs correctly', function() { it('decodes large inputs correctly', function() {

View file

@ -4,7 +4,6 @@ var T = require('../src/transaction.js')
var Transaction = T.Transaction var Transaction = T.Transaction
var TransactionOut = T.TransactionOut var TransactionOut = T.TransactionOut
var Script = require('../src/script.js') var Script = require('../src/script.js')
var convert = require('../src/convert.js')
var assert = require('assert') var assert = require('assert')
var sinon = require('sinon') var sinon = require('sinon')
var crypto = require('../').crypto var crypto = require('../').crypto
@ -338,7 +337,7 @@ describe('Wallet', function() {
function verifyOutputAdded(index) { function verifyOutputAdded(index) {
var txOut = tx.outs[index] var txOut = tx.outs[index]
var key = convert.bytesToHex(tx.getHash()) + ":" + index var key = tx.getHash() + ":" + index
var output = wallet.outputs[key] var output = wallet.outputs[key]
assert.equal(output.receive, key) assert.equal(output.receive, key)
assert.equal(output.value, txOut.value) assert.equal(output.value, txOut.value)
@ -367,7 +366,7 @@ describe('Wallet', function() {
var key = txIn.outpoint.hash + ":" + txIn.outpoint.index var key = txIn.outpoint.hash + ":" + txIn.outpoint.index
var output = wallet.outputs[key] var output = wallet.outputs[key]
assert.equal(output.spend, convert.bytesToHex(tx.getHash()) + ':' + 0) assert.equal(output.spend, tx.getHash() + ':' + 0)
}) })
}) })