685 lines
14 KiB
JavaScript
685 lines
14 KiB
JavaScript
/*!
|
|
* witness.js - witness object for bcoin
|
|
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
|
|
* Copyright (c) 2014-2016, Christopher Jeffrey (MIT License).
|
|
* https://github.com/bcoin-org/bcoin
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
var assert = require('assert');
|
|
var BN = require('bn.js');
|
|
var util = require('../utils/util');
|
|
var Script = require('./script');
|
|
var common = require('./common');
|
|
var encoding = require('../utils/encoding');
|
|
var Opcode = require('./opcode');
|
|
var BufferReader = require('../utils/reader');
|
|
var StaticWriter = require('../utils/staticwriter');
|
|
var Address = require('../primitives/address');
|
|
var Stack = require('./stack');
|
|
var opcodes = common.opcodes;
|
|
var scriptTypes = common.types;
|
|
var STACK_FALSE = common.STACK_FALSE;
|
|
var STACK_NEGATE = common.STACK_NEGATE;
|
|
|
|
/**
|
|
* Refers to the witness field of segregated witness transactions.
|
|
* @exports Witness
|
|
* @constructor
|
|
* @param {Buffer[]|NakedWitness} items - Array of
|
|
* stack items.
|
|
* @property {Buffer[]} items
|
|
* @property {Script?} redeem
|
|
* @property {Number} length
|
|
*/
|
|
|
|
function Witness(options) {
|
|
if (!(this instanceof Witness))
|
|
return new Witness(options);
|
|
|
|
this.items = [];
|
|
|
|
if (options)
|
|
this.fromOptions(options);
|
|
}
|
|
|
|
/**
|
|
* Getter to retrieve items length.
|
|
* @function
|
|
* @name length(get)
|
|
* @memberof Witness#
|
|
* @returns {Number}
|
|
*/
|
|
|
|
Witness.prototype.__defineGetter__('length', function() {
|
|
return this.items.length;
|
|
});
|
|
|
|
/**
|
|
* Setter to set items length.
|
|
* @function
|
|
* @name length(set)
|
|
* @memberof Witness#
|
|
* @returns {Number}
|
|
*/
|
|
|
|
Witness.prototype.__defineSetter__('length', function(length) {
|
|
return this.items.length = length;
|
|
});
|
|
|
|
/**
|
|
* Inject properties from options object.
|
|
* @private
|
|
* @param {Object} options
|
|
*/
|
|
|
|
Witness.prototype.fromOptions = function fromOptions(options) {
|
|
var items;
|
|
|
|
assert(options, 'Witness data is required.');
|
|
|
|
items = options.items;
|
|
|
|
if (!items)
|
|
items = options;
|
|
|
|
if (items)
|
|
this.fromArray(items);
|
|
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Instantiate witness from options.
|
|
* @param {Object} options
|
|
* @returns {Witness}
|
|
*/
|
|
|
|
Witness.fromOptions = function fromOptions(options) {
|
|
return new Witness().fromOptions(options);
|
|
};
|
|
|
|
/**
|
|
* Convert witness to an array of buffers.
|
|
* @returns {Buffer[]}
|
|
*/
|
|
|
|
Witness.prototype.toArray = function toArray() {
|
|
return this.items.slice();
|
|
};
|
|
|
|
/**
|
|
* Inject properties from an array of buffers.
|
|
* @private
|
|
* @param {Buffer[]} items
|
|
*/
|
|
|
|
Witness.prototype.fromArray = function fromArray(items) {
|
|
assert(Array.isArray(items));
|
|
this.items = items;
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Insantiate witness from an array of buffers.
|
|
* @param {Buffer[]} items
|
|
* @returns {Witness}
|
|
*/
|
|
|
|
Witness.fromArray = function fromArray(items) {
|
|
return new Witness().fromArray(items);
|
|
};
|
|
|
|
/**
|
|
* Inspect a Witness object.
|
|
* @returns {String} Human-readable script.
|
|
*/
|
|
|
|
Witness.prototype.inspect = function inspect() {
|
|
return '<Witness: ' + this.toString() + '>';
|
|
};
|
|
|
|
/**
|
|
* Convert a Witness object to a String.
|
|
* @returns {String} Human-readable script.
|
|
*/
|
|
|
|
Witness.prototype.toString = function toString() {
|
|
return common.formatStack(this.items);
|
|
};
|
|
|
|
/**
|
|
* Format the witness object as bitcoind asm.
|
|
* @param {Boolean?} decode - Attempt to decode hash types.
|
|
* @returns {String} Human-readable script.
|
|
*/
|
|
|
|
Witness.prototype.toASM = function toASM(decode) {
|
|
return common.formatStackASM(this.items, decode);
|
|
};
|
|
|
|
/**
|
|
* Clone the witness object.
|
|
* @returns {Witness} A clone of the current witness object.
|
|
*/
|
|
|
|
Witness.prototype.clone = function clone() {
|
|
return new Witness().inject(this);
|
|
};
|
|
|
|
/**
|
|
* Inject properties from witness.
|
|
* Used for cloning.
|
|
* @private
|
|
* @param {Witness} witness
|
|
* @returns {Witness}
|
|
*/
|
|
|
|
Witness.prototype.inject = function inject(witness) {
|
|
this.items = witness.items.slice();
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Convert the Witness to a Stack object.
|
|
* This is usually done before executing
|
|
* a witness program.
|
|
* @returns {Stack}
|
|
*/
|
|
|
|
Witness.prototype.toStack = function toStack() {
|
|
return new Stack(this.items.slice());
|
|
};
|
|
|
|
/**
|
|
* "Guess" the type of the witness.
|
|
* This method is not 100% reliable.
|
|
* @returns {ScriptType}
|
|
*/
|
|
|
|
Witness.prototype.getInputType = function getInputType() {
|
|
if (this.isPubkeyhashInput())
|
|
return scriptTypes.WITNESSPUBKEYHASH;
|
|
|
|
if (this.isScripthashInput())
|
|
return scriptTypes.WITNESSSCRIPTHASH;
|
|
|
|
return scriptTypes.NONSTANDARD;
|
|
};
|
|
|
|
/**
|
|
* "Guess" the address of the witness.
|
|
* This method is not 100% reliable.
|
|
* @returns {Address|null}
|
|
*/
|
|
|
|
Witness.prototype.getInputAddress = function getInputAddress() {
|
|
return Address.fromWitness(this);
|
|
};
|
|
|
|
/**
|
|
* "Test" whether the witness is a pubkey input.
|
|
* Always returns false.
|
|
* @returns {Boolean}
|
|
*/
|
|
|
|
Witness.prototype.isPubkeyInput = function isPubkeyInput() {
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* "Guess" whether the witness is a pubkeyhash input.
|
|
* This method is not 100% reliable.
|
|
* @returns {Boolean}
|
|
*/
|
|
|
|
Witness.prototype.isPubkeyhashInput = function isPubkeyhashInput() {
|
|
return this.items.length === 2
|
|
&& common.isSignatureEncoding(this.items[0])
|
|
&& common.isKeyEncoding(this.items[1]);
|
|
};
|
|
|
|
/**
|
|
* "Test" whether the witness is a multisig input.
|
|
* Always returns false.
|
|
* @returns {Boolean}
|
|
*/
|
|
|
|
Witness.prototype.isMultisigInput = function isMultisigInput() {
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* "Guess" whether the witness is a scripthash input.
|
|
* This method is not 100% reliable.
|
|
* @returns {Boolean}
|
|
*/
|
|
|
|
Witness.prototype.isScripthashInput = function isScripthashInput() {
|
|
return this.items.length > 0 && !this.isPubkeyhashInput();
|
|
};
|
|
|
|
/**
|
|
* "Guess" whether the witness is an unknown/non-standard type.
|
|
* This method is not 100% reliable.
|
|
* @returns {Boolean}
|
|
*/
|
|
|
|
Witness.prototype.isUnknownInput = function isUnknownInput() {
|
|
return this.getInputType() === scriptTypes.NONSTANDARD;
|
|
};
|
|
|
|
/**
|
|
* Test the witness against a bloom filter.
|
|
* @param {Bloom} filter
|
|
* @returns {Boolean}
|
|
*/
|
|
|
|
Witness.prototype.test = function test(filter) {
|
|
var i, chunk;
|
|
|
|
for (i = 0; i < this.items.length; i++) {
|
|
chunk = this.items[i];
|
|
|
|
if (chunk.length === 0)
|
|
continue;
|
|
|
|
if (filter.test(chunk))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* Grab and deserialize the redeem script from the witness.
|
|
* @returns {Script} Redeem script.
|
|
*/
|
|
|
|
Witness.prototype.getRedeem = function getRedeem() {
|
|
var redeem;
|
|
|
|
if (this.items.length === 0)
|
|
return;
|
|
|
|
redeem = this.items[this.items.length - 1];
|
|
|
|
if (!redeem)
|
|
return;
|
|
|
|
return Script.fromRaw(redeem);
|
|
};
|
|
|
|
/**
|
|
* Does nothing currently.
|
|
*/
|
|
|
|
Witness.prototype.compile = function compile() {
|
|
// NOP
|
|
};
|
|
|
|
/**
|
|
* Find a data element in a witness.
|
|
* @param {Buffer} data - Data element to match against.
|
|
* @returns {Number} Index (`-1` if not present).
|
|
*/
|
|
|
|
Witness.prototype.indexOf = function indexOf(data) {
|
|
return util.indexOf(this.items, data);
|
|
};
|
|
|
|
/**
|
|
* Calculate size of the witness
|
|
* excluding the varint size bytes.
|
|
* @returns {Number}
|
|
*/
|
|
|
|
Witness.prototype.getSize = function getSize() {
|
|
var size = 0;
|
|
var i, item;
|
|
|
|
for (i = 0; i < this.items.length; i++) {
|
|
item = this.items[i];
|
|
size += encoding.sizeVarBytes(item);
|
|
}
|
|
|
|
return size;
|
|
};
|
|
|
|
/**
|
|
* Calculate size of the witness
|
|
* including the varint size bytes.
|
|
* @returns {Number}
|
|
*/
|
|
|
|
Witness.prototype.getVarSize = function getVarSize() {
|
|
return encoding.sizeVarint(this.items.length) + this.getSize();
|
|
};
|
|
|
|
/**
|
|
* Write witness to a buffer writer.
|
|
* @param {BufferWriter} bw
|
|
*/
|
|
|
|
Witness.prototype.toWriter = function toWriter(bw) {
|
|
var i;
|
|
|
|
bw.writeVarint(this.items.length);
|
|
|
|
for (i = 0; i < this.items.length; i++)
|
|
bw.writeVarBytes(this.items[i]);
|
|
|
|
return bw;
|
|
};
|
|
|
|
/**
|
|
* Encode the witness to a Buffer.
|
|
* @param {String} enc - Encoding, either `'hex'` or `null`.
|
|
* @returns {Buffer|String} Serialized script.
|
|
*/
|
|
|
|
Witness.prototype.toRaw = function toRaw() {
|
|
var size = this.getVarSize();
|
|
return this.toWriter(new StaticWriter(size)).render();
|
|
};
|
|
|
|
/**
|
|
* Convert witness to a hex string.
|
|
* @returns {String}
|
|
*/
|
|
|
|
Witness.prototype.toJSON = function toJSON() {
|
|
return this.toRaw().toString('hex');
|
|
};
|
|
|
|
/**
|
|
* Inject properties from json object.
|
|
* @private
|
|
* @param {String} json
|
|
*/
|
|
|
|
Witness.prototype.fromJSON = function fromJSON(json) {
|
|
assert(typeof json === 'string', 'Witness must be a string.');
|
|
return this.fromRaw(new Buffer(json, 'hex'));
|
|
};
|
|
|
|
/**
|
|
* Insantiate witness from a hex string.
|
|
* @param {String} json
|
|
* @returns {Witness}
|
|
*/
|
|
|
|
Witness.fromJSON = function fromJSON(json) {
|
|
return new Witness().fromJSON(json);
|
|
};
|
|
|
|
/**
|
|
* Unshift an item onto the witness vector.
|
|
* @param {Number|String|Buffer|BN} data
|
|
* @returns {Number}
|
|
*/
|
|
|
|
Witness.prototype.unshift = function unshift(data) {
|
|
return this.items.unshift(Witness.encodeItem(data));
|
|
};
|
|
|
|
/**
|
|
* Push an item onto the witness vector.
|
|
* @param {Number|String|Buffer|BN} data
|
|
* @returns {Number}
|
|
*/
|
|
|
|
Witness.prototype.push = function push(data) {
|
|
return this.items.push(Witness.encodeItem(data));
|
|
};
|
|
|
|
/**
|
|
* Shift an item off the witness vector.
|
|
* @returns {Buffer}
|
|
*/
|
|
|
|
Witness.prototype.shift = function shift() {
|
|
return this.items.shift();
|
|
};
|
|
|
|
/**
|
|
* Shift an item off the witness vector.
|
|
* @returns {Buffer}
|
|
*/
|
|
|
|
Witness.prototype.pop = function push(data) {
|
|
return this.items.pop();
|
|
};
|
|
|
|
/**
|
|
* Remove an item from the witness vector.
|
|
* @param {Number} index
|
|
* @returns {Buffer}
|
|
*/
|
|
|
|
Witness.prototype.remove = function remove(i) {
|
|
return this.items.splice(i, 1)[0];
|
|
};
|
|
|
|
/**
|
|
* Insert an item into the witness vector.
|
|
* @param {Number} index
|
|
* @param {Number|String|Buffer|BN} data
|
|
*/
|
|
|
|
Witness.prototype.insert = function insert(i, data) {
|
|
assert(i <= this.items.length, 'Index out of bounds.');
|
|
this.items.splice(i, 0, Witness.encodeItem(data))[0];
|
|
};
|
|
|
|
/**
|
|
* Get an item from the witness vector.
|
|
* @param {Number} index
|
|
* @returns {Buffer}
|
|
*/
|
|
|
|
Witness.prototype.get = function get(i) {
|
|
return this.items[i];
|
|
};
|
|
|
|
/**
|
|
* Get a small int (0-16) from the witness vector.
|
|
* @param {Number} index
|
|
* @returns {Number} `-1` on non-existent.
|
|
*/
|
|
|
|
Witness.prototype.getSmall = function getSmall(i) {
|
|
var item = this.items[i];
|
|
if (!item || item.length > 1)
|
|
return -1;
|
|
if (item.length === 0)
|
|
return 0;
|
|
if (!(item[0] >= 1 && item[1] <= 16))
|
|
return -1;
|
|
return item[0];
|
|
};
|
|
|
|
/**
|
|
* Get a number from the witness vector.
|
|
* @param {Number} index
|
|
* @returns {BN}
|
|
*/
|
|
|
|
Witness.prototype.getNumber = function getNumber(i) {
|
|
var item = this.items[i];
|
|
if (!item || item.length > 5)
|
|
return;
|
|
return common.num(item, common.flags.VERIFY_NONE, 5);
|
|
};
|
|
|
|
/**
|
|
* Get a string from the witness vector.
|
|
* @param {Number} index
|
|
* @returns {String}
|
|
*/
|
|
|
|
Witness.prototype.getString = function getString(i) {
|
|
var item = this.items[i];
|
|
if (!item)
|
|
return;
|
|
return item.toString('utf8');
|
|
};
|
|
|
|
/**
|
|
* Clear the witness items.
|
|
*/
|
|
|
|
Witness.prototype.clear = function clear() {
|
|
this.items.length = 0;
|
|
};
|
|
|
|
/**
|
|
* Set an item in the witness vector.
|
|
* @param {Number} index
|
|
* @param {Number|String|Buffer|BN} data
|
|
*/
|
|
|
|
Witness.prototype.set = function set(i, data) {
|
|
assert(i <= this.items.length, 'Index out of bounds.');
|
|
this.items[i] = Witness.encodeItem(data);
|
|
};
|
|
|
|
/**
|
|
* Encode a witness item.
|
|
* @param {Number|String|Buffer|BN} data
|
|
* @returns {Buffer}
|
|
*/
|
|
|
|
Witness.encodeItem = function encodeItem(data) {
|
|
if (data instanceof Opcode)
|
|
data = data.data || data.value;
|
|
|
|
if (typeof data === 'number') {
|
|
if (data === opcodes.OP_1NEGATE)
|
|
return STACK_NEGATE;
|
|
|
|
if (data === opcodes.OP_0)
|
|
return STACK_FALSE;
|
|
|
|
if (data >= opcodes.OP_1 && data <= opcodes.OP_16)
|
|
return new Buffer([data - 0x50]);
|
|
|
|
throw new Error('Non-push opcode in witness.');
|
|
}
|
|
|
|
if (BN.isBN(data))
|
|
return common.array(data);
|
|
|
|
if (typeof data === 'string')
|
|
return new Buffer(data, 'utf8');
|
|
|
|
return data;
|
|
};
|
|
|
|
/**
|
|
* Inject properties from buffer reader.
|
|
* @private
|
|
* @param {BufferReader} br
|
|
*/
|
|
|
|
Witness.prototype.fromReader = function fromReader(br) {
|
|
var chunkCount = br.readVarint();
|
|
var i;
|
|
|
|
for (i = 0; i < chunkCount; i++)
|
|
this.items.push(br.readVarBytes());
|
|
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Inject properties from serialized data.
|
|
* @private
|
|
* @param {Buffer} data
|
|
*/
|
|
|
|
Witness.prototype.fromRaw = function fromRaw(data) {
|
|
return this.fromReader(new BufferReader(data));
|
|
};
|
|
|
|
/**
|
|
* Create a witness from a buffer reader.
|
|
* @param {BufferReader} br
|
|
*/
|
|
|
|
Witness.fromReader = function fromReader(br) {
|
|
return new Witness().fromReader(br);
|
|
};
|
|
|
|
/**
|
|
* Create a witness from a serialized buffer.
|
|
* @param {Buffer|String} data - Serialized witness.
|
|
* @param {String?} enc - Either `"hex"` or `null`.
|
|
* @returns {Witness}
|
|
*/
|
|
|
|
Witness.fromRaw = function fromRaw(data, enc) {
|
|
if (typeof data === 'string')
|
|
data = new Buffer(data, enc);
|
|
return new Witness().fromRaw(data);
|
|
};
|
|
|
|
/**
|
|
* Inject items from string.
|
|
* @private
|
|
* @param {String|String[]} items
|
|
*/
|
|
|
|
Witness.prototype.fromString = function fromString(items) {
|
|
var i;
|
|
|
|
if (!Array.isArray(items)) {
|
|
assert(typeof items === 'string');
|
|
|
|
items = items.trim();
|
|
|
|
if (items.length === 0)
|
|
return this;
|
|
|
|
items = items.split(/\s+/);
|
|
}
|
|
|
|
for (i = 0; i < items.length; i++)
|
|
this.items.push(new Buffer(items[i], 'hex'));
|
|
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Parse a test script/array
|
|
* string into a witness object. _Must_
|
|
* contain only stack items (no non-push
|
|
* opcodes).
|
|
* @param {String|String[]} items - Script string.
|
|
* @returns {Witness}
|
|
* @throws Parse error.
|
|
*/
|
|
|
|
Witness.fromString = function fromString(items) {
|
|
return new Witness().fromString(items);
|
|
};
|
|
|
|
/**
|
|
* Test an object to see if it is a Witness.
|
|
* @param {Object} obj
|
|
* @returns {Boolean}
|
|
*/
|
|
|
|
Witness.isWitness = function isWitness(obj) {
|
|
return obj
|
|
&& Array.isArray(obj.items)
|
|
&& typeof obj.toStack === 'function';
|
|
};
|
|
|
|
/*
|
|
* Expose
|
|
*/
|
|
|
|
module.exports = Witness;
|