script: refactor opcode and optimize.

This commit is contained in:
Christopher Jeffrey 2017-08-25 18:54:51 -07:00
parent eabcf21f49
commit 49154be76d
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
5 changed files with 136 additions and 233 deletions

View File

@ -23,7 +23,7 @@ const ScriptNum = require('./scriptnum');
*/ */
exports.opcodes = { exports.opcodes = {
OP_FALSE: 0x00, // Push
OP_0: 0x00, OP_0: 0x00,
OP_PUSHDATA1: 0x4c, OP_PUSHDATA1: 0x4c,
@ -34,7 +34,6 @@ exports.opcodes = {
OP_RESERVED: 0x50, OP_RESERVED: 0x50,
OP_TRUE: 0x51,
OP_1: 0x51, OP_1: 0x51,
OP_2: 0x52, OP_2: 0x52,
OP_3: 0x53, OP_3: 0x53,
@ -52,6 +51,7 @@ exports.opcodes = {
OP_15: 0x5f, OP_15: 0x5f,
OP_16: 0x60, OP_16: 0x60,
// Control
OP_NOP: 0x61, OP_NOP: 0x61,
OP_VER: 0x62, OP_VER: 0x62,
OP_IF: 0x63, OP_IF: 0x63,
@ -63,6 +63,7 @@ exports.opcodes = {
OP_VERIFY: 0x69, OP_VERIFY: 0x69,
OP_RETURN: 0x6a, OP_RETURN: 0x6a,
// Stack
OP_TOALTSTACK: 0x6b, OP_TOALTSTACK: 0x6b,
OP_FROMALTSTACK: 0x6c, OP_FROMALTSTACK: 0x6c,
OP_2DROP: 0x6d, OP_2DROP: 0x6d,
@ -83,22 +84,24 @@ exports.opcodes = {
OP_SWAP: 0x7c, OP_SWAP: 0x7c,
OP_TUCK: 0x7d, OP_TUCK: 0x7d,
// Splice
OP_CAT: 0x7e, OP_CAT: 0x7e,
OP_SUBSTR: 0x7f, OP_SUBSTR: 0x7f,
OP_LEFT: 0x80, OP_LEFT: 0x80,
OP_RIGHT: 0x81, OP_RIGHT: 0x81,
OP_SIZE: 0x82, OP_SIZE: 0x82,
// Bit
OP_INVERT: 0x83, OP_INVERT: 0x83,
OP_AND: 0x84, OP_AND: 0x84,
OP_OR: 0x85, OP_OR: 0x85,
OP_XOR: 0x86, OP_XOR: 0x86,
OP_EQUAL: 0x87, OP_EQUAL: 0x87,
OP_EQUALVERIFY: 0x88, OP_EQUALVERIFY: 0x88,
OP_RESERVED1: 0x89, OP_RESERVED1: 0x89,
OP_RESERVED2: 0x8a, OP_RESERVED2: 0x8a,
// Numeric
OP_1ADD: 0x8b, OP_1ADD: 0x8b,
OP_1SUB: 0x8c, OP_1SUB: 0x8c,
OP_2MUL: 0x8d, OP_2MUL: 0x8d,
@ -127,6 +130,7 @@ exports.opcodes = {
OP_MAX: 0xa4, OP_MAX: 0xa4,
OP_WITHIN: 0xa5, OP_WITHIN: 0xa5,
// Crypto
OP_RIPEMD160: 0xa6, OP_RIPEMD160: 0xa6,
OP_SHA1: 0xa7, OP_SHA1: 0xa7,
OP_SHA256: 0xa8, OP_SHA256: 0xa8,
@ -138,11 +142,9 @@ exports.opcodes = {
OP_CHECKMULTISIG: 0xae, OP_CHECKMULTISIG: 0xae,
OP_CHECKMULTISIGVERIFY: 0xaf, OP_CHECKMULTISIGVERIFY: 0xaf,
OP_EVAL: 0xb0, // Expansion
OP_NOP1: 0xb0, OP_NOP1: 0xb0,
OP_NOP2: 0xb1,
OP_CHECKLOCKTIMEVERIFY: 0xb1, OP_CHECKLOCKTIMEVERIFY: 0xb1,
OP_NOP3: 0xb2,
OP_CHECKSEQUENCEVERIFY: 0xb2, OP_CHECKSEQUENCEVERIFY: 0xb2,
OP_NOP4: 0xb3, OP_NOP4: 0xb3,
OP_NOP5: 0xb4, OP_NOP5: 0xb4,
@ -152,8 +154,7 @@ exports.opcodes = {
OP_NOP9: 0xb8, OP_NOP9: 0xb8,
OP_NOP10: 0xb9, OP_NOP10: 0xb9,
OP_PUBKEYHASH: 0xfd, // Custom
OP_PUBKEY: 0xfe,
OP_INVALIDOPCODE: 0xff OP_INVALIDOPCODE: 0xff
}; };
@ -496,7 +497,7 @@ exports.isSignatureEncoding = function isSignatureEncoding(sig) {
exports.toASM = function toASM(item, decode) { exports.toASM = function toASM(item, decode) {
if (item.length <= 4) { if (item.length <= 4) {
const num = ScriptNum.decode(item, false, 4); const num = ScriptNum.decode(item);
return num.toString(10); return num.toString(10);
} }

View File

@ -14,11 +14,15 @@ const common = require('./common');
const BufferReader = require('../utils/reader'); const BufferReader = require('../utils/reader');
const StaticWriter = require('../utils/staticwriter'); const StaticWriter = require('../utils/staticwriter');
const opcodes = common.opcodes; const opcodes = common.opcodes;
const opCache = []; const opCache = [];
let PARSE_ERROR = null;
/** /**
* A simple struct which contains * A simple struct which contains
* an opcode and pushdata buffer. * an opcode and pushdata buffer.
* Note: this should not be called directly.
* @alias module:script.Opcode * @alias module:script.Opcode
* @constructor * @constructor
* @param {Number} value - Opcode. * @param {Number} value - Opcode.
@ -44,24 +48,25 @@ Opcode.prototype.isMinimal = function isMinimal() {
if (!this.data) if (!this.data)
return true; return true;
if (this.data.length === 0) if (this.data.length === 1) {
return this.value === opcodes.OP_0; if (this.data[0] === 0x81)
return false;
if (this.data.length === 1 && this.data[0] >= 1 && this.data[0] <= 16) if (this.data[0] >= 1 && this.data[0] <= 16)
return false; return false;
}
if (this.data.length === 1 && this.data[0] === 0x81) if (this.data.length <= 0x4b)
return false;
if (this.data.length <= 75)
return this.value === this.data.length; return this.value === this.data.length;
if (this.data.length <= 255) if (this.data.length <= 0xff)
return this.value === opcodes.OP_PUSHDATA1; return this.value === opcodes.OP_PUSHDATA1;
if (this.data.length <= 65535) if (this.data.length <= 0xffff)
return this.value === opcodes.OP_PUSHDATA2; return this.value === opcodes.OP_PUSHDATA2;
assert(this.value === opcodes.OP_PUSHDATA4);
return true; return true;
}; };
@ -156,19 +161,16 @@ Opcode.prototype.toLength = function toLength() {
*/ */
Opcode.prototype.toPush = function toPush() { Opcode.prototype.toPush = function toPush() {
if (this.data) if (this.value === opcodes.OP_0)
return this.data; return common.small[0 + 1];
if (this.value === opcodes.OP_1NEGATE) if (this.value === opcodes.OP_1NEGATE)
return common.small[-1 + 1]; return common.small[-1 + 1];
if (this.value === opcodes.OP_0)
return common.small[0 + 1];
if (this.value >= opcodes.OP_1 && this.value <= opcodes.OP_16) if (this.value >= opcodes.OP_1 && this.value <= opcodes.OP_16)
return common.small[this.value - 0x50 + 1]; return common.small[this.value - 0x50 + 1];
return null; return this.toData();
}; };
/** /**
@ -203,17 +205,20 @@ Opcode.prototype.toSmall = function toSmall() {
/** /**
* Convert opcode to script number. * Convert opcode to script number.
* @param {Boolean?} minimal
* @param {Number?} limit
* @returns {ScriptNum|null} * @returns {ScriptNum|null}
*/ */
Opcode.prototype.toNum = function toNum(minimal, limit) { Opcode.prototype.toNum = function toNum(minimal, limit) {
if (this.value === opcodes.OP_0)
return ScriptNum.fromInt(0);
if (this.value === opcodes.OP_1NEGATE) if (this.value === opcodes.OP_1NEGATE)
return ScriptNum.fromInt(-1); return ScriptNum.fromInt(-1);
const smi = this.toSmall(); if (this.value >= opcodes.OP_1 && this.value <= opcodes.OP_16)
return ScriptNum.fromInt(this.value - 0x50);
if (smi !== -1)
return ScriptNum.fromInt(smi);
if (!this.data) if (!this.data)
return null; return null;
@ -223,11 +228,13 @@ Opcode.prototype.toNum = function toNum(minimal, limit) {
/** /**
* Convert opcode to integer. * Convert opcode to integer.
* @param {Boolean?} minimal
* @param {Number?} limit
* @returns {Number} * @returns {Number}
*/ */
Opcode.prototype.toInt = function toInt() { Opcode.prototype.toInt = function toInt(minimal, limit) {
const num = this.toNum(); const num = this.toNum(minimal, limit);
if (!num) if (!num)
return -1; return -1;
@ -255,15 +262,13 @@ Opcode.prototype.toBool = function toBool() {
*/ */
Opcode.prototype.toSymbol = function toSymbol() { Opcode.prototype.toSymbol = function toSymbol() {
let op = this.value; if (this.value === -1)
return 'OP_INVALIDOPCODE';
if (op === -1) const symbol = common.opcodesByVal[this.value];
op = 0xff;
let symbol = common.opcodesByVal[op]; if (!symbol)
return `0x${util.hex8(this.value)}`;
if (symbol == null)
symbol = `0x${util.hex8(op)}`;
return symbol; return symbol;
}; };
@ -277,9 +282,6 @@ Opcode.prototype.getSize = function getSize() {
if (!this.data) if (!this.data)
return 1; return 1;
if (this.value <= 0x4b)
return 1 + this.data.length;
switch (this.value) { switch (this.value) {
case opcodes.OP_PUSHDATA1: case opcodes.OP_PUSHDATA1:
return 2 + this.data.length; return 2 + this.data.length;
@ -288,7 +290,7 @@ Opcode.prototype.getSize = function getSize() {
case opcodes.OP_PUSHDATA4: case opcodes.OP_PUSHDATA4:
return 5 + this.data.length; return 5 + this.data.length;
default: default:
throw new Error('Unknown pushdata opcode.'); return 1 + this.data.length;
} }
}; };
@ -306,13 +308,6 @@ Opcode.prototype.toWriter = function toWriter(bw) {
return bw; return bw;
} }
if (this.value <= 0x4b) {
assert(this.value === this.data.length);
bw.writeU8(this.value);
bw.writeBytes(this.data);
return bw;
}
switch (this.value) { switch (this.value) {
case opcodes.OP_PUSHDATA1: case opcodes.OP_PUSHDATA1:
bw.writeU8(this.value); bw.writeU8(this.value);
@ -330,7 +325,10 @@ Opcode.prototype.toWriter = function toWriter(bw) {
bw.writeBytes(this.data); bw.writeBytes(this.data);
break; break;
default: default:
throw new Error('Unknown pushdata opcode.'); assert(this.value === this.data.length);
bw.writeU8(this.value);
bw.writeBytes(this.data);
break;
} }
return bw; return bw;
@ -352,11 +350,17 @@ Opcode.prototype.toRaw = function toRaw() {
*/ */
Opcode.prototype.toFormat = function toFormat() { Opcode.prototype.toFormat = function toFormat() {
// Bad push
if (this.value === -1) if (this.value === -1)
return 'OP_INVALIDOPCODE'; return '0x01';
if (this.data) { if (this.data) {
// Numbers
if (this.data.length <= 4) {
const num = this.toNum();
if (this.equals(Opcode.fromNum(num)))
return num.toString(10);
}
const symbol = common.opcodesByVal[this.value]; const symbol = common.opcodesByVal[this.value];
const data = this.data.toString('hex'); const data = this.data.toString('hex');
@ -413,10 +417,9 @@ Opcode.fromOp = function fromOp(op) {
const cached = opCache[op]; const cached = opCache[op];
if (cached) assert(cached, 'Bad opcode.');
return cached;
return new Opcode(op, null); return cached;
}; };
/** /**
@ -430,11 +433,11 @@ Opcode.fromData = function fromData(data) {
assert(Buffer.isBuffer(data)); assert(Buffer.isBuffer(data));
if (data.length === 1) { if (data.length === 1) {
if (data[0] >= 1 && data[0] <= 16)
return Opcode.fromOp(data[0] + 0x50);
if (data[0] === 0x81) if (data[0] === 0x81)
return Opcode.fromOp(opcodes.OP_1NEGATE); return Opcode.fromOp(opcodes.OP_1NEGATE);
if (data[0] >= 1 && data[0] <= 16)
return Opcode.fromOp(data[0] + 0x50);
} }
return Opcode.fromPush(data); return Opcode.fromPush(data);
@ -513,12 +516,12 @@ Opcode.fromNum = function fromNum(num) {
Opcode.fromInt = function fromInt(num) { Opcode.fromInt = function fromInt(num) {
assert(util.isInt(num)); assert(util.isInt(num));
if (num === -1)
return Opcode.fromOp(opcodes.OP_1NEGATE);
if (num === 0) if (num === 0)
return Opcode.fromOp(opcodes.OP_0); return Opcode.fromOp(opcodes.OP_0);
if (num === -1)
return Opcode.fromOp(opcodes.OP_1NEGATE);
if (num >= 1 && num <= 16) if (num >= 1 && num <= 16)
return Opcode.fromOp(num + 0x50); return Opcode.fromOp(num + 0x50);
@ -554,18 +557,19 @@ Opcode.fromSymbol = function fromSymbol(name) {
if (!util.startsWith(name, 'OP_')) if (!util.startsWith(name, 'OP_'))
name = `OP_${name}`; name = `OP_${name}`;
let op = common.opcodes[name]; const op = common.opcodes[name];
if (op == null) { if (op != null)
assert(util.startsWith(name, 'OP_0X'), 'Unknown opcode.'); return Opcode.fromOp(op);
assert(name.length === 7, 'Unknown opcode.');
op = parseInt(name.substring(5), 16); assert(util.startsWith(name, 'OP_0X'), 'Unknown opcode.');
assert(name.length === 7, 'Unknown opcode.');
assert(util.isU8(op), 'Unknown opcode.'); const value = parseInt(name.substring(5), 16);
}
return Opcode.fromOp(op); assert(util.isU8(value), 'Unknown opcode.');
return Opcode.fromOp(value);
}; };
/** /**
@ -576,84 +580,72 @@ Opcode.fromSymbol = function fromSymbol(name) {
Opcode.fromReader = function fromReader(br) { Opcode.fromReader = function fromReader(br) {
const value = br.readU8(); const value = br.readU8();
const op = opCache[value];
const cached = opCache[value]; if (op)
if (cached)
return cached;
const op = new Opcode(value, null);
if (value >= 0x01 && value <= 0x4b) {
if (br.left() < value) {
op.value = -1;
br.seek(br.left());
return op;
}
op.data = br.readBytes(value);
return op; return op;
}
switch (value) { switch (value) {
case opcodes.OP_PUSHDATA1: { case opcodes.OP_PUSHDATA1: {
if (br.left() < 1) { if (br.left() < 1)
op.value = -1; return PARSE_ERROR;
break;
}
const size = br.readU8(); const size = br.readU8();
if (br.left() < size) { if (br.left() < size) {
op.value = -1;
br.seek(br.left()); br.seek(br.left());
break; return PARSE_ERROR;
} }
op.data = br.readBytes(size); const data = br.readBytes(size);
break; return new Opcode(value, data);
} }
case opcodes.OP_PUSHDATA2: { case opcodes.OP_PUSHDATA2: {
if (br.left() < 2) { if (br.left() < 2) {
op.value = -1;
br.seek(br.left()); br.seek(br.left());
break; return PARSE_ERROR;
} }
const size = br.readU16(); const size = br.readU16();
if (br.left() < size) { if (br.left() < size) {
op.value = -1;
br.seek(br.left()); br.seek(br.left());
break; return PARSE_ERROR;
} }
op.data = br.readBytes(size); const data = br.readBytes(size);
break; return new Opcode(value, data);
} }
case opcodes.OP_PUSHDATA4: { case opcodes.OP_PUSHDATA4: {
if (br.left() < 4) { if (br.left() < 4) {
op.value = -1;
br.seek(br.left()); br.seek(br.left());
break; return PARSE_ERROR;
} }
const size = br.readU32(); const size = br.readU32();
if (br.left() < size) { if (br.left() < size) {
op.value = -1;
br.seek(br.left()); br.seek(br.left());
break; return PARSE_ERROR;
} }
op.data = br.readBytes(size); const data = br.readBytes(size);
break; return new Opcode(value, data);
}
default: {
if (br.left() < value) {
br.seek(br.left());
return PARSE_ERROR;
}
const data = br.readBytes(value);
return new Opcode(value, data);
} }
} }
return op;
}; };
/** /**
@ -680,17 +672,15 @@ Opcode.isOpcode = function isOpcode(obj) {
* Fill Cache * Fill Cache
*/ */
PARSE_ERROR = Object.freeze(new Opcode(-1));
for (let value = 0x00; value <= 0xff; value++) { for (let value = 0x00; value <= 0xff; value++) {
if (value >= 0x01 && value <= 0x4e) { if (value >= 0x01 && value <= 0x4e) {
opCache.push(null); opCache.push(null);
continue; continue;
} }
const op = new Opcode(value);
const op = new Opcode(value, null); opCache.push(Object.freeze(op));
Object.freeze(op);
opCache.push(op);
} }
/* /*

View File

@ -657,7 +657,7 @@ Script.prototype.execute = function execute(stack, flags, tx, index, value, vers
if (locktime.isNeg()) if (locktime.isNeg())
throw new ScriptError('NEGATIVE_LOCKTIME', op, ip); throw new ScriptError('NEGATIVE_LOCKTIME', op, ip);
locktime = locktime.toNumber(); locktime = locktime.toDouble();
if (!tx.verifyLocktime(index, locktime)) if (!tx.verifyLocktime(index, locktime))
throw new ScriptError('UNSATISFIED_LOCKTIME', op, ip); throw new ScriptError('UNSATISFIED_LOCKTIME', op, ip);
@ -683,7 +683,7 @@ Script.prototype.execute = function execute(stack, flags, tx, index, value, vers
if (locktime.isNeg()) if (locktime.isNeg())
throw new ScriptError('NEGATIVE_LOCKTIME', op, ip); throw new ScriptError('NEGATIVE_LOCKTIME', op, ip);
locktime = locktime.toNumber(); locktime = locktime.toDouble();
if (!tx.verifySequence(index, locktime)) if (!tx.verifySequence(index, locktime))
throw new ScriptError('UNSATISFIED_LOCKTIME', op, ip); throw new ScriptError('UNSATISFIED_LOCKTIME', op, ip);
@ -896,7 +896,7 @@ Script.prototype.execute = function execute(stack, flags, tx, index, value, vers
if (stack.length < 2) if (stack.length < 2)
throw new ScriptError('INVALID_STACK_OPERATION', op, ip); throw new ScriptError('INVALID_STACK_OPERATION', op, ip);
const num = stack.getInt(-1, minimal); const num = stack.getInt(-1, minimal, 4);
stack.pop(); stack.pop();
if (num < 0 || num >= stack.length) if (num < 0 || num >= stack.length)
@ -971,7 +971,7 @@ Script.prototype.execute = function execute(stack, flags, tx, index, value, vers
if (stack.length < 1) if (stack.length < 1)
throw new ScriptError('INVALID_STACK_OPERATION', op, ip); throw new ScriptError('INVALID_STACK_OPERATION', op, ip);
let num = stack.getNum(-1, minimal); let num = stack.getNum(-1, minimal, 4);
let cmp; let cmp;
switch (op.value) { switch (op.value) {
@ -1021,8 +1021,8 @@ Script.prototype.execute = function execute(stack, flags, tx, index, value, vers
if (stack.length < 2) if (stack.length < 2)
throw new ScriptError('INVALID_STACK_OPERATION', op, ip); throw new ScriptError('INVALID_STACK_OPERATION', op, ip);
const n1 = stack.getNum(-2, minimal); const n1 = stack.getNum(-2, minimal, 4);
const n2 = stack.getNum(-1, minimal); const n2 = stack.getNum(-1, minimal, 4);
let num, cmp; let num, cmp;
switch (op.value) { switch (op.value) {
@ -1095,9 +1095,9 @@ Script.prototype.execute = function execute(stack, flags, tx, index, value, vers
if (stack.length < 3) if (stack.length < 3)
throw new ScriptError('INVALID_STACK_OPERATION', op, ip); throw new ScriptError('INVALID_STACK_OPERATION', op, ip);
const n1 = stack.getNum(-3, minimal); const n1 = stack.getNum(-3, minimal, 4);
const n2 = stack.getNum(-2, minimal); const n2 = stack.getNum(-2, minimal, 4);
const n3 = stack.getNum(-1, minimal); const n3 = stack.getNum(-1, minimal, 4);
const val = n2.lte(n1) && n1.lt(n3); const val = n2.lte(n1) && n1.lt(n3);
@ -1201,7 +1201,7 @@ Script.prototype.execute = function execute(stack, flags, tx, index, value, vers
if (stack.length < i) if (stack.length < i)
throw new ScriptError('INVALID_STACK_OPERATION', op, ip); throw new ScriptError('INVALID_STACK_OPERATION', op, ip);
let n = stack.getInt(-i, minimal); let n = stack.getInt(-i, minimal, 4);
let okey = n + 2; let okey = n + 2;
let ikey, isig; let ikey, isig;
@ -1220,7 +1220,7 @@ Script.prototype.execute = function execute(stack, flags, tx, index, value, vers
if (stack.length < i) if (stack.length < i)
throw new ScriptError('INVALID_STACK_OPERATION', op, ip); throw new ScriptError('INVALID_STACK_OPERATION', op, ip);
let m = stack.getInt(-i, minimal); let m = stack.getInt(-i, minimal, 4);
if (m < 0 || m > n) if (m < 0 || m > n)
throw new ScriptError('SIG_COUNT', op, ip); throw new ScriptError('SIG_COUNT', op, ip);
@ -1425,7 +1425,7 @@ Script.prototype.isCode = function isCode() {
return false; return false;
} }
if (op.value > opcodes.OP_NOP3) if (op.value > opcodes.OP_CHECKSEQUENCEVERIFY)
return false; return false;
} }
@ -1449,7 +1449,7 @@ Script.prototype.fromPubkey = function fromPubkey(key) {
key = this.raw.slice(1, 1 + key.length); key = this.raw.slice(1, 1 + key.length);
this.code.length = 0; this.code.length = 0;
this.code.push(Opcode.fromData(key)); this.code.push(Opcode.fromPush(key));
this.code.push(Opcode.fromOp(opcodes.OP_CHECKSIG)); this.code.push(Opcode.fromOp(opcodes.OP_CHECKSIG));
return this; return this;
@ -2470,53 +2470,25 @@ Script.getCoinbaseHeight = function getCoinbaseHeight(raw) {
if (raw.length === 0) if (raw.length === 0)
return -1; return -1;
// First opcode. if (raw[0] >= opcodes.OP_1 && raw[0] <= opcodes.OP_16)
const value = raw[0]; return raw[0] - 0x50;
// Small ints are allowed. if (raw[0] > 0x06)
if (value === 0)
return 0;
if (value >= opcodes.OP_1 && value <= opcodes.OP_16)
return value - 0x50;
// No more than 6 bytes (we can't
// handle 7 byte JS numbers and
// height 281 trillion is far away).
if (value > 0x06)
return -1; return -1;
// No bad pushes allowed. const op = Opcode.fromRaw(raw);
if (raw.length < 1 + value) const num = op.toNum();
if (!num)
return 1;
if (num.isNeg())
return -1; return -1;
const data = raw.slice(1, 1 + value); if (!op.equals(Opcode.fromNum(num)))
// Deserialize the height.
let height;
try {
height = ScriptNum.decode(data, true, 6);
} catch (e) {
return -1;
}
// Cannot be negative.
if (height.isNeg())
return -1; return -1;
// Reserialize the height. return num.toDouble();
const op = Opcode.fromNum(height);
// Should have been OP_0-OP_16.
if (!op.data)
return -1;
// Ensure the miner serialized the
// number in the most minimal fashion.
if (!data.equals(op.data))
return -1;
return height.toNumber();
}; };
/** /**

View File

@ -128,7 +128,6 @@ ScriptNum.prototype.toRaw = function toRaw() {
ScriptNum.prototype.fromRaw = function fromRaw(data) { ScriptNum.prototype.fromRaw = function fromRaw(data) {
assert(Buffer.isBuffer(data)); assert(Buffer.isBuffer(data));
assert(data.length <= 9);
// Empty arrays are always zero. // Empty arrays are always zero.
if (data.length === 0) if (data.length === 0)
@ -136,10 +135,6 @@ ScriptNum.prototype.fromRaw = function fromRaw(data) {
// Read number (9 bytes max). // Read number (9 bytes max).
switch (data.length) { switch (data.length) {
case 9:
// Note: this shift overflows to
// zero in modern bitcoin core.
this.lo |= data[8];
case 8: case 8:
this.hi |= data[7] << 24; this.hi |= data[7] << 24;
case 7: case 7:
@ -156,6 +151,11 @@ ScriptNum.prototype.fromRaw = function fromRaw(data) {
this.lo |= data[1] << 8; this.lo |= data[1] << 8;
case 1: case 1:
this.lo |= data[0]; this.lo |= data[0];
break;
default:
for (let i = 0; i < data.length; i++)
this.orb(i, data[i]);
break;
} }
// Remove high bit and flip sign. // Remove high bit and flip sign.
@ -188,20 +188,9 @@ ScriptNum.prototype.encode = function encode() {
ScriptNum.prototype.decode = function decode(data, minimal, limit) { ScriptNum.prototype.decode = function decode(data, minimal, limit) {
assert(Buffer.isBuffer(data)); assert(Buffer.isBuffer(data));
if (minimal == null) if (limit != null && data.length > limit)
minimal = true;
if (limit == null)
limit = 4;
// We can't handle more than 9 bytes.
assert(limit >= 4 && limit <= 9, 'Bad script number size limit.');
// Max size is 4 bytes by default, 9 bytes max.
if (data.length > limit)
throw new ScriptError('UNKNOWN_ERROR', 'Script number overflow.'); throw new ScriptError('UNKNOWN_ERROR', 'Script number overflow.');
// Ensure minimal serialization.
if (minimal && !ScriptNum.isMinimal(data)) if (minimal && !ScriptNum.isMinimal(data))
throw new ScriptError('UNKNOWN_ERROR', 'Non-minimal script number.'); throw new ScriptError('UNKNOWN_ERROR', 'Non-minimal script number.');
@ -234,37 +223,13 @@ ScriptNum.isMinimal = function isMinimal(data) {
if (data.length === 1) if (data.length === 1)
return false; return false;
if (!(data[data.length - 2] & 0x80)) if ((data[data.length - 2] & 0x80) === 0)
return false; return false;
} }
return true; return true;
}; };
/**
* Encode a script number.
* @param {Number|ScriptNum|BN} num
* @returns {Buffer}
*/
ScriptNum.encode = function encode(num) {
assert(num != null);
if (ScriptNum.isScriptNum(num))
return num.encode();
if (typeof num === 'number')
num = ScriptNum.fromNumber(num);
else if (I64.isN64(num))
num = ScriptNum.fromObject(num);
else if (Array.isArray(num.words))
num = ScriptNum.fromBN(num);
else
throw new Error('Object must be encodable.');
return num.encode();
};
/** /**
* Decode and verify script number. * Decode and verify script number.
* @param {Buffer} data * @param {Buffer} data
@ -277,31 +242,6 @@ ScriptNum.decode = function decode(data, minimal, limit) {
return new ScriptNum().decode(data, minimal, limit); return new ScriptNum().decode(data, minimal, limit);
}; };
/**
* Test whether object is encodable.
* @param {Object} obj
* @returns {Boolean}
*/
ScriptNum.isEncodable = function isEncodable(obj) {
if (obj == null)
return false;
if (ScriptNum.isScriptNum(obj))
return true;
if (typeof obj === 'number')
return true;
if (I64.isN64(obj))
return true;
if (Array.isArray(obj.words))
return true;
return false;
};
/** /**
* Test whether object is a script number. * Test whether object is a script number.
* @param {Object} obj * @param {Object} obj

View File

@ -25,7 +25,7 @@
"dependencies": { "dependencies": {
"bn.js": "4.11.8", "bn.js": "4.11.8",
"elliptic": "6.4.0", "elliptic": "6.4.0",
"n64": "0.0.17" "n64": "0.0.18"
}, },
"optionalDependencies": { "optionalDependencies": {
"bcoin-native": "0.0.23", "bcoin-native": "0.0.23",