script: classify.

This commit is contained in:
Christopher Jeffrey 2017-11-16 18:44:38 -08:00
parent 91ee6077b1
commit 51e3341252
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
9 changed files with 5483 additions and 5440 deletions

View File

@ -484,7 +484,7 @@ exports.isHashType = function isHashType(sig) {
const type = sig[sig.length - 1] & ~exports.hashType.ANYONECANPAY;
if (!(type >= exports.hashType.ALL && type <= exports.hashType.SINGLE))
if (type < exports.hashType.ALL || type > exports.hashType.SINGLE)
return false;
return true;

File diff suppressed because it is too large Load Diff

View File

@ -13,91 +13,92 @@ const scriptTypes = common.types;
/**
* Witness Program
* @constructor
* @alias module:script.Program
* @param {Number} version
* @param {Buffer} data
* @property {Number} version - Ranges from 0 to 16.
* @property {String|null} type - Null if malformed. `unknown` if unknown
* version (treated as anyone-can-spend). Otherwise one of `witnesspubkeyhash`
* or `witnessscripthash`.
* @property {String|null} type - Null if malformed.
* @property {Buffer} data - The hash (for now).
*/
function Program(version, data) {
if (!(this instanceof Program))
return new Program(version, data);
class Program {
/**
* Create a witness program.
* @constructor
* @param {Number} version
* @param {Buffer} data
*/
assert((version & 0xff) === version);
assert(version >= 0 && version <= 16);
assert(Buffer.isBuffer(data));
assert(data.length >= 2 && data.length <= 40);
constructor(version, data) {
assert((version & 0xff) === version);
assert(version >= 0 && version <= 16);
assert(Buffer.isBuffer(data));
assert(data.length >= 2 && data.length <= 40);
this.version = version;
this.data = data;
this.version = version;
this.data = data;
}
/**
* Get the witness program type.
* @returns {ScriptType}
*/
getType() {
if (this.version === 0) {
if (this.data.length === 20)
return scriptTypes.WITNESSPUBKEYHASH;
if (this.data.length === 32)
return scriptTypes.WITNESSSCRIPTHASH;
// Fail on bad version=0
return scriptTypes.WITNESSMALFORMED;
}
if (this.version === 1) {
if (this.data.length === 32)
return scriptTypes.WITNESSMASTHASH;
// Fail on bad version=1
return scriptTypes.WITNESSMALFORMED;
}
// No interpretation of script (anyone can spend)
return scriptTypes.NONSTANDARD;
}
/**
* Test whether the program is either
* an unknown version or malformed.
* @returns {Boolean}
*/
isUnknown() {
const type = this.getType();
return type === scriptTypes.WITNESSMALFORMED
|| type === scriptTypes.NONSTANDARD;
}
/**
* Test whether the program is malformed.
* @returns {Boolean}
*/
isMalformed() {
return this.getType() === scriptTypes.WITNESSMALFORMED;
}
/**
* Inspect the program.
* @returns {String}
*/
inspect() {
const data = this.data.toString('hex');
const type = common.typesByVal[this.getType()].toLowerCase();
return `<Program: version=${this.version} data=${data} type=${type}>`;
}
}
/**
* Get the witness program type.
* @returns {ScriptType}
*/
Program.prototype.getType = function getType() {
if (this.version === 0) {
if (this.data.length === 20)
return scriptTypes.WITNESSPUBKEYHASH;
if (this.data.length === 32)
return scriptTypes.WITNESSSCRIPTHASH;
// Fail on bad version=0
return scriptTypes.WITNESSMALFORMED;
}
if (this.version === 1) {
if (this.data.length === 32)
return scriptTypes.WITNESSMASTHASH;
// Fail on bad version=1
return scriptTypes.WITNESSMALFORMED;
}
// No interpretation of script (anyone can spend)
return scriptTypes.NONSTANDARD;
};
/**
* Test whether the program is either
* an unknown version or malformed.
* @returns {Boolean}
*/
Program.prototype.isUnknown = function isUnknown() {
const type = this.getType();
return type === scriptTypes.WITNESSMALFORMED
|| type === scriptTypes.NONSTANDARD;
};
/**
* Test whether the program is malformed.
* @returns {Boolean}
*/
Program.prototype.isMalformed = function isMalformed() {
return this.getType() === scriptTypes.WITNESSMALFORMED;
};
/**
* Inspect the program.
* @returns {String}
*/
Program.prototype.inspect = function inspect() {
const data = this.data.toString('hex');
const type = common.typesByVal[this.getType()].toLowerCase();
return `<Program: version=${this.version} data=${data} type=${type}>`;
};
/*
* Expose
*/

File diff suppressed because it is too large Load Diff

View File

@ -7,44 +7,50 @@
'use strict';
/**
* Script Error
* An error thrown from the scripting system,
* potentially pertaining to Script execution.
* @alias module:script.ScriptError
* @constructor
* @extends Error
* @param {String} code - Error code.
* @param {Opcode} op - Opcode.
* @param {Number?} ip - Instruction pointer.
* @property {String} message - Error message.
* @property {String} code - Original code passed in.
* @property {Number} op - Opcode.
* @property {Number} ip - Instruction pointer.
*/
function ScriptError(code, op, ip) {
if (!(this instanceof ScriptError))
return new ScriptError(code, op, ip);
class ScriptError extends Error {
/**
* Create an error.
* @constructor
* @param {String} code - Error code.
* @param {Opcode} op - Opcode.
* @param {Number?} ip - Instruction pointer.
*/
Error.call(this);
constructor(code, op, ip) {
super();
this.type = 'ScriptError';
this.code = code;
this.message = code;
this.op = -1;
this.ip = -1;
this.type = 'ScriptError';
this.code = code;
this.message = code;
this.op = -1;
this.ip = -1;
if (typeof op === 'string') {
this.message = op;
} else if (op) {
this.message = `${code} (op=${op.toSymbol()}, ip=${ip})`;
this.op = op.value;
this.ip = ip;
if (typeof op === 'string') {
this.message = op;
} else if (op) {
this.message = `${code} (op=${op.toSymbol()}, ip=${ip})`;
this.op = op.value;
this.ip = ip;
}
if (Error.captureStackTrace)
Error.captureStackTrace(this, ScriptError);
}
}
if (Error.captureStackTrace)
Error.captureStackTrace(this, ScriptError);
};
Object.setPrototypeOf(ScriptError.prototype, Error.prototype);
/*
* Expose
*/
module.exports = ScriptError;

View File

@ -20,237 +20,237 @@ const EMPTY_ARRAY = Buffer.alloc(0);
* Script Number
* @see https://github.com/chjj/n64
* @alias module:script.ScriptNum
* @constructor
* @param {(Number|String|Buffer|Object)?} num
* @param {(String|Number)?} base
* @property {Number} hi
* @property {Number} lo
* @property {Number} sign
*/
function ScriptNum(num, base) {
if (!(this instanceof ScriptNum))
return new ScriptNum(num, base);
class ScriptNum extends I64 {
/**
* Create a script number.
* @constructor
* @param {(Number|String|Buffer|Object)?} num
* @param {(String|Number)?} base
*/
I64.call(this, num, base);
}
Object.setPrototypeOf(ScriptNum, I64);
Object.setPrototypeOf(ScriptNum.prototype, I64.prototype);
/**
* Cast to int32.
* @returns {Number}
*/
ScriptNum.prototype.getInt = function getInt() {
if (this.lt(I64.INT32_MIN))
return I64.LONG_MIN;
if (this.gt(I64.INT32_MAX))
return I64.LONG_MAX;
return this.toInt();
};
/**
* Serialize script number.
* @returns {Buffer}
*/
ScriptNum.prototype.toRaw = function toRaw() {
let num = this;
// Zeroes are always empty arrays.
if (num.isZero())
return EMPTY_ARRAY;
// Need to append sign bit.
let neg = false;
if (num.isNeg()) {
num = num.neg();
neg = true;
constructor(num, base) {
super(num, base);
}
// Calculate size.
const size = num.byteLength();
/**
* Cast to int32.
* @returns {Number}
*/
let offset = 0;
getInt() {
if (this.lt(I64.INT32_MIN))
return I64.LONG_MIN;
if (num.testn((size * 8) - 1))
offset = 1;
if (this.gt(I64.INT32_MAX))
return I64.LONG_MAX;
// Write number.
const data = Buffer.allocUnsafe(size + offset);
switch (size) {
case 8:
data[7] = (num.hi >>> 24) & 0xff;
case 7:
data[6] = (num.hi >> 16) & 0xff;
case 6:
data[5] = (num.hi >> 8) & 0xff;
case 5:
data[4] = num.hi & 0xff;
case 4:
data[3] = (num.lo >>> 24) & 0xff;
case 3:
data[2] = (num.lo >> 16) & 0xff;
case 2:
data[1] = (num.lo >> 8) & 0xff;
case 1:
data[0] = num.lo & 0xff;
return this.toInt();
}
// Append sign bit.
if (data[size - 1] & 0x80) {
assert(offset === 1);
assert(data.length === size + offset);
data[size] = neg ? 0x80 : 0;
} else if (neg) {
assert(offset === 0);
assert(data.length === size);
data[size - 1] |= 0x80;
} else {
assert(offset === 0);
assert(data.length === size);
/**
* Serialize script number.
* @returns {Buffer}
*/
toRaw() {
let num = this;
// Zeroes are always empty arrays.
if (num.isZero())
return EMPTY_ARRAY;
// Need to append sign bit.
let neg = false;
if (num.isNeg()) {
num = num.neg();
neg = true;
}
// Calculate size.
const size = num.byteLength();
let offset = 0;
if (num.testn((size * 8) - 1))
offset = 1;
// Write number.
const data = Buffer.allocUnsafe(size + offset);
switch (size) {
case 8:
data[7] = (num.hi >>> 24) & 0xff;
case 7:
data[6] = (num.hi >> 16) & 0xff;
case 6:
data[5] = (num.hi >> 8) & 0xff;
case 5:
data[4] = num.hi & 0xff;
case 4:
data[3] = (num.lo >>> 24) & 0xff;
case 3:
data[2] = (num.lo >> 16) & 0xff;
case 2:
data[1] = (num.lo >> 8) & 0xff;
case 1:
data[0] = num.lo & 0xff;
}
// Append sign bit.
if (data[size - 1] & 0x80) {
assert(offset === 1);
assert(data.length === size + offset);
data[size] = neg ? 0x80 : 0;
} else if (neg) {
assert(offset === 0);
assert(data.length === size);
data[size - 1] |= 0x80;
} else {
assert(offset === 0);
assert(data.length === size);
}
return data;
}
return data;
};
/**
* Instantiate script number from serialized data.
* @private
* @param {Buffer} data
* @returns {ScriptNum}
*/
/**
* Instantiate script number from serialized data.
* @private
* @param {Buffer} data
* @returns {ScriptNum}
*/
fromRaw(data) {
assert(Buffer.isBuffer(data));
ScriptNum.prototype.fromRaw = function fromRaw(data) {
assert(Buffer.isBuffer(data));
// Empty arrays are always zero.
if (data.length === 0)
return this;
// Read number (9 bytes max).
switch (data.length) {
case 8:
this.hi |= data[7] << 24;
case 7:
this.hi |= data[6] << 16;
case 6:
this.hi |= data[5] << 8;
case 5:
this.hi |= data[4];
case 4:
this.lo |= data[3] << 24;
case 3:
this.lo |= data[2] << 16;
case 2:
this.lo |= data[1] << 8;
case 1:
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.
if (data[data.length - 1] & 0x80) {
this.setn((data.length * 8) - 1, 0);
this.ineg();
}
// Empty arrays are always zero.
if (data.length === 0)
return this;
// Read number (9 bytes max).
switch (data.length) {
case 8:
this.hi |= data[7] << 24;
case 7:
this.hi |= data[6] << 16;
case 6:
this.hi |= data[5] << 8;
case 5:
this.hi |= data[4];
case 4:
this.lo |= data[3] << 24;
case 3:
this.lo |= data[2] << 16;
case 2:
this.lo |= data[1] << 8;
case 1:
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.
if (data[data.length - 1] & 0x80) {
this.setn((data.length * 8) - 1, 0);
this.ineg();
/**
* Serialize script number.
* @returns {Buffer}
*/
encode() {
return this.toRaw();
}
return this;
};
/**
* Decode and verify script number.
* @private
* @param {Buffer} data
* @param {Boolean?} minimal - Require minimal encoding.
* @param {Number?} limit - Size limit.
* @returns {ScriptNum}
*/
/**
* Serialize script number.
* @returns {Buffer}
*/
decode(data, minimal, limit) {
assert(Buffer.isBuffer(data));
ScriptNum.prototype.encode = function encode() {
return this.toRaw();
};
if (limit != null && data.length > limit)
throw new ScriptError('UNKNOWN_ERROR', 'Script number overflow.');
/**
* Decode and verify script number.
* @private
* @param {Buffer} data
* @param {Boolean?} minimal - Require minimal encoding.
* @param {Number?} limit - Size limit.
* @returns {ScriptNum}
*/
if (minimal && !ScriptNum.isMinimal(data))
throw new ScriptError('UNKNOWN_ERROR', 'Non-minimal script number.');
ScriptNum.prototype.decode = function decode(data, minimal, limit) {
assert(Buffer.isBuffer(data));
return this.fromRaw(data);
}
if (limit != null && data.length > limit)
throw new ScriptError('UNKNOWN_ERROR', 'Script number overflow.');
/**
* Inspect script number.
* @returns {String}
*/
if (minimal && !ScriptNum.isMinimal(data))
throw new ScriptError('UNKNOWN_ERROR', 'Non-minimal script number.');
inspect() {
return `<ScriptNum: ${this.toString(10)}>`;
}
return this.fromRaw(data);
};
/**
* Test wether a serialized script
* number is in its most minimal form.
* @param {Buffer} data
* @returns {Boolean}
*/
/**
* Inspect script number.
* @returns {String}
*/
static isMinimal(data) {
assert(Buffer.isBuffer(data));
ScriptNum.prototype.inspect = function inspect() {
return `<ScriptNum: ${this.toString(10)}>`;
};
if (data.length === 0)
return true;
/**
* Test wether a serialized script
* number is in its most minimal form.
* @param {Buffer} data
* @returns {Boolean}
*/
if ((data[data.length - 1] & 0x7f) === 0) {
if (data.length === 1)
return false;
ScriptNum.isMinimal = function isMinimal(data) {
assert(Buffer.isBuffer(data));
if ((data[data.length - 2] & 0x80) === 0)
return false;
}
if (data.length === 0)
return true;
if ((data[data.length - 1] & 0x7f) === 0) {
if (data.length === 1)
return false;
if ((data[data.length - 2] & 0x80) === 0)
return false;
}
return true;
};
/**
* Decode and verify script number.
* @param {Buffer} data
* @param {Boolean?} minimal - Require minimal encoding.
* @param {Number?} limit - Size limit.
* @returns {ScriptNum}
*/
/**
* Decode and verify script number.
* @param {Buffer} data
* @param {Boolean?} minimal - Require minimal encoding.
* @param {Number?} limit - Size limit.
* @returns {ScriptNum}
*/
static decode(data, minimal, limit) {
return new this().decode(data, minimal, limit);
}
ScriptNum.decode = function decode(data, minimal, limit) {
return new ScriptNum().decode(data, minimal, limit);
};
/**
* Test whether object is a script number.
* @param {Object} obj
* @returns {Boolean}
*/
/**
* Test whether object is a script number.
* @param {Object} obj
* @returns {Boolean}
*/
ScriptNum.isScriptNum = function isScriptNum(obj) {
return obj instanceof ScriptNum;
};
static isScriptNum(obj) {
return obj instanceof ScriptNum;
}
}
/*
* Expose

View File

@ -12,135 +12,144 @@ const secp256k1 = require('bcrypto/lib/secp256k1');
/**
* Signature cache.
* @alias module:script.SigCache
* @constructor
* @param {Number} [size=10000]
* @property {Number} size
* @property {Hash[]} keys
* @property {Object} valid
*/
function SigCache(size) {
if (!(this instanceof SigCache))
return new SigCache(size);
class SigCache {
/**
* Create a signature cache.
* @constructor
* @param {Number} [size=10000]
*/
if (size == null)
size = 10000;
constructor(size) {
if (size == null)
size = 10000;
assert((size >>> 0) === size);
assert((size >>> 0) === size);
this.size = size;
this.keys = [];
this.valid = new Map();
this.size = size;
this.keys = [];
this.valid = new Map();
}
/**
* Resize the sigcache.
* @param {Number} size
*/
resize(size) {
assert((size >>> 0) === size);
this.size = size;
this.keys.length = 0;
this.valid.clear();
}
/**
* Add item to the sigcache.
* Potentially evict a random member.
* @param {Hash} hash - Sig hash.
* @param {Buffer} sig
* @param {Buffer} key
*/
add(hash, sig, key) {
if (this.size === 0)
return;
this.valid.set(hash, new SigCacheEntry(sig, key));
if (this.keys.length >= this.size) {
const i = Math.floor(Math.random() * this.keys.length);
const k = this.keys[i];
this.valid.delete(k);
this.keys[i] = hash;
} else {
this.keys.push(hash);
}
}
/**
* Test whether the sig exists.
* @param {Hash} hash - Sig hash.
* @param {Buffer} sig
* @param {Buffer} key
* @returns {Boolean}
*/
has(hash, sig, key) {
const entry = this.valid.get(hash);
if (!entry)
return false;
return entry.equals(sig, key);
}
/**
* Verify a signature, testing
* it against the cache first.
* @param {Buffer} msg
* @param {Buffer} sig
* @param {Buffer} key
* @returns {Boolean}
*/
verify(msg, sig, key) {
if (this.size === 0)
return secp256k1.verify(msg, sig, key);
const hash = msg.toString('hex');
if (this.has(hash, sig, key))
return true;
const result = secp256k1.verify(msg, sig, key);
if (!result)
return false;
this.add(hash, sig, key);
return true;
}
}
/**
* Resize the sigcache.
* @param {Number} size
*/
SigCache.prototype.resize = function resize(size) {
assert((size >>> 0) === size);
this.size = size;
this.keys.length = 0;
this.valid.clear();
};
/**
* Add item to the sigcache.
* Potentially evict a random member.
* @param {Hash} hash - Sig hash.
* @param {Buffer} sig
* @param {Buffer} key
*/
SigCache.prototype.add = function add(hash, sig, key) {
if (this.size === 0)
return;
this.valid.set(hash, new SigCacheEntry(sig, key));
if (this.keys.length >= this.size) {
const i = Math.floor(Math.random() * this.keys.length);
const k = this.keys[i];
this.valid.delete(k);
this.keys[i] = hash;
} else {
this.keys.push(hash);
}
};
/**
* Test whether the sig exists.
* @param {Hash} hash - Sig hash.
* @param {Buffer} sig
* @param {Buffer} key
* @returns {Boolean}
*/
SigCache.prototype.has = function has(hash, sig, key) {
const entry = this.valid.get(hash);
if (!entry)
return false;
return entry.equals(sig, key);
};
/**
* Verify a signature, testing
* it against the cache first.
* @param {Buffer} msg
* @param {Buffer} sig
* @param {Buffer} key
* @returns {Boolean}
*/
SigCache.prototype.verify = function verify(msg, sig, key) {
if (this.size === 0)
return secp256k1.verify(msg, sig, key);
const hash = msg.toString('hex');
if (this.has(hash, sig, key))
return true;
const result = secp256k1.verify(msg, sig, key);
if (!result)
return false;
this.add(hash, sig, key);
return true;
};
/**
* Signature cache entry.
* @constructor
* Signature Cache Entry
* @ignore
* @param {Buffer} sig
* @param {Buffer} key
* @property {Buffer} sig
* @property {Buffer} key
*/
function SigCacheEntry(sig, key) {
this.sig = Buffer.from(sig);
this.key = Buffer.from(key);
class SigCacheEntry {
/**
* Create a cache entry.
* @constructor
* @param {Buffer} sig
* @param {Buffer} key
*/
constructor(sig, key) {
this.sig = Buffer.from(sig);
this.key = Buffer.from(key);
}
/**
* Compare an entry to a sig and key.
* @param {Buffer} sig
* @param {Buffer} key
* @returns {Boolean}
*/
equals(sig, key) {
return this.sig.equals(sig) && this.key.equals(key);
}
}
/**
* Compare an entry to a sig and key.
* @param {Buffer} sig
* @param {Buffer} key
* @returns {Boolean}
*/
SigCacheEntry.prototype.equals = function equals(sig, key) {
return this.sig.equals(sig) && this.key.equals(key);
};
/*
* Expose
*/

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff