bitcore/lib/script.js

578 lines
16 KiB
JavaScript
Raw Normal View History

2014-11-20 07:52:32 -08:00
'use strict';
2014-12-09 12:30:52 -08:00
var Address = require('./address');
2014-11-21 07:54:56 -08:00
var BufferReader = require('./encoding/bufferreader');
var BufferWriter = require('./encoding/bufferwriter');
2014-12-09 12:30:52 -08:00
var Hash = require('./crypto/hash');
var Hash = require('./crypto/hash');
2014-09-01 15:45:03 -07:00
var Opcode = require('./opcode');
var PublicKey = require('./publickey');
2014-12-09 12:30:52 -08:00
var PublicKey = require('./publickey');
2014-12-10 07:35:01 -08:00
var _ = require('lodash');
var buffer = require('buffer');
var bufferUtil = require('./util/buffer');
var jsUtil = require('./util/js');
2014-09-01 15:45:03 -07:00
2014-12-04 13:39:13 -08:00
/**
* A bitcoin transaction script. Each transaction's inputs and outputs
* has a script that is evaluated to validate it's spending.
*
* See https://en.bitcoin.it/wiki/Script
*
* @constructor
* @param {Object|string|Buffer} [from] optional data to populate script
*/
2014-11-27 12:10:35 -08:00
var Script = function Script(from) {
if (!(this instanceof Script)) {
return new Script(from);
}
2014-09-01 15:45:03 -07:00
this.chunks = [];
if (bufferUtil.isBuffer(from)) {
2014-11-27 13:30:16 -08:00
return Script.fromBuffer(from);
2014-12-09 12:30:52 -08:00
} else if (from instanceof Script) {
return Script.fromBuffer(from.toBuffer());
2014-11-27 12:10:35 -08:00
} else if (typeof from === 'string') {
2014-11-27 13:30:16 -08:00
return Script.fromString(from);
2014-11-27 12:10:35 -08:00
} else if (typeof from !== 'undefined') {
this.set(from);
2014-09-01 15:45:03 -07:00
}
};
Script.prototype.set = function(obj) {
this.chunks = obj.chunks || this.chunks;
return this;
};
2014-11-27 12:10:35 -08:00
Script.fromBuffer = function(buffer) {
var script = new Script();
script.chunks = [];
2014-11-27 12:10:35 -08:00
var br = new BufferReader(buffer);
while (!br.eof()) {
var opcodenum = br.readUInt8();
var len, buf;
if (opcodenum > 0 && opcodenum < Opcode.map.OP_PUSHDATA1) {
len = opcodenum;
2014-11-27 12:10:35 -08:00
script.chunks.push({
2014-09-17 15:49:45 -07:00
buf: br.read(len),
len: len,
opcodenum: opcodenum
});
} else if (opcodenum === Opcode.map.OP_PUSHDATA1) {
len = br.readUInt8();
2014-11-27 12:10:35 -08:00
buf = br.read(len);
script.chunks.push({
buf: buf,
len: len,
opcodenum: opcodenum
});
} else if (opcodenum === Opcode.map.OP_PUSHDATA2) {
len = br.readUInt16LE();
2014-09-17 15:49:45 -07:00
buf = br.read(len);
2014-11-27 12:10:35 -08:00
script.chunks.push({
buf: buf,
len: len,
opcodenum: opcodenum
});
} else if (opcodenum === Opcode.map.OP_PUSHDATA4) {
len = br.readUInt32LE();
2014-09-17 15:49:45 -07:00
buf = br.read(len);
2014-11-27 12:10:35 -08:00
script.chunks.push({
buf: buf,
len: len,
opcodenum: opcodenum
});
} else {
2014-11-27 12:10:35 -08:00
script.chunks.push(opcodenum);
}
}
2014-11-27 12:10:35 -08:00
return script;
};
Script.prototype.toBuffer = function() {
var bw = new BufferWriter();
2014-09-01 18:01:17 -07:00
for (var i = 0; i < this.chunks.length; i++) {
var chunk = this.chunks[i];
2014-11-27 12:10:35 -08:00
var opcodenum;
2014-09-01 18:01:17 -07:00
if (typeof chunk === 'number') {
2014-11-27 12:10:35 -08:00
opcodenum = chunk;
2014-09-01 18:01:17 -07:00
bw.writeUInt8(opcodenum);
} else {
2014-11-27 12:10:35 -08:00
opcodenum = chunk.opcodenum;
2014-09-01 18:01:17 -07:00
bw.writeUInt8(chunk.opcodenum);
if (opcodenum < Opcode.map.OP_PUSHDATA1) {
bw.write(chunk.buf);
2014-11-27 12:10:35 -08:00
} else if (opcodenum === Opcode.map.OP_PUSHDATA1) {
2014-09-01 18:01:17 -07:00
bw.writeUInt8(chunk.len);
bw.write(chunk.buf);
2014-11-27 12:10:35 -08:00
} else if (opcodenum === Opcode.map.OP_PUSHDATA2) {
2014-09-01 18:01:17 -07:00
bw.writeUInt16LE(chunk.len);
bw.write(chunk.buf);
2014-11-27 12:10:35 -08:00
} else if (opcodenum === Opcode.map.OP_PUSHDATA4) {
2014-09-01 18:01:17 -07:00
bw.writeUInt32LE(chunk.len);
bw.write(chunk.buf);
}
}
}
2014-09-01 18:01:17 -07:00
return bw.concat();
};
2014-11-27 13:30:16 -08:00
Script.fromString = function(str) {
if (jsUtil.isHexa(str)) {
return new Script(new buffer.Buffer(str, 'hex'));
}
2014-11-27 13:30:16 -08:00
var script = new Script();
script.chunks = [];
var tokens = str.split(' ');
var i = 0;
while (i < tokens.length) {
var token = tokens[i];
var opcode = Opcode(token);
var opcodenum = opcode.toNumber();
if (typeof opcodenum === 'undefined') {
opcodenum = parseInt(token);
if (opcodenum > 0 && opcodenum < Opcode.map.OP_PUSHDATA1) {
2014-11-27 13:30:16 -08:00
script.chunks.push({
buf: new Buffer(tokens[i + 1].slice(2), 'hex'),
len: opcodenum,
opcodenum: opcodenum
});
i = i + 2;
2014-11-27 12:10:35 -08:00
} else {
2014-12-01 06:49:44 -08:00
throw new Error('Invalid script: ' + JSON.stringify(str));
}
2014-11-28 06:48:06 -08:00
} else if (opcodenum === Opcode.map.OP_PUSHDATA1 ||
opcodenum === Opcode.map.OP_PUSHDATA2 ||
opcodenum === Opcode.map.OP_PUSHDATA4) {
2014-11-27 12:10:35 -08:00
if (tokens[i + 2].slice(0, 2) !== '0x') {
throw new Error('Pushdata data must start with 0x');
2014-11-27 12:10:35 -08:00
}
2014-11-27 13:30:16 -08:00
script.chunks.push({
buf: new Buffer(tokens[i + 2].slice(2), 'hex'),
len: parseInt(tokens[i + 1]),
opcodenum: opcodenum
});
i = i + 3;
} else {
2014-11-27 13:30:16 -08:00
script.chunks.push(opcodenum);
i = i + 1;
}
}
2014-11-27 13:30:16 -08:00
return script;
};
2014-09-01 18:31:02 -07:00
Script.prototype.toString = function() {
2014-11-27 12:10:35 -08:00
var str = '';
2014-09-01 18:31:02 -07:00
for (var i = 0; i < this.chunks.length; i++) {
var chunk = this.chunks[i];
2014-11-27 12:10:35 -08:00
var opcodenum;
2014-09-01 18:31:02 -07:00
if (typeof chunk === 'number') {
2014-11-27 12:10:35 -08:00
opcodenum = chunk;
str = str + Opcode(opcodenum).toString() + ' ';
2014-09-01 18:31:02 -07:00
} else {
2014-11-27 12:10:35 -08:00
opcodenum = chunk.opcodenum;
if (opcodenum === Opcode.map.OP_PUSHDATA1 ||
opcodenum === Opcode.map.OP_PUSHDATA2 ||
opcodenum === Opcode.map.OP_PUSHDATA4) {
str = str + Opcode(opcodenum).toString() + ' ';
}
str = str + chunk.len + ' ';
str = str + '0x' + chunk.buf.toString('hex') + ' ';
2014-09-01 18:31:02 -07:00
}
}
return str.substr(0, str.length - 1);
};
2014-11-28 11:19:07 -08:00
// script classification methods
/**
* @returns true if this is a pay to pubkey hash output script
*/
Script.prototype.isPublicKeyHashOut = function() {
2014-11-28 06:45:42 -08:00
return this.chunks[0] === Opcode('OP_DUP').toNumber() &&
2014-11-28 06:08:33 -08:00
this.chunks[1] === Opcode('OP_HASH160').toNumber() &&
this.chunks[2].buf &&
this.chunks[3] === Opcode('OP_EQUALVERIFY').toNumber() &&
2014-11-28 06:45:42 -08:00
this.chunks[4] === Opcode('OP_CHECKSIG').toNumber();
};
2014-11-28 11:19:07 -08:00
/**
* @returns true if this is a pay to public key hash input script
*/
Script.prototype.isPublicKeyHashIn = function() {
2014-11-28 06:45:42 -08:00
return !!(this.chunks.length === 2 &&
2014-11-28 06:08:33 -08:00
this.chunks[0].buf &&
2014-12-01 07:59:56 -08:00
this.chunks[0].buf.length >= 0x47 &&
this.chunks[0].buf.length <= 0x49 &&
2014-11-28 13:58:39 -08:00
this.chunks[1].buf &&
2014-12-04 09:49:20 -08:00
PublicKey.isValid(this.chunks[1].buf));
};
2014-11-28 13:26:05 -08:00
/**
* @returns true if this is a public key output script
*/
Script.prototype.isPublicKeyOut = function() {
2014-12-01 06:49:44 -08:00
return this.chunks.length === 2 &&
bufferUtil.isBuffer(this.chunks[0].buf) &&
2014-12-04 09:49:20 -08:00
PublicKey.isValid(this.chunks[0].buf) &&
2014-12-01 06:49:44 -08:00
this.chunks[1] === Opcode('OP_CHECKSIG').toNumber();
2014-11-28 13:26:05 -08:00
};
/**
* @returns true if this is a pay to public key input script
*/
Script.prototype.isPublicKeyIn = function() {
2014-12-01 06:49:44 -08:00
return this.chunks.length === 1 &&
bufferUtil.isBuffer(this.chunks[0].buf) &&
2014-12-01 06:49:44 -08:00
this.chunks[0].buf.length === 0x47;
2014-11-28 13:26:05 -08:00
};
2014-11-28 11:19:07 -08:00
/**
* @returns true if this is a p2sh output script
*/
Script.prototype.isScriptHashOut = function() {
2014-11-28 06:45:42 -08:00
return this.chunks.length === 3 &&
2014-11-28 06:08:33 -08:00
this.chunks[0] === Opcode('OP_HASH160').toNumber() &&
this.chunks[1].buf &&
this.chunks[1].buf.length === 20 &&
2014-11-28 06:45:42 -08:00
this.chunks[2] === Opcode('OP_EQUAL').toNumber();
};
2014-11-28 11:19:07 -08:00
/**
* @returns true if this is a p2sh input script
* Note that these are frequently indistinguishable from pubkeyhashin
*/
Script.prototype.isScriptHashIn = function() {
2014-11-28 13:58:39 -08:00
if (this.chunks.length === 0) {
return false;
}
2014-11-28 13:44:23 -08:00
var chunk = this.chunks[this.chunks.length - 1];
if (!chunk) {
return false;
}
var scriptBuf = chunk.buf;
if (!scriptBuf) {
return false;
}
var redeemScript = new Script(scriptBuf);
var type = redeemScript.classify();
return type !== Script.types.UNKNOWN;
};
2014-11-28 11:19:07 -08:00
/**
* @returns true if this is a mutlsig output script
*/
Script.prototype.isMultisigOut = function() {
return (this.chunks.length > 3 &&
Opcode.isSmallIntOp(this.chunks[0]) &&
this.chunks.slice(1, this.chunks.length - 2).every(function(obj) {
return obj.buf && bufferUtil.isBuffer(obj.buf);
2014-11-28 11:19:07 -08:00
}) &&
Opcode.isSmallIntOp(this.chunks[this.chunks.length - 2]) &&
this.chunks[this.chunks.length - 1] === Opcode.map.OP_CHECKMULTISIG);
};
2014-11-28 11:57:33 -08:00
/**
* @returns true if this is a mutlsig input script
*/
Script.prototype.isMultisigIn = function() {
return this.chunks[0] === 0 &&
this.chunks.slice(1, this.chunks.length).every(function(obj) {
return obj.buf &&
bufferUtil.isBuffer(obj.buf) &&
2014-11-28 11:57:33 -08:00
obj.buf.length === 0x47;
});
};
2014-11-28 11:19:07 -08:00
/**
* @returns true if this is an OP_RETURN data script
*/
2014-12-04 05:33:09 -08:00
Script.prototype.isDataOut = function() {
2014-11-28 11:19:07 -08:00
return (this.chunks[0] === Opcode('OP_RETURN').toNumber() &&
(this.chunks.length === 1 ||
(this.chunks.length === 2 &&
this.chunks[1].buf &&
this.chunks[1].buf.length <= 40 &&
this.chunks[1].length === this.chunks.len)));
};
2014-12-10 07:35:01 -08:00
/**
* @returns true if the script is only composed of data pushing
* opcodes or small int opcodes (OP_0, OP_1, ..., OP_16)
*/
Script.prototype.isPushOnly = function() {
return _.every(this.chunks, function(chunk) {
var opcodenum = chunk.opcodenum;
return !_.isUndefined(opcodenum) || chunk <= Opcode.map.OP_16;
});
};
2014-11-28 11:19:07 -08:00
2014-11-28 13:26:05 -08:00
Script.types = {};
Script.types.UNKNOWN = 'Unknown';
Script.types.PUBKEY_OUT = 'Pay to public key';
Script.types.PUBKEY_IN = 'Spend from public key';
Script.types.PUBKEYHASH_OUT = 'Pay to public key hash';
Script.types.PUBKEYHASH_IN = 'Spend from public key hash';
Script.types.SCRIPTHASH_OUT = 'Pay to script hash';
Script.types.SCRIPTHASH_IN = 'Spend from script hash';
Script.types.MULTISIG_OUT = 'Pay to multisig';
Script.types.MULTISIG_IN = 'Spend from multisig';
2014-12-04 05:33:09 -08:00
Script.types.DATA_OUT = 'Data push';
2014-11-28 13:26:05 -08:00
Script.identifiers = {};
Script.identifiers.PUBKEY_OUT = Script.prototype.isPublicKeyOut;
Script.identifiers.PUBKEY_IN = Script.prototype.isPublicKeyIn;
Script.identifiers.PUBKEYHASH_OUT = Script.prototype.isPublicKeyHashOut;
Script.identifiers.PUBKEYHASH_IN = Script.prototype.isPublicKeyHashIn;
Script.identifiers.MULTISIG_OUT = Script.prototype.isMultisigOut;
Script.identifiers.MULTISIG_IN = Script.prototype.isMultisigIn;
2014-11-28 13:58:39 -08:00
Script.identifiers.SCRIPTHASH_OUT = Script.prototype.isScriptHashOut;
Script.identifiers.SCRIPTHASH_IN = Script.prototype.isScriptHashIn;
2014-12-04 05:33:09 -08:00
Script.identifiers.DATA_OUT = Script.prototype.isDataOut;
2014-11-28 13:26:05 -08:00
/**
* @returns {object} The Script type if it is a known form,
* or Script.UNKNOWN if it isn't
*/
Script.prototype.classify = function() {
for (var type in Script.identifiers) {
if (Script.identifiers[type].bind(this)()) {
return Script.types[type];
}
}
return Script.types.UNKNOWN;
};
2014-12-02 09:19:56 -08:00
/**
* @returns true if script is one of the known types
*/
Script.prototype.isStandard = function() {
2014-12-09 12:30:52 -08:00
// TODO: Add BIP62 compliance
2014-12-02 09:19:56 -08:00
return this.classify() !== Script.types.UNKNOWN;
};
2014-11-28 11:19:07 -08:00
// Script construction methods
2014-11-28 07:57:19 -08:00
/**
* Adds a script element at the start of the script.
* @param {*} obj a string, number, Opcode, Bufer, or object to add
* @returns {Script} this script instance
*/
Script.prototype.prepend = function(obj) {
this._addByType(obj, true);
return this;
};
/**
* Adds a script element to the end of the script.
*
* @param {*} obj a string, number, Opcode, Bufer, or object to add
* @returns {Script} this script instance
*
*/
2014-11-27 12:10:35 -08:00
Script.prototype.add = function(obj) {
2014-11-28 07:57:19 -08:00
this._addByType(obj, false);
return this;
};
Script.prototype._addByType = function(obj, prepend) {
2014-11-27 12:10:35 -08:00
if (typeof obj === 'string') {
2014-11-28 07:57:19 -08:00
this._addOpcode(obj, prepend);
2014-11-27 12:10:35 -08:00
} else if (typeof obj === 'number') {
2014-11-28 07:57:19 -08:00
this._addOpcode(obj, prepend);
2014-11-27 12:10:35 -08:00
} else if (obj.constructor && obj.constructor.name && obj.constructor.name === 'Opcode') {
2014-11-28 07:57:19 -08:00
this._addOpcode(obj, prepend);
} else if (bufferUtil.isBuffer(obj)) {
2014-11-28 07:57:19 -08:00
this._addBuffer(obj, prepend);
2014-11-27 12:10:35 -08:00
} else if (typeof obj === 'object') {
2014-11-28 07:57:19 -08:00
this._insertAtPosition(obj, prepend);
2014-11-27 12:10:35 -08:00
} else {
throw new Error('Invalid script chunk');
2014-11-27 12:10:35 -08:00
}
};
2014-11-28 07:57:19 -08:00
Script.prototype._insertAtPosition = function(op, prepend) {
if (prepend) {
this.chunks.unshift(op);
} else {
this.chunks.push(op);
}
};
Script.prototype._addOpcode = function(opcode, prepend) {
var op;
2014-11-27 12:10:35 -08:00
if (typeof opcode === 'number') {
2014-11-28 07:57:19 -08:00
op = opcode;
2014-11-27 12:10:35 -08:00
} else if (opcode.constructor && opcode.constructor.name && opcode.constructor.name === 'Opcode') {
2014-11-28 07:57:19 -08:00
op = opcode.toNumber();
2014-11-27 12:10:35 -08:00
} else {
2014-11-28 07:57:19 -08:00
op = Opcode(opcode).toNumber();
2014-11-27 12:10:35 -08:00
}
2014-11-28 07:57:19 -08:00
this._insertAtPosition(op, prepend);
return this;
};
2014-11-28 07:57:19 -08:00
Script.prototype._addBuffer = function(buf, prepend) {
var opcodenum;
var len = buf.length;
2014-12-04 09:49:20 -08:00
if (len === 0) {
return;
} else if (len > 0 && len < Opcode.map.OP_PUSHDATA1) {
opcodenum = len;
} else if (len < Math.pow(2, 8)) {
opcodenum = Opcode.map.OP_PUSHDATA1;
2014-12-04 09:49:20 -08:00
} else if (len < Math.pow(2, 16)) {
opcodenum = Opcode.map.OP_PUSHDATA2;
2014-12-04 09:49:20 -08:00
} else if (len < Math.pow(2, 32)) {
opcodenum = Opcode.map.OP_PUSHDATA4;
} else {
2014-11-27 12:10:35 -08:00
throw new Error('You can\'t push that much data');
}
2014-11-28 07:57:19 -08:00
this._insertAtPosition({
buf: buf,
len: len,
opcodenum: opcodenum
2014-11-28 07:57:19 -08:00
}, prepend);
return this;
};
2014-12-09 12:30:52 -08:00
Script.prototype.removeCodeseparators = function() {
var chunks = [];
for (var i = 0; i < this.chunks.length; i++) {
if (this.chunks[i] !== Opcode.OP_CODESEPARATOR) {
2014-12-09 12:30:52 -08:00
chunks.push(this.chunks[i]);
}
}
this.chunks = chunks;
return this;
};
2014-12-03 15:40:06 -08:00
// high level script builder methods
/**
* @returns a new Multisig output script for given public keys,
* requiring m of those public keys to spend
2014-12-04 13:39:13 -08:00
* @param {PublicKey[]} pubkeys - list of all public keys controlling the output
2014-12-10 07:35:01 -08:00
* @param {number} m - amount of required signatures to spend the output
2014-12-03 15:40:06 -08:00
*/
Script.buildMultisigOut = function(pubkeys, m) {
2014-12-04 05:49:38 -08:00
var s = new Script();
s.add(Opcode.smallInt(m));
2014-12-04 09:49:20 -08:00
for (var i = 0; i < pubkeys.length; i++) {
2014-12-04 05:49:38 -08:00
var pubkey = pubkeys[i];
s.add(pubkey.toBuffer());
}
s.add(Opcode.smallInt(pubkeys.length));
s.add(Opcode('OP_CHECKMULTISIG'));
return s;
2014-12-03 15:40:06 -08:00
};
/**
* @returns a new pay to public key hash output for the given
* address or public key
2014-12-04 13:39:13 -08:00
* @param {(Address|PublicKey)} to - destination address or public key
2014-12-03 15:40:06 -08:00
*/
Script.buildPublicKeyHashOut = function(to) {
if (to instanceof PublicKey) {
to = to.toAddress();
2014-12-09 12:30:52 -08:00
} else if (_.isString(to)) {
to = new Address(to);
}
var s = new Script();
s.add(Opcode('OP_DUP'))
.add(Opcode('OP_HASH160'))
.add(to.hashBuffer)
.add(Opcode('OP_EQUALVERIFY'))
.add(Opcode('OP_CHECKSIG'));
return s;
2014-12-03 15:40:06 -08:00
};
/**
* @returns a new pay to public key output for the given
* public key
*/
Script.buildPublicKeyOut = function(pubkey) {
2014-12-04 09:49:20 -08:00
var s = new Script();
s.add(pubkey.toBuffer())
.add(Opcode('OP_CHECKSIG'));
return s;
2014-12-03 15:40:06 -08:00
};
/**
* @returns a new OP_RETURN script with data
2014-12-04 13:39:13 -08:00
* @param {(string|Buffer)} to - the data to embed in the output
2014-12-03 15:40:06 -08:00
*/
Script.buildDataOut = function(data) {
2014-12-04 09:49:20 -08:00
if (typeof data === 'string') {
data = new Buffer(data);
}
var s = new Script();
s.add(Opcode('OP_RETURN'))
.add(data);
return s;
2014-12-03 15:40:06 -08:00
};
/**
2014-12-10 07:35:01 -08:00
* @param {Script} script - the redeemScript for the new p2sh output
2014-12-09 12:30:52 -08:00
* @returns Script new pay to script hash script for given script
2014-12-03 15:40:06 -08:00
*/
Script.buildScriptHashOut = function(script) {
2014-12-04 10:06:47 -08:00
var s = new Script();
s.add(Opcode('OP_HASH160'))
.add(Hash.sha256ripemd160(script.toBuffer()))
.add(Opcode('OP_EQUAL'));
return s;
2014-12-03 15:40:06 -08:00
};
/**
* Builds a scriptSig (a script for an input) that signs a public key hash
* output script.
*
* @param {Object} signature
* @param {PublicKey} signature.publicKey
* @param {Buffer} signature.signature
* @param {number} signature.sigtype
*/
Script.buildPublicKeyHashIn = function(signature) {
var script = new Script()
.add(bufferUtil.concat([
signature.signature.toDER(),
bufferUtil.integerAsSingleByteBuffer(signature.sigtype)
]))
.add(signature.publicKey.toBuffer());
return script;
};
2014-12-09 12:30:52 -08:00
/**
* @returns Script an empty script
*/
Script.empty = function() {
return new Script();
};
2014-12-03 15:40:06 -08:00
/**
2014-12-09 12:30:52 -08:00
* @returns Script a new pay to script hash script that pays to this script
2014-12-03 15:40:06 -08:00
*/
Script.prototype.toScriptHashOut = function() {
return Script.buildScriptHashOut(this);
};
2014-09-01 15:45:03 -07:00
module.exports = Script;