From 059f48b0c426f9a0c901bc0b9c1fef09b29c2632 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Tue, 5 Jan 2016 01:59:14 +1100 Subject: [PATCH 1/5] script_number: add impl/tests --- src/script_number.js | 78 +++++++++++ test/fixtures/script_number.json | 225 +++++++++++++++++++++++++++++++ test/script_number.js | 27 ++++ 3 files changed, 330 insertions(+) create mode 100644 src/script_number.js create mode 100644 test/fixtures/script_number.json create mode 100644 test/script_number.js diff --git a/src/script_number.js b/src/script_number.js new file mode 100644 index 0000000..857e44b --- /dev/null +++ b/src/script_number.js @@ -0,0 +1,78 @@ +function decode (buffer, maxLength, minimal) { + maxLength = maxLength || 4 + minimal = minimal === undefined ? true : minimal + + var length = buffer.length + if (length === 0) return 0 + if (length > maxLength) throw new TypeError('Script number overflow') + if (minimal) { + if ((buffer[length - 1] & 0x7f) === 0) { + if (length <= 1 || (buffer[length - 2] & 0x80) === 0) throw new Error('Non-minimally encoded script number') + } + } + + // 32-bit? + if (length < 5) { + var result = 0 + + for (var i = 0; i < length; ++i) { + result += buffer[i] << (8 * i) + } + + if (buffer[length - 1] & 0x80) return -(result & ~(0x80 << (8 * (length - 1)))) + return result + } + + // 40-bit + var a = buffer.readUInt32LE(0) + var b = buffer.readUInt8(4) + + // TODO: refactor + var neg = false + if (b & 0x80) { + b &= ~0x80 + neg = true + } + + var r = b * 0x100000000 + a + + if (neg) { + r = -r + } + + return r +} + +function scriptNumSize (i) { + return i > 0x7fffffff ? 5 + : i > 0x7fffff ? 4 + : i > 0x7fff ? 3 + : i > 0x7f ? 2 + : i > 0x00 ? 1 + : 0 +} + +function encode (number) { + var value = Math.abs(number) + var size = scriptNumSize(value) + var buffer = new Buffer(size) + var negative = number < 0 + + for (var i = 0; i < size; ++i) { + buffer.writeUInt8(value & 0xff, i) + value >>= 8 + } + + if (buffer[size - 1] & 0x80) { + buffer.writeUInt8(negative ? 0x80 : 0x00, size - 1) + } else if (negative) { + buffer[size - 1] |= 0x80 + } + + return buffer +} + +module.exports = { + decode: decode, + encode: encode +} diff --git a/test/fixtures/script_number.json b/test/fixtures/script_number.json new file mode 100644 index 0000000..f934740 --- /dev/null +++ b/test/fixtures/script_number.json @@ -0,0 +1,225 @@ +[ + { + "hex": "", + "number": 0 + }, + { + "hex": "01", + "number": 1 + }, + { + "hex": "02", + "number": 2 + }, + { + "hex": "03", + "number": 3 + }, + { + "hex": "7e", + "number": 126 + }, + { + "hex": "7f", + "number": 127 + }, + { + "hex": "8000", + "number": 128 + }, + { + "hex": "8100", + "number": 129 + }, + { + "hex": "8200", + "number": 130 + }, + { + "hex": "ff00", + "number": 255 + }, + { + "hex": "fe7f", + "number": 32766 + }, + { + "hex": "ff7f", + "number": 32767 + }, + { + "hex": "008000", + "number": 32768 + }, + { + "hex": "ffff00", + "number": 65535 + }, + { + "hex": "018000", + "number": 32769 + }, + { + "hex": "028000", + "number": 32770 + }, + { + "hex": "ffffff00", + "number": 16777215 + }, + { + "hex": "feff7f", + "number": 8388606 + }, + { + "hex": "ffff7f", + "number": 8388607 + }, + { + "hex": "00008000", + "number": 8388608 + }, + { + "hex": "01008000", + "number": 8388609 + }, + { + "hex": "02008000", + "number": 8388610 + }, + { + "hex": "feffff7f", + "number": 2147483646 + }, + { + "hex": "ffffff7f", + "number": 2147483647 + }, + { + "bytes": 5, + "hex": "0000008000", + "number": 2147483648 + }, + { + "bytes": 5, + "hex": "0100008000", + "number": 2147483649 + }, + { + "bytes": 5, + "hex": "0200008000", + "number": 2147483650 + }, + { + "bytes": 5, + "hex": "ffffffff00", + "number": 4294967295 + }, + { + "bytes": 5, + "hex": "0200008080", + "number": -2147483650 + }, + { + "bytes": 5, + "hex": "0100008080", + "number": -2147483649 + }, + { + "bytes": 5, + "hex": "0000008080", + "number": -2147483648 + }, + { + "hex": "ffffffff", + "number": -2147483647 + }, + { + "hex": "feffffff", + "number": -2147483646 + }, + { + "hex": "fdffffff", + "number": -2147483645 + }, + { + "hex": "ffffff80", + "number": -16777215 + }, + { + "hex": "01008080", + "number": -8388609 + }, + { + "hex": "00008080", + "number": -8388608 + }, + { + "hex": "ffffff", + "number": -8388607 + }, + { + "hex": "feffff", + "number": -8388606 + }, + { + "hex": "fdffff", + "number": -8388605 + }, + { + "hex": "ffff80", + "number": -65535 + }, + { + "hex": "018080", + "number": -32769 + }, + { + "hex": "008080", + "number": -32768 + }, + { + "hex": "ffff", + "number": -32767 + }, + { + "hex": "feff", + "number": -32766 + }, + { + "hex": "fdff", + "number": -32765 + }, + { + "hex": "ff80", + "number": -255 + }, + { + "hex": "8180", + "number": -129 + }, + { + "hex": "8080", + "number": -128 + }, + { + "hex": "ff", + "number": -127 + }, + { + "hex": "fe", + "number": -126 + }, + { + "hex": "fd", + "number": -125 + }, + { + "hex": "82", + "number": -2 + }, + { + "hex": "81", + "number": -1 + } +] diff --git a/test/script_number.js b/test/script_number.js new file mode 100644 index 0000000..8200df3 --- /dev/null +++ b/test/script_number.js @@ -0,0 +1,27 @@ +/* global describe, it */ + +var assert = require('assert') +var scriptNumber = require('../src/script_number') +var fixtures = require('./fixtures/script_number.json') + +describe('script', function () { + describe('decode', function () { + fixtures.forEach(function (f) { + it(f.hex + ' returns ' + f.number, function () { + var actual = scriptNumber.decode(new Buffer(f.hex, 'hex'), f.bytes) + + assert.strictEqual(actual, f.number) + }) + }) + }) + + describe('encode', function () { + fixtures.forEach(function (f) { + it(f.number + ' returns ' + f.hex, function () { + var actual = scriptNumber.encode(f.number) + + assert.strictEqual(actual.toString('hex'), f.hex) + }) + }) + }) +}) From 29a1a839ec8bcbae22f44f8fcf67446057b7678e Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Tue, 5 Jan 2016 02:11:48 +1100 Subject: [PATCH 2/5] script: exposes scriptNumber through script.number --- src/script.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/script.js b/src/script.js index 6f9b21e..24783e5 100644 --- a/src/script.js +++ b/src/script.js @@ -372,6 +372,8 @@ module.exports = { fromASM: fromASM, toASM: toASM, + number: require('./script_number'), + isCanonicalPubKey: isCanonicalPubKey, isCanonicalSignature: isCanonicalSignature, isDefinedHashType: isDefinedHashType, From 945bdfa7477c86cb2853056f65cd521c2a9f804b Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Tue, 5 Jan 2016 02:18:50 +1100 Subject: [PATCH 3/5] script_number: refactor --- src/script_number.js | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/script_number.js b/src/script_number.js index 857e44b..f2db26b 100644 --- a/src/script_number.js +++ b/src/script_number.js @@ -11,36 +11,36 @@ function decode (buffer, maxLength, minimal) { } } - // 32-bit? - if (length < 5) { - var result = 0 + var result - for (var i = 0; i < length; ++i) { - result += buffer[i] << (8 * i) - } + // 40-bit + if (length === 5) { + var a = buffer.readUInt32LE(0) + var b = buffer.readUInt8(4) - if (buffer[length - 1] & 0x80) return -(result & ~(0x80 << (8 * (length - 1)))) - return result - } + if (b & 0x80) return -((b & ~0x80) * 0x100000000 + a) + return b * 0x100000000 + a - // 40-bit - var a = buffer.readUInt32LE(0) - var b = buffer.readUInt8(4) + // 32-bit + } else if (length === 4) { + result = buffer.readUInt32LE(0) - // TODO: refactor - var neg = false - if (b & 0x80) { - b &= ~0x80 - neg = true - } + // 24-bit + } else if (length === 3) { + result = buffer.readUInt16LE(0) + result |= buffer.readUInt8(2) << 16 - var r = b * 0x100000000 + a + // 16-bit + } else if (length === 2) { + result = buffer.readUInt16LE(0) - if (neg) { - r = -r + // 8-bit + } else { + result = buffer.readUInt8(0) } - return r + if (buffer[length - 1] & 0x80) return -(result & ~(0x80 << (8 * (length - 1)))) + return result } function scriptNumSize (i) { From 302bbe81ba17670b556f0d52c6bfb28112a840ab Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Tue, 5 Jan 2016 02:38:04 +1100 Subject: [PATCH 4/5] script_number: refactor to mimic bitcoin/bitcoin --- src/script_number.js | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/src/script_number.js b/src/script_number.js index f2db26b..5177a8c 100644 --- a/src/script_number.js +++ b/src/script_number.js @@ -20,23 +20,11 @@ function decode (buffer, maxLength, minimal) { if (b & 0x80) return -((b & ~0x80) * 0x100000000 + a) return b * 0x100000000 + a + } - // 32-bit - } else if (length === 4) { - result = buffer.readUInt32LE(0) - - // 24-bit - } else if (length === 3) { - result = buffer.readUInt16LE(0) - result |= buffer.readUInt8(2) << 16 - - // 16-bit - } else if (length === 2) { - result = buffer.readUInt16LE(0) - - // 8-bit - } else { - result = buffer.readUInt8(0) + // 32-bit / 24-bit / 16-bit / 8-bit + for (var i = 0; i < length; ++i) { + result |= buffer[i] << (8 * i) } if (buffer[length - 1] & 0x80) return -(result & ~(0x80 << (8 * (length - 1)))) From d7da2920639377ad141e387c4e156000553adaee Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Tue, 5 Jan 2016 13:11:49 +1100 Subject: [PATCH 5/5] script_number: initialize result to 0 --- src/script_number.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/script_number.js b/src/script_number.js index 5177a8c..2c1a0ce 100644 --- a/src/script_number.js +++ b/src/script_number.js @@ -11,8 +11,6 @@ function decode (buffer, maxLength, minimal) { } } - var result - // 40-bit if (length === 5) { var a = buffer.readUInt32LE(0) @@ -22,6 +20,8 @@ function decode (buffer, maxLength, minimal) { return b * 0x100000000 + a } + var result = 0 + // 32-bit / 24-bit / 16-bit / 8-bit for (var i = 0; i < length; ++i) { result |= buffer[i] << (8 * i)