|
@ -3,7 +3,19 @@ |
|
|
var BufferReader = require('./encoding/bufferreader'); |
|
|
var BufferReader = require('./encoding/bufferreader'); |
|
|
var BufferWriter = require('./encoding/bufferwriter'); |
|
|
var BufferWriter = require('./encoding/bufferwriter'); |
|
|
var Opcode = require('./opcode'); |
|
|
var Opcode = require('./opcode'); |
|
|
|
|
|
var PublicKey = require('./publickey'); |
|
|
|
|
|
var Hash = require('./crypto/hash'); |
|
|
|
|
|
var bu = require('./util/buffer'); |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 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 |
|
|
|
|
|
*/ |
|
|
var Script = function Script(from) { |
|
|
var Script = function Script(from) { |
|
|
if (!(this instanceof Script)) { |
|
|
if (!(this instanceof Script)) { |
|
|
return new Script(from); |
|
|
return new Script(from); |
|
@ -11,7 +23,7 @@ var Script = function Script(from) { |
|
|
|
|
|
|
|
|
this.chunks = []; |
|
|
this.chunks = []; |
|
|
|
|
|
|
|
|
if (Buffer.isBuffer(from)) { |
|
|
if (bu.isBuffer(from)) { |
|
|
return Script.fromBuffer(from); |
|
|
return Script.fromBuffer(from); |
|
|
} else if (typeof from === 'string') { |
|
|
} else if (typeof from === 'string') { |
|
|
return Script.fromString(from); |
|
|
return Script.fromString(from); |
|
@ -194,12 +206,7 @@ Script.prototype.isPublicKeyHashIn = function() { |
|
|
this.chunks[0].buf.length >= 0x47 && |
|
|
this.chunks[0].buf.length >= 0x47 && |
|
|
this.chunks[0].buf.length <= 0x49 && |
|
|
this.chunks[0].buf.length <= 0x49 && |
|
|
this.chunks[1].buf && |
|
|
this.chunks[1].buf && |
|
|
( |
|
|
PublicKey.isValid(this.chunks[1].buf)); |
|
|
// compressed public key
|
|
|
|
|
|
(this.chunks[1].buf[0] === 0x03 && this.chunks[1].buf.length === 0x21) || |
|
|
|
|
|
// uncompressed public key
|
|
|
|
|
|
(this.chunks[1].buf[0] === 0x04 && this.chunks[1].buf.length === 0x41)) |
|
|
|
|
|
); |
|
|
|
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
@ -207,8 +214,8 @@ Script.prototype.isPublicKeyHashIn = function() { |
|
|
*/ |
|
|
*/ |
|
|
Script.prototype.isPublicKeyOut = function() { |
|
|
Script.prototype.isPublicKeyOut = function() { |
|
|
return this.chunks.length === 2 && |
|
|
return this.chunks.length === 2 && |
|
|
Buffer.isBuffer(this.chunks[0].buf) && |
|
|
bu.isBuffer(this.chunks[0].buf) && |
|
|
this.chunks[0].buf.length === 0x41 && |
|
|
PublicKey.isValid(this.chunks[0].buf) && |
|
|
this.chunks[1] === Opcode('OP_CHECKSIG').toNumber(); |
|
|
this.chunks[1] === Opcode('OP_CHECKSIG').toNumber(); |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
@ -217,7 +224,7 @@ Script.prototype.isPublicKeyOut = function() { |
|
|
*/ |
|
|
*/ |
|
|
Script.prototype.isPublicKeyIn = function() { |
|
|
Script.prototype.isPublicKeyIn = function() { |
|
|
return this.chunks.length === 1 && |
|
|
return this.chunks.length === 1 && |
|
|
Buffer.isBuffer(this.chunks[0].buf) && |
|
|
bu.isBuffer(this.chunks[0].buf) && |
|
|
this.chunks[0].buf.length === 0x47; |
|
|
this.chunks[0].buf.length === 0x47; |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
@ -261,7 +268,7 @@ Script.prototype.isMultisigOut = function() { |
|
|
return (this.chunks.length > 3 && |
|
|
return (this.chunks.length > 3 && |
|
|
Opcode.isSmallIntOp(this.chunks[0]) && |
|
|
Opcode.isSmallIntOp(this.chunks[0]) && |
|
|
this.chunks.slice(1, this.chunks.length - 2).every(function(obj) { |
|
|
this.chunks.slice(1, this.chunks.length - 2).every(function(obj) { |
|
|
return obj.buf && Buffer.isBuffer(obj.buf); |
|
|
return obj.buf && bu.isBuffer(obj.buf); |
|
|
}) && |
|
|
}) && |
|
|
Opcode.isSmallIntOp(this.chunks[this.chunks.length - 2]) && |
|
|
Opcode.isSmallIntOp(this.chunks[this.chunks.length - 2]) && |
|
|
this.chunks[this.chunks.length - 1] === Opcode.map.OP_CHECKMULTISIG); |
|
|
this.chunks[this.chunks.length - 1] === Opcode.map.OP_CHECKMULTISIG); |
|
@ -275,7 +282,7 @@ Script.prototype.isMultisigIn = function() { |
|
|
return this.chunks[0] === 0 && |
|
|
return this.chunks[0] === 0 && |
|
|
this.chunks.slice(1, this.chunks.length).every(function(obj) { |
|
|
this.chunks.slice(1, this.chunks.length).every(function(obj) { |
|
|
return obj.buf && |
|
|
return obj.buf && |
|
|
Buffer.isBuffer(obj.buf) && |
|
|
bu.isBuffer(obj.buf) && |
|
|
obj.buf.length === 0x47; |
|
|
obj.buf.length === 0x47; |
|
|
}); |
|
|
}); |
|
|
}; |
|
|
}; |
|
@ -283,7 +290,7 @@ Script.prototype.isMultisigIn = function() { |
|
|
/** |
|
|
/** |
|
|
* @returns true if this is an OP_RETURN data script |
|
|
* @returns true if this is an OP_RETURN data script |
|
|
*/ |
|
|
*/ |
|
|
Script.prototype.isOpReturn = function() { |
|
|
Script.prototype.isDataOut = function() { |
|
|
return (this.chunks[0] === Opcode('OP_RETURN').toNumber() && |
|
|
return (this.chunks[0] === Opcode('OP_RETURN').toNumber() && |
|
|
(this.chunks.length === 1 || |
|
|
(this.chunks.length === 1 || |
|
|
(this.chunks.length === 2 && |
|
|
(this.chunks.length === 2 && |
|
@ -303,7 +310,7 @@ Script.types.SCRIPTHASH_OUT = 'Pay to script hash'; |
|
|
Script.types.SCRIPTHASH_IN = 'Spend from script hash'; |
|
|
Script.types.SCRIPTHASH_IN = 'Spend from script hash'; |
|
|
Script.types.MULTISIG_OUT = 'Pay to multisig'; |
|
|
Script.types.MULTISIG_OUT = 'Pay to multisig'; |
|
|
Script.types.MULTISIG_IN = 'Spend from multisig'; |
|
|
Script.types.MULTISIG_IN = 'Spend from multisig'; |
|
|
Script.types.OP_RETURN = 'Data push'; |
|
|
Script.types.DATA_OUT = 'Data push'; |
|
|
|
|
|
|
|
|
Script.identifiers = {}; |
|
|
Script.identifiers = {}; |
|
|
Script.identifiers.PUBKEY_OUT = Script.prototype.isPublicKeyOut; |
|
|
Script.identifiers.PUBKEY_OUT = Script.prototype.isPublicKeyOut; |
|
@ -312,9 +319,9 @@ Script.identifiers.PUBKEYHASH_OUT = Script.prototype.isPublicKeyHashOut; |
|
|
Script.identifiers.PUBKEYHASH_IN = Script.prototype.isPublicKeyHashIn; |
|
|
Script.identifiers.PUBKEYHASH_IN = Script.prototype.isPublicKeyHashIn; |
|
|
Script.identifiers.MULTISIG_OUT = Script.prototype.isMultisigOut; |
|
|
Script.identifiers.MULTISIG_OUT = Script.prototype.isMultisigOut; |
|
|
Script.identifiers.MULTISIG_IN = Script.prototype.isMultisigIn; |
|
|
Script.identifiers.MULTISIG_IN = Script.prototype.isMultisigIn; |
|
|
Script.identifiers.OP_RETURN = Script.prototype.isOpReturn; |
|
|
|
|
|
Script.identifiers.SCRIPTHASH_OUT = Script.prototype.isScriptHashOut; |
|
|
Script.identifiers.SCRIPTHASH_OUT = Script.prototype.isScriptHashOut; |
|
|
Script.identifiers.SCRIPTHASH_IN = Script.prototype.isScriptHashIn; |
|
|
Script.identifiers.SCRIPTHASH_IN = Script.prototype.isScriptHashIn; |
|
|
|
|
|
Script.identifiers.DATA_OUT = Script.prototype.isDataOut; |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* @returns {object} The Script type if it is a known form, |
|
|
* @returns {object} The Script type if it is a known form, |
|
@ -369,7 +376,7 @@ Script.prototype._addByType = function(obj, prepend) { |
|
|
this._addOpcode(obj, prepend); |
|
|
this._addOpcode(obj, prepend); |
|
|
} else if (obj.constructor && obj.constructor.name && obj.constructor.name === 'Opcode') { |
|
|
} else if (obj.constructor && obj.constructor.name && obj.constructor.name === 'Opcode') { |
|
|
this._addOpcode(obj, prepend); |
|
|
this._addOpcode(obj, prepend); |
|
|
} else if (Buffer.isBuffer(obj)) { |
|
|
} else if (bu.isBuffer(obj)) { |
|
|
this._addBuffer(obj, prepend); |
|
|
this._addBuffer(obj, prepend); |
|
|
} else if (typeof obj === 'object') { |
|
|
} else if (typeof obj === 'object') { |
|
|
this._insertAtPosition(obj, prepend); |
|
|
this._insertAtPosition(obj, prepend); |
|
@ -402,13 +409,15 @@ Script.prototype._addOpcode = function(opcode, prepend) { |
|
|
Script.prototype._addBuffer = function(buf, prepend) { |
|
|
Script.prototype._addBuffer = function(buf, prepend) { |
|
|
var opcodenum; |
|
|
var opcodenum; |
|
|
var len = buf.length; |
|
|
var len = buf.length; |
|
|
if (buf.length > 0 && buf.length < Opcode.map.OP_PUSHDATA1) { |
|
|
if (len === 0) { |
|
|
opcodenum = buf.length; |
|
|
return; |
|
|
} else if (buf.length < Math.pow(2, 8)) { |
|
|
} else if (len > 0 && len < Opcode.map.OP_PUSHDATA1) { |
|
|
|
|
|
opcodenum = len; |
|
|
|
|
|
} else if (len < Math.pow(2, 8)) { |
|
|
opcodenum = Opcode.map.OP_PUSHDATA1; |
|
|
opcodenum = Opcode.map.OP_PUSHDATA1; |
|
|
} else if (buf.length < Math.pow(2, 16)) { |
|
|
} else if (len < Math.pow(2, 16)) { |
|
|
opcodenum = Opcode.map.OP_PUSHDATA2; |
|
|
opcodenum = Opcode.map.OP_PUSHDATA2; |
|
|
} else if (buf.length < Math.pow(2, 32)) { |
|
|
} else if (len < Math.pow(2, 32)) { |
|
|
opcodenum = Opcode.map.OP_PUSHDATA4; |
|
|
opcodenum = Opcode.map.OP_PUSHDATA4; |
|
|
} else { |
|
|
} else { |
|
|
throw new Error('You can\'t push that much data'); |
|
|
throw new Error('You can\'t push that much data'); |
|
@ -421,4 +430,88 @@ Script.prototype._addBuffer = function(buf, prepend) { |
|
|
return this; |
|
|
return this; |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// high level script builder methods
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* @returns a new Multisig output script for given public keys, |
|
|
|
|
|
* requiring m of those public keys to spend |
|
|
|
|
|
* @param {PublicKey[]} pubkeys - list of all public keys controlling the output |
|
|
|
|
|
* @param {number} m - amount of required signatures to spend the output |
|
|
|
|
|
*/ |
|
|
|
|
|
Script.buildMultisigOut = function(pubkeys, m) { |
|
|
|
|
|
var s = new Script(); |
|
|
|
|
|
s.add(Opcode.smallInt(m)); |
|
|
|
|
|
for (var i = 0; i < pubkeys.length; i++) { |
|
|
|
|
|
var pubkey = pubkeys[i]; |
|
|
|
|
|
s.add(pubkey.toBuffer()); |
|
|
|
|
|
} |
|
|
|
|
|
s.add(Opcode.smallInt(pubkeys.length)); |
|
|
|
|
|
s.add(Opcode('OP_CHECKMULTISIG')); |
|
|
|
|
|
return s; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* @returns a new pay to public key hash output for the given |
|
|
|
|
|
* address or public key |
|
|
|
|
|
* @param {(Address|PublicKey)} to - destination address or public key |
|
|
|
|
|
*/ |
|
|
|
|
|
Script.buildPublicKeyHashOut = function(to) { |
|
|
|
|
|
if (to instanceof PublicKey) { |
|
|
|
|
|
to = to.toAddress(); |
|
|
|
|
|
} |
|
|
|
|
|
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; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* @returns a new pay to public key output for the given |
|
|
|
|
|
* public key |
|
|
|
|
|
*/ |
|
|
|
|
|
Script.buildPublicKeyOut = function(pubkey) { |
|
|
|
|
|
var s = new Script(); |
|
|
|
|
|
s.add(pubkey.toBuffer()) |
|
|
|
|
|
.add(Opcode('OP_CHECKSIG')); |
|
|
|
|
|
return s; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* @returns a new OP_RETURN script with data |
|
|
|
|
|
* @param {(string|Buffer)} to - the data to embed in the output |
|
|
|
|
|
*/ |
|
|
|
|
|
Script.buildDataOut = function(data) { |
|
|
|
|
|
if (typeof data === 'string') { |
|
|
|
|
|
data = new Buffer(data); |
|
|
|
|
|
} |
|
|
|
|
|
var s = new Script(); |
|
|
|
|
|
s.add(Opcode('OP_RETURN')) |
|
|
|
|
|
.add(data); |
|
|
|
|
|
return s; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* @returns a new pay to script hash script for given script |
|
|
|
|
|
* @param {Script} script - the redeemScript for the new p2sh output |
|
|
|
|
|
*/ |
|
|
|
|
|
Script.buildScriptHashOut = function(script) { |
|
|
|
|
|
var s = new Script(); |
|
|
|
|
|
s.add(Opcode('OP_HASH160')) |
|
|
|
|
|
.add(Hash.sha256ripemd160(script.toBuffer())) |
|
|
|
|
|
.add(Opcode('OP_EQUAL')); |
|
|
|
|
|
return s; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* @returns a new pay to script hash script that pays to this script |
|
|
|
|
|
*/ |
|
|
|
|
|
Script.prototype.toScriptHashOut = function() { |
|
|
|
|
|
return Script.buildScriptHashOut(this); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
module.exports = Script; |
|
|
module.exports = Script; |
|
|