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, diff --git a/src/script_number.js b/src/script_number.js new file mode 100644 index 0000000..2c1a0ce --- /dev/null +++ b/src/script_number.js @@ -0,0 +1,66 @@ +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') + } + } + + // 40-bit + if (length === 5) { + var a = buffer.readUInt32LE(0) + var b = buffer.readUInt8(4) + + if (b & 0x80) return -((b & ~0x80) * 0x100000000 + a) + 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) + } + + if (buffer[length - 1] & 0x80) return -(result & ~(0x80 << (8 * (length - 1)))) + return result +} + +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) + }) + }) + }) +})