From 059f48b0c426f9a0c901bc0b9c1fef09b29c2632 Mon Sep 17 00:00:00 2001
From: Daniel Cousens <github@dcousens.com>
Date: Tue, 5 Jan 2016 01:59:14 +1100
Subject: [PATCH] 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)
+      })
+    })
+  })
+})