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; 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 false;
return true; return true;

File diff suppressed because it is too large Load Diff

View File

@ -13,91 +13,92 @@ const scriptTypes = common.types;
/** /**
* Witness Program * Witness Program
* @constructor
* @alias module:script.Program * @alias module:script.Program
* @param {Number} version
* @param {Buffer} data
* @property {Number} version - Ranges from 0 to 16. * @property {Number} version - Ranges from 0 to 16.
* @property {String|null} type - Null if malformed. `unknown` if unknown * @property {String|null} type - Null if malformed.
* version (treated as anyone-can-spend). Otherwise one of `witnesspubkeyhash`
* or `witnessscripthash`.
* @property {Buffer} data - The hash (for now). * @property {Buffer} data - The hash (for now).
*/ */
function Program(version, data) { class Program {
if (!(this instanceof Program)) /**
return new Program(version, data); * Create a witness program.
* @constructor
* @param {Number} version
* @param {Buffer} data
*/
assert((version & 0xff) === version); constructor(version, data) {
assert(version >= 0 && version <= 16); assert((version & 0xff) === version);
assert(Buffer.isBuffer(data)); assert(version >= 0 && version <= 16);
assert(data.length >= 2 && data.length <= 40); assert(Buffer.isBuffer(data));
assert(data.length >= 2 && data.length <= 40);
this.version = version; this.version = version;
this.data = data; 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 * Expose
*/ */

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -12,135 +12,144 @@ const secp256k1 = require('bcrypto/lib/secp256k1');
/** /**
* Signature cache. * Signature cache.
* @alias module:script.SigCache * @alias module:script.SigCache
* @constructor
* @param {Number} [size=10000]
* @property {Number} size * @property {Number} size
* @property {Hash[]} keys * @property {Hash[]} keys
* @property {Object} valid * @property {Object} valid
*/ */
function SigCache(size) { class SigCache {
if (!(this instanceof SigCache)) /**
return new SigCache(size); * Create a signature cache.
* @constructor
* @param {Number} [size=10000]
*/
if (size == null) constructor(size) {
size = 10000; if (size == null)
size = 10000;
assert((size >>> 0) === size); assert((size >>> 0) === size);
this.size = size; this.size = size;
this.keys = []; this.keys = [];
this.valid = new Map(); 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. * Signature Cache Entry
* @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
* @ignore * @ignore
* @param {Buffer} sig
* @param {Buffer} key
* @property {Buffer} sig * @property {Buffer} sig
* @property {Buffer} key * @property {Buffer} key
*/ */
function SigCacheEntry(sig, key) { class SigCacheEntry {
this.sig = Buffer.from(sig); /**
this.key = Buffer.from(key); * 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 * Expose
*/ */

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff