Merge pull request #170 from dcousens/bufferx
BufferExt and Transaction serialization to use Buffers
This commit is contained in:
commit
5deab5188f
13 changed files with 438 additions and 227 deletions
94
src/buffer.js
Normal file
94
src/buffer.js
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
var assert = require('assert')
|
||||||
|
|
||||||
|
function readUInt64LE(buffer, offset) {
|
||||||
|
var a = buffer.readUInt32LE(offset)
|
||||||
|
var b = buffer.readUInt32LE(offset + 4)
|
||||||
|
b *= 0x100000000
|
||||||
|
|
||||||
|
// Javascript Safe Integer limitation
|
||||||
|
// assert(Number.isSafeInteger(value), 'value must be < 2^53')
|
||||||
|
assert(b + a < 0x0020000000000000, 'value must be < 2^53')
|
||||||
|
|
||||||
|
return b + a
|
||||||
|
}
|
||||||
|
|
||||||
|
function readVarInt(buffer, offset) {
|
||||||
|
var t = buffer.readUInt8(offset)
|
||||||
|
var number, size
|
||||||
|
|
||||||
|
// 8-bit
|
||||||
|
if (t < 253) {
|
||||||
|
number = t
|
||||||
|
size = 1
|
||||||
|
|
||||||
|
// 16-bit
|
||||||
|
} else if (t < 254) {
|
||||||
|
number = buffer.readUInt16LE(offset + 1)
|
||||||
|
size = 3
|
||||||
|
|
||||||
|
// 32-bit
|
||||||
|
} else if (t < 255) {
|
||||||
|
number = buffer.readUInt32LE(offset + 1)
|
||||||
|
size = 5
|
||||||
|
|
||||||
|
// 64 bit
|
||||||
|
} else {
|
||||||
|
number = readUInt64LE(buffer, offset + 1)
|
||||||
|
size = 9
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
number: number,
|
||||||
|
size: size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeUInt64LE(buffer, value, offset) {
|
||||||
|
// Javascript Safe Integer limitation
|
||||||
|
// assert(Number.isSafeInteger(value), 'value must be < 2^53')
|
||||||
|
assert(value < 0x0020000000000000, 'value must be < 2^53')
|
||||||
|
|
||||||
|
buffer.writeInt32LE(value & -1, offset)
|
||||||
|
buffer.writeUInt32LE(Math.floor(value / 0x100000000), offset + 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
function varIntSize(i) {
|
||||||
|
return i < 253 ? 1
|
||||||
|
: i < 0x10000 ? 3
|
||||||
|
: i < 0x100000000 ? 5
|
||||||
|
: 9
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeVarInt(buffer, number, offset) {
|
||||||
|
var size = varIntSize(number)
|
||||||
|
|
||||||
|
// 8 bit
|
||||||
|
if (size === 1) {
|
||||||
|
buffer.writeUInt8(number, offset)
|
||||||
|
|
||||||
|
// 16 bit
|
||||||
|
} else if (size === 3) {
|
||||||
|
buffer.writeUInt8(253, offset)
|
||||||
|
buffer.writeUInt16LE(number, offset + 1)
|
||||||
|
|
||||||
|
// 32 bit
|
||||||
|
} else if (size === 5) {
|
||||||
|
buffer.writeUInt8(254, offset)
|
||||||
|
buffer.writeUInt32LE(number, offset + 1)
|
||||||
|
|
||||||
|
// 64 bit
|
||||||
|
} else {
|
||||||
|
buffer.writeUInt8(255, offset)
|
||||||
|
writeUInt64LE(buffer, number, offset + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
readUInt64LE: readUInt64LE,
|
||||||
|
readVarInt: readVarInt,
|
||||||
|
varIntSize: varIntSize,
|
||||||
|
writeUInt64LE: writeUInt64LE,
|
||||||
|
writeVarInt: writeVarInt
|
||||||
|
}
|
|
@ -24,15 +24,6 @@ function hexToBytes(hex) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a byte array representing a number with the given length
|
|
||||||
*/
|
|
||||||
function numToBytes(num, bytes) {
|
|
||||||
if (bytes === undefined) bytes = 8
|
|
||||||
if (bytes === 0) return []
|
|
||||||
return [num % 256].concat(numToBytes(Math.floor(num / 256), bytes - 1))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a byte array to the number that it represents
|
* Convert a byte array to the number that it represents
|
||||||
*/
|
*/
|
||||||
|
@ -41,42 +32,6 @@ function bytesToNum(bytes) {
|
||||||
return bytes[0] + 256 * bytesToNum(bytes.slice(1))
|
return bytes[0] + 256 * bytesToNum(bytes.slice(1))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Turn an integer into a "var_int".
|
|
||||||
*
|
|
||||||
* "var_int" is a variable length integer used by Bitcoin's binary format.
|
|
||||||
*
|
|
||||||
* Returns a byte array.
|
|
||||||
*/
|
|
||||||
function numToVarInt(num) {
|
|
||||||
if (num < 253) return [num]
|
|
||||||
if (num < 65536) return [253].concat(numToBytes(num, 2))
|
|
||||||
if (num < 4294967296) return [254].concat(numToBytes(num, 4))
|
|
||||||
return [255].concat(numToBytes(num, 8))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Turn an VarInt into an integer
|
|
||||||
*
|
|
||||||
* "var_int" is a variable length integer used by Bitcoin's binary format.
|
|
||||||
*
|
|
||||||
* Returns { bytes: bytesUsed, number: theNumber }
|
|
||||||
*/
|
|
||||||
function varIntToNum(bytes) {
|
|
||||||
var prefix = bytes[0]
|
|
||||||
|
|
||||||
var viBytes =
|
|
||||||
prefix < 253 ? bytes.slice(0, 1)
|
|
||||||
: prefix === 253 ? bytes.slice(1, 3)
|
|
||||||
: prefix === 254 ? bytes.slice(1, 5)
|
|
||||||
: bytes.slice(1, 9)
|
|
||||||
|
|
||||||
return {
|
|
||||||
bytes: prefix < 253 ? viBytes : bytes.slice(0, viBytes.length + 1),
|
|
||||||
number: bytesToNum(viBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function bytesToWords(bytes) {
|
function bytesToWords(bytes) {
|
||||||
assert(Array.isArray(bytes) || Buffer.isBuffer(bytes), 'Input must be a byte array')
|
assert(Array.isArray(bytes) || Buffer.isBuffer(bytes), 'Input must be a byte array')
|
||||||
var words = []
|
var words = []
|
||||||
|
@ -110,10 +65,7 @@ module.exports = {
|
||||||
lpad: lpad,
|
lpad: lpad,
|
||||||
bytesToHex: bytesToHex,
|
bytesToHex: bytesToHex,
|
||||||
hexToBytes: hexToBytes,
|
hexToBytes: hexToBytes,
|
||||||
numToBytes: numToBytes,
|
|
||||||
bytesToNum: bytesToNum,
|
bytesToNum: bytesToNum,
|
||||||
numToVarInt: numToVarInt,
|
|
||||||
varIntToNum: varIntToNum,
|
|
||||||
bytesToWords: bytesToWords,
|
bytesToWords: bytesToWords,
|
||||||
wordsToBytes: wordsToBytes,
|
wordsToBytes: wordsToBytes,
|
||||||
bytesToWordArray: bytesToWordArray,
|
bytesToWordArray: bytesToWordArray,
|
||||||
|
|
|
@ -179,10 +179,11 @@ HDWallet.prototype.toBase58 = function(priv) {
|
||||||
}
|
}
|
||||||
|
|
||||||
HDWallet.prototype.derive = function(i) {
|
HDWallet.prototype.derive = function(i) {
|
||||||
var iBytes = convert.numToBytes(i, 4).reverse()
|
var iBuffer = new Buffer(4)
|
||||||
, cPar = this.chaincode
|
iBuffer.writeUInt32BE(i, 0)
|
||||||
, usePriv = i >= HDWallet.HIGHEST_BIT
|
|
||||||
, SHA512 = CJS.algo.SHA512
|
var cPar = this.chaincode
|
||||||
|
var usePriv = i >= HDWallet.HIGHEST_BIT
|
||||||
|
|
||||||
var I
|
var I
|
||||||
if (usePriv) {
|
if (usePriv) {
|
||||||
|
@ -191,21 +192,20 @@ HDWallet.prototype.derive = function(i) {
|
||||||
// If 1, private derivation is used:
|
// If 1, private derivation is used:
|
||||||
// let I = HMAC-SHA512(Key = cpar, Data = 0x00 || kpar || i) [Note:]
|
// let I = HMAC-SHA512(Key = cpar, Data = 0x00 || kpar || i) [Note:]
|
||||||
var kPar = this.priv.toBuffer().slice(0, 32)
|
var kPar = this.priv.toBuffer().slice(0, 32)
|
||||||
kPar = Array.prototype.slice.call(kPar)
|
iBuffer = Buffer.concat([new Buffer([0]), kPar, iBuffer], 37)
|
||||||
|
|
||||||
// FIXME: Dislikes buffers
|
// FIXME: Dislikes buffers
|
||||||
I = HmacFromBytesToBytes(SHA512, [0].concat(kPar, iBytes), cPar)
|
I = HmacFromBytesToBytes(CJS.algo.SHA512, Array.prototype.slice.call(iBuffer), cPar)
|
||||||
} else {
|
} else {
|
||||||
// If 0, public derivation is used:
|
// If 0, public derivation is used:
|
||||||
// let I = HMAC-SHA512(Key = cpar, Data = χ(kpar*G) || i)
|
// let I = HMAC-SHA512(Key = cpar, Data = χ(kpar*G) || i)
|
||||||
var KPar = this.pub.toBuffer()
|
var KPar = this.pub.toBuffer()
|
||||||
KPar = Array.prototype.slice.call(KPar)
|
iBuffer = Buffer.concat([KPar, iBuffer])
|
||||||
|
|
||||||
// FIXME: Dislikes buffers
|
// FIXME: Dislikes buffers
|
||||||
I = HmacFromBytesToBytes(SHA512, KPar.concat(iBytes), cPar)
|
I = HmacFromBytesToBytes(CJS.algo.SHA512, Array.prototype.slice.call(iBuffer), cPar)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// FIXME: Boo, CSJ.algo.SHA512 uses byte arrays
|
// FIXME: Boo, CSJ.algo.SHA512 uses byte arrays
|
||||||
I = new Buffer(I)
|
I = new Buffer(I)
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ module.exports = {
|
||||||
Address: require('./address'),
|
Address: require('./address'),
|
||||||
base58: require('./base58'),
|
base58: require('./base58'),
|
||||||
base58check: require('./base58check'),
|
base58check: require('./base58check'),
|
||||||
|
BufferExt: require('./buffer'),
|
||||||
convert: require('./convert'),
|
convert: require('./convert'),
|
||||||
crypto: require('./crypto'),
|
crypto: require('./crypto'),
|
||||||
ec: ec,
|
ec: ec,
|
||||||
|
|
|
@ -1,24 +1,22 @@
|
||||||
/// Implements Bitcoin's feature for signing arbitrary messages.
|
/// Implements Bitcoin's feature for signing arbitrary messages.
|
||||||
|
|
||||||
var Address = require('./address')
|
var Address = require('./address')
|
||||||
var convert = require('./convert')
|
var BufferExt = require('./buffer')
|
||||||
var crypto = require('./crypto')
|
var crypto = require('./crypto')
|
||||||
var ecdsa = require('./ecdsa')
|
var ecdsa = require('./ecdsa')
|
||||||
var ECPubKey = require('./eckey').ECPubKey
|
var ECPubKey = require('./eckey').ECPubKey
|
||||||
|
|
||||||
// FIXME: magicHash is incompatible with other magic messages
|
// FIXME: incompatible with other networks (Litecoin etc)
|
||||||
var magicBytes = new Buffer('Bitcoin Signed Message:\n')
|
var MAGIC_PREFIX = new Buffer('\x18Bitcoin Signed Message:\n')
|
||||||
|
|
||||||
function magicHash(message) {
|
function magicHash(message) {
|
||||||
var messageBytes = new Buffer(message)
|
var messageBuffer = new Buffer(message)
|
||||||
|
var lengthBuffer = new Buffer(BufferExt.varIntSize(messageBuffer.length))
|
||||||
|
BufferExt.writeVarInt(lengthBuffer, messageBuffer.length, 0)
|
||||||
|
|
||||||
var buffer = Buffer.concat([
|
var buffer = Buffer.concat([
|
||||||
new Buffer(convert.numToVarInt(magicBytes.length)),
|
MAGIC_PREFIX, lengthBuffer, messageBuffer
|
||||||
magicBytes,
|
|
||||||
new Buffer(convert.numToVarInt(messageBytes.length)),
|
|
||||||
messageBytes
|
|
||||||
])
|
])
|
||||||
|
|
||||||
return crypto.hash256(buffer)
|
return crypto.hash256(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,8 +13,14 @@ function Script(data) {
|
||||||
this.parse()
|
this.parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
Script.fromHex = function(data) {
|
Script.fromBuffer = function(buffer) {
|
||||||
return new Script(convert.hexToBytes(data))
|
assert(Buffer.isBuffer(buffer)) // FIXME: transitionary
|
||||||
|
|
||||||
|
return new Script(Array.prototype.slice.call(buffer))
|
||||||
|
}
|
||||||
|
|
||||||
|
Script.fromHex = function(hex) {
|
||||||
|
return Script.fromBuffer(new Buffer(hex, 'hex'))
|
||||||
}
|
}
|
||||||
|
|
||||||
Script.fromPubKey = function(str) {
|
Script.fromPubKey = function(str) {
|
||||||
|
|
|
@ -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')
|
||||||
|
@ -16,7 +17,7 @@ var Transaction = function (doc) {
|
||||||
this.locktime = 0
|
this.locktime = 0
|
||||||
this.ins = []
|
this.ins = []
|
||||||
this.outs = []
|
this.outs = []
|
||||||
this.defaultSequence = [255, 255, 255, 255] // 0xFFFFFFFF
|
this.defaultSequence = 0xffffffff
|
||||||
|
|
||||||
if (doc) {
|
if (doc) {
|
||||||
if (typeof doc == "string" || Array.isArray(doc)) {
|
if (typeof doc == "string" || Array.isArray(doc)) {
|
||||||
|
@ -118,45 +119,76 @@ Transaction.prototype.addOutput = function (address, value, network) {
|
||||||
/**
|
/**
|
||||||
* Serialize this transaction.
|
* Serialize this transaction.
|
||||||
*
|
*
|
||||||
* Returns the transaction as a byte array in the standard Bitcoin binary
|
* Returns the transaction as a binary buffer in
|
||||||
* format. This method is byte-perfect, i.e. the resulting byte array can
|
* accordance with the Bitcoin protocol.
|
||||||
* be hashed to get the transaction's standard Bitcoin hash.
|
|
||||||
*/
|
*/
|
||||||
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) // FIXME: Performance: transitionary only
|
||||||
buffer = buffer.concat(txin.sequence)
|
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 writeVarInt(i) {
|
||||||
|
var n = BufferExt.writeVarInt(buffer, i, offset)
|
||||||
|
offset += n
|
||||||
|
}
|
||||||
|
|
||||||
|
writeUInt32(this.version)
|
||||||
|
writeVarInt(this.ins.length)
|
||||||
|
|
||||||
|
this.ins.forEach(function(txin, i) {
|
||||||
|
var hash = new Buffer(txin.outpoint.hash, 'hex') // FIXME: Performance: convert on tx.addInput instead
|
||||||
|
|
||||||
|
// TxHash hex is big-endian, we need little-endian
|
||||||
|
Array.prototype.reverse.call(hash)
|
||||||
|
|
||||||
|
writeSlice(hash)
|
||||||
|
writeUInt32(txin.outpoint.index)
|
||||||
|
writeVarInt(txin.script.buffer.length)
|
||||||
|
writeSlice(txin.script.buffer)
|
||||||
|
writeUInt32(txin.sequence)
|
||||||
})
|
})
|
||||||
|
|
||||||
buffer = buffer.concat(convert.numToVarInt(this.outs.length))
|
writeVarInt(this.outs.length)
|
||||||
|
|
||||||
this.outs.forEach(function(txout) {
|
this.outs.forEach(function(txout) {
|
||||||
buffer = buffer.concat(convert.numToBytes(txout.value,8))
|
writeUInt64(txout.value)
|
||||||
|
writeVarInt(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 buffer
|
return buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
Transaction.prototype.serializeHex = function() {
|
Transaction.prototype.serializeHex = function() {
|
||||||
return convert.bytesToHex(this.serialize())
|
return this.serialize().toString('hex')
|
||||||
}
|
}
|
||||||
|
|
||||||
//var OP_CODESEPARATOR = 171
|
//var OP_CODESEPARATOR = 171
|
||||||
|
@ -213,23 +245,20 @@ Transaction.prototype.hashTransactionForSignature =
|
||||||
txTmp.ins = [txTmp.ins[inIndex]]
|
txTmp.ins = [txTmp.ins[inIndex]]
|
||||||
}
|
}
|
||||||
|
|
||||||
var buffer = txTmp.serialize()
|
var htB = new Buffer(4)
|
||||||
buffer = buffer.concat(convert.numToBytes(parseInt(hashType), 4))
|
htB.writeUInt32LE(hashType, 0)
|
||||||
|
|
||||||
|
var buffer = Buffer.concat([txTmp.serialize(), htB])
|
||||||
return crypto.hash256(buffer)
|
return crypto.hash256(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
Transaction.prototype.getHash = function () {
|
||||||
* Calculate and return the transaction's hash.
|
var buffer = crypto.hash256(this.serialize())
|
||||||
* 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)
|
|
||||||
|
|
||||||
return Array.prototype.slice.call(hash).reverse()
|
// Big-endian is used for TxHash
|
||||||
|
Array.prototype.reverse.call(buffer)
|
||||||
|
|
||||||
|
return buffer.toString('hex')
|
||||||
}
|
}
|
||||||
|
|
||||||
Transaction.prototype.clone = function ()
|
Transaction.prototype.clone = function ()
|
||||||
|
@ -250,60 +279,82 @@ 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
|
// Copy because we mutate (reverse TxOutHashs)
|
||||||
return result.number
|
buffer = new Buffer(buffer)
|
||||||
}
|
|
||||||
var readBytes = function(bytes) {
|
|
||||||
pos += bytes
|
|
||||||
return buffer.slice(pos - bytes, pos)
|
|
||||||
}
|
|
||||||
var readVarString = function() {
|
|
||||||
var size = readVarInt()
|
|
||||||
return readBytes(size)
|
|
||||||
}
|
|
||||||
var obj = {
|
|
||||||
ins: [],
|
|
||||||
outs: []
|
|
||||||
}
|
|
||||||
obj.version = readAsInt(4)
|
|
||||||
var ins = readVarInt()
|
|
||||||
var i
|
|
||||||
|
|
||||||
for (i = 0; i < ins; i++) {
|
var offset = 0
|
||||||
obj.ins.push({
|
function readSlice(n) {
|
||||||
|
offset += n
|
||||||
|
return buffer.slice(offset - n, offset)
|
||||||
|
}
|
||||||
|
function readUInt32() {
|
||||||
|
var i = buffer.readUInt32LE(offset)
|
||||||
|
offset += 4
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
function readUInt64() {
|
||||||
|
var i = BufferExt.readUInt64LE(buffer, offset)
|
||||||
|
offset += 8
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
function readVarInt() {
|
||||||
|
var vi = BufferExt.readVarInt(buffer, offset)
|
||||||
|
offset += vi.size
|
||||||
|
return vi.number
|
||||||
|
}
|
||||||
|
|
||||||
|
var ins = []
|
||||||
|
var outs = []
|
||||||
|
|
||||||
|
var version = readUInt32()
|
||||||
|
var vinLen = readVarInt()
|
||||||
|
|
||||||
|
for (var i = 0; i < vinLen; ++i) {
|
||||||
|
var hash = readSlice(32)
|
||||||
|
|
||||||
|
// TxHash is little-endian, we want big-endian hex
|
||||||
|
Array.prototype.reverse.call(hash)
|
||||||
|
|
||||||
|
var vout = readUInt32()
|
||||||
|
var scriptLen = readVarInt()
|
||||||
|
var script = readSlice(scriptLen)
|
||||||
|
var sequence = readUInt32()
|
||||||
|
|
||||||
|
ins.push({
|
||||||
outpoint: {
|
outpoint: {
|
||||||
hash: convert.bytesToHex(readBytes(32).reverse()),
|
hash: hash.toString('hex'),
|
||||||
index: readAsInt(4)
|
index: vout,
|
||||||
},
|
},
|
||||||
script: new Script(readVarString()),
|
script: Script.fromBuffer(script),
|
||||||
sequence: readBytes(4)
|
sequence: sequence
|
||||||
})
|
|
||||||
}
|
|
||||||
var outs = readVarInt()
|
|
||||||
|
|
||||||
for (i = 0; i < outs; i++) {
|
|
||||||
obj.outs.push({
|
|
||||||
value: convert.bytesToNum(readBytes(8)),
|
|
||||||
script: new Script(readVarString())
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
obj.locktime = readAsInt(4)
|
var voutLen = readVarInt()
|
||||||
|
|
||||||
return new Transaction(obj)
|
for (i = 0; i < voutLen; ++i) {
|
||||||
|
var value = readUInt64()
|
||||||
|
var scriptLen = readVarInt()
|
||||||
|
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
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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()
|
||||||
|
|
86
test/buffer.js
Normal file
86
test/buffer.js
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
var assert = require('assert')
|
||||||
|
var BufferExt = require('../').BufferExt
|
||||||
|
|
||||||
|
var fixtures = require('./fixtures/buffer.js')
|
||||||
|
|
||||||
|
describe('Buffer Extensions', function() {
|
||||||
|
describe('readUInt64LE', function() {
|
||||||
|
it('matches test vectors', function() {
|
||||||
|
fixtures.valid.forEach(function(f) {
|
||||||
|
var buffer = new Buffer(f.hex64, 'hex')
|
||||||
|
var number = BufferExt.readUInt64LE(buffer, 0)
|
||||||
|
|
||||||
|
assert.equal(number, f.dec)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('readVarInt', function() {
|
||||||
|
it('matches test vectors', function() {
|
||||||
|
fixtures.valid.forEach(function(f) {
|
||||||
|
var buffer = new Buffer(f.hexVI, 'hex')
|
||||||
|
var d = BufferExt.readVarInt(buffer, 0)
|
||||||
|
|
||||||
|
assert.equal(d.number, f.dec)
|
||||||
|
assert.equal(d.size, buffer.length)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('varIntSize', function() {
|
||||||
|
it('matches test vectors', function() {
|
||||||
|
fixtures.valid.forEach(function(f) {
|
||||||
|
var number = parseInt(f.dec)
|
||||||
|
var size = BufferExt.varIntSize(number)
|
||||||
|
|
||||||
|
assert.equal(size, f.hexVI.length / 2)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('writeUInt64LE', function() {
|
||||||
|
it('matches test vectors', function() {
|
||||||
|
fixtures.valid.forEach(function(f) {
|
||||||
|
var buffer = new Buffer(8)
|
||||||
|
buffer.fill(0)
|
||||||
|
|
||||||
|
BufferExt.writeUInt64LE(buffer, f.dec, 0)
|
||||||
|
assert.equal(buffer.toString('hex'), f.hex64)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
fixtures.invalid.forEach(function(f) {
|
||||||
|
it('throws on ' + f.description, function() {
|
||||||
|
assert.throws(function() {
|
||||||
|
var buffer = new Buffer(8)
|
||||||
|
buffer.fill(0)
|
||||||
|
|
||||||
|
BufferExt.writeUInt64LE(buffer, f.dec, 0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('writeVarInt', function() {
|
||||||
|
it('matches test vectors', function() {
|
||||||
|
fixtures.valid.forEach(function(f) {
|
||||||
|
var buffer = new Buffer(9)
|
||||||
|
buffer.fill(0)
|
||||||
|
|
||||||
|
var n = BufferExt.writeVarInt(buffer, f.dec, 0)
|
||||||
|
assert.equal(buffer.slice(0, n).toString('hex'), f.hexVI)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
fixtures.invalid.forEach(function(f) {
|
||||||
|
it('throws on ' + f.description, function() {
|
||||||
|
assert.throws(function() {
|
||||||
|
var buffer = new Buffer(9)
|
||||||
|
buffer.fill(0)
|
||||||
|
|
||||||
|
BufferExt.writeVarInt(buffer, f.dec, 0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -52,67 +52,6 @@ describe('convert', function() {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('numToVarInt', function() {
|
|
||||||
describe('works', function() {
|
|
||||||
var data = [
|
|
||||||
0, 128, 252, // 8-bit
|
|
||||||
256, 512, 1024, // 16-bit
|
|
||||||
65541, // 32-bit
|
|
||||||
4294967299, // 64-bit
|
|
||||||
]
|
|
||||||
var expected = [
|
|
||||||
[0], [128], [252], // 8-bit
|
|
||||||
[253, 0, 1], [253, 0, 2], [253, 0, 4], // 16-bit
|
|
||||||
[254, 5, 0, 1, 0], // 32-bit
|
|
||||||
[255, 3, 0, 0, 0, 1, 0, 0, 0] // 64-bit
|
|
||||||
]
|
|
||||||
|
|
||||||
for (var i = 0; i < data.length; ++i) {
|
|
||||||
var actual = convert.numToVarInt(data[i])
|
|
||||||
assert.deepEqual(actual, expected[i])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('varIntToNum', function() {
|
|
||||||
it('works on valid input', function() {
|
|
||||||
var data = [
|
|
||||||
[0], [128], [252], // 8-bit
|
|
||||||
[253, 0, 1], [253, 0, 2], [253, 0, 4], // 16-bit
|
|
||||||
[254, 5, 0, 1, 0], // 32-bit
|
|
||||||
[255, 3, 0, 0, 0, 1, 0, 0, 0] // 64-bit
|
|
||||||
]
|
|
||||||
var expected = [
|
|
||||||
0, 128, 252, // 8-bit
|
|
||||||
256, 512, 1024, // 16-bit
|
|
||||||
65541, // 32-bit
|
|
||||||
4294967299, // 64-bit
|
|
||||||
]
|
|
||||||
|
|
||||||
for (var i = 0; i < data.length; ++i) {
|
|
||||||
var actual = convert.varIntToNum(data[i])
|
|
||||||
assert.equal(actual.number, expected[i])
|
|
||||||
assert.deepEqual(actual.bytes, data[i])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
it('uses only what is necessary', function() {
|
|
||||||
var data = [
|
|
||||||
[0, 99],
|
|
||||||
[253, 0, 1, 99],
|
|
||||||
[254, 5, 0, 1, 0, 99],
|
|
||||||
[255, 3, 0, 0, 0, 1, 0, 0, 0, 99]
|
|
||||||
]
|
|
||||||
var expected = [0, 256, 65541, 4294967299]
|
|
||||||
|
|
||||||
for (var i = 0; i < data.length; ++i) {
|
|
||||||
var actual = convert.varIntToNum(data[i])
|
|
||||||
assert.equal(actual.number, expected[i])
|
|
||||||
assert.deepEqual(actual.bytes, data[i].slice(0, -1))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('reverseEndian', function() {
|
describe('reverseEndian', function() {
|
||||||
it('works', function() {
|
it('works', function() {
|
||||||
var bigEndian = "6a4062273ac4f9ea4ffca52d9fd102b08f6c32faa0a4d1318e3a7b2e437bb9c7"
|
var bigEndian = "6a4062273ac4f9ea4ffca52d9fd102b08f6c32faa0a4d1318e3a7b2e437bb9c7"
|
||||||
|
|
74
test/fixtures/buffer.js
vendored
Normal file
74
test/fixtures/buffer.js
vendored
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
module.exports = {
|
||||||
|
"valid": [
|
||||||
|
{
|
||||||
|
"dec": 0,
|
||||||
|
"hex64": "0000000000000000",
|
||||||
|
"hexVI": "00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dec": 1,
|
||||||
|
"hex64": "0100000000000000",
|
||||||
|
"hexVI": "01"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dec": 252,
|
||||||
|
"hex64": "fc00000000000000",
|
||||||
|
"hexVI": "fc"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dec": 253,
|
||||||
|
"hex64": "fd00000000000000",
|
||||||
|
"hexVI": "fdfd00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dec": 254,
|
||||||
|
"hex64": "fe00000000000000",
|
||||||
|
"hexVI": "fdfe00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dec": 65535,
|
||||||
|
"hex64": "ffff000000000000",
|
||||||
|
"hexVI": "fdffff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dec": 65536,
|
||||||
|
"hex64": "0000010000000000",
|
||||||
|
"hexVI": "fe00000100"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dec": 65537,
|
||||||
|
"hex64": "0100010000000000",
|
||||||
|
"hexVI": "fe01000100"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dec": 4294967295,
|
||||||
|
"hex64": "ffffffff00000000",
|
||||||
|
"hexVI": "feffffffff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dec": 4294967296,
|
||||||
|
"hex64": "0000000001000000",
|
||||||
|
"hexVI": "ff0000000001000000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dec": 4294967297,
|
||||||
|
"hex64": "0100000001000000",
|
||||||
|
"hexVI": "ff0100000001000000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dec": 9007199254740991,
|
||||||
|
"hex64": "ffffffffffff1f00",
|
||||||
|
"hexVI": "ffffffffffffff1f00"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"invalid": [
|
||||||
|
{
|
||||||
|
"description": "n === 2^53",
|
||||||
|
"value": 9007199254740992
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "n > 2^53",
|
||||||
|
"value": 18374686479671624000
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -38,6 +38,13 @@ describe('Transaction', function() {
|
||||||
assert.equal(b2h(actual), expected)
|
assert.equal(b2h(actual), expected)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('does not mutate the input buffer', function() {
|
||||||
|
var buffer = new Buffer(serializedTx, 'hex')
|
||||||
|
Transaction.deserialize(buffer)
|
||||||
|
|
||||||
|
assert.equal(buffer.toString('hex'), serializedTx)
|
||||||
|
})
|
||||||
|
|
||||||
it('decodes version correctly', function(){
|
it('decodes version correctly', function(){
|
||||||
assert.equal(tx.version, 1)
|
assert.equal(tx.version, 1)
|
||||||
})
|
})
|
||||||
|
@ -50,7 +57,7 @@ describe('Transaction', function() {
|
||||||
assert.equal(tx.ins.length, 1)
|
assert.equal(tx.ins.length, 1)
|
||||||
|
|
||||||
var input = tx.ins[0]
|
var input = tx.ins[0]
|
||||||
assert.deepEqual(input.sequence, [255, 255, 255, 255])
|
assert.equal(input.sequence, 4294967295)
|
||||||
|
|
||||||
assert.equal(input.outpoint.index, 0)
|
assert.equal(input.outpoint.index, 0)
|
||||||
assert.equal(input.outpoint.hash, "69d02fc05c4e0ddc87e796eee42693c244a3112fffe1f762c3fb61ffcb304634")
|
assert.equal(input.outpoint.hash, "69d02fc05c4e0ddc87e796eee42693c244a3112fffe1f762c3fb61ffcb304634")
|
||||||
|
@ -72,7 +79,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() {
|
||||||
|
@ -81,14 +88,18 @@ describe('Transaction', function() {
|
||||||
tx.addInput("0cb859105100ebc3344f749c835c7af7d7103ec0d8cbc3d8ccbd5d28c3c36b57", 0)
|
tx.addInput("0cb859105100ebc3344f749c835c7af7d7103ec0d8cbc3d8ccbd5d28c3c36b57", 0)
|
||||||
tx.addOutput("15mMHKL96tWAUtqF3tbVf99Z8arcmnJrr3", 100)
|
tx.addOutput("15mMHKL96tWAUtqF3tbVf99Z8arcmnJrr3", 100)
|
||||||
|
|
||||||
// but we're going to replace the tx.ins.length VarInt with a 32-bit equivalent
|
var buffer = tx.serialize()
|
||||||
// however the same resultant number of inputs (1)
|
|
||||||
var bytes = tx.serialize()
|
|
||||||
var mutated = bytes.slice(0, 4).concat([254, 1, 0, 0, 0], bytes.slice(5))
|
|
||||||
|
|
||||||
// the deserialized-serialized transaction should return to its original state (== tx)
|
// we're going to replace the 8bit VarInt for tx.ins.length with a stretched 32bit equivalent
|
||||||
var bytes2 = Transaction.deserialize(mutated).serialize()
|
var mutated = Buffer.concat([
|
||||||
assert.deepEqual(bytes, bytes2)
|
buffer.slice(0, 4),
|
||||||
|
new Buffer([254, 1, 0, 0, 0]),
|
||||||
|
buffer.slice(5)
|
||||||
|
])
|
||||||
|
|
||||||
|
// the deserialized-serialized transaction should return to its non-mutated state (== tx)
|
||||||
|
var buffer2 = Transaction.deserialize(mutated).serialize()
|
||||||
|
assert.deepEqual(buffer, buffer2)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -128,7 +139,7 @@ describe('Transaction', function() {
|
||||||
assert.equal(tx.ins.length, 1)
|
assert.equal(tx.ins.length, 1)
|
||||||
|
|
||||||
var input = tx.ins[0]
|
var input = tx.ins[0]
|
||||||
assert.deepEqual(input.sequence, [255, 255, 255, 255])
|
assert.equal(input.sequence, 4294967295)
|
||||||
|
|
||||||
assert.equal(input.outpoint.index, 0)
|
assert.equal(input.outpoint.index, 0)
|
||||||
assert.equal(input.outpoint.hash, "0cb859105100ebc3344f749c835c7af7d7103ec0d8cbc3d8ccbd5d28c3c36b57")
|
assert.equal(input.outpoint.hash, "0cb859105100ebc3344f749c835c7af7d7103ec0d8cbc3d8ccbd5d28c3c36b57")
|
||||||
|
|
|
@ -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)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -539,7 +538,7 @@ describe('Wallet', function() {
|
||||||
})
|
})
|
||||||
|
|
||||||
function fakeTxHash(i) {
|
function fakeTxHash(i) {
|
||||||
return "txtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtx" + i
|
return "efefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe" + i
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue