diff --git a/src/script.js b/src/script.js index 16191fd..7a544bd 100644 --- a/src/script.js +++ b/src/script.js @@ -3,100 +3,100 @@ var util = require('./util'); var conv = require('./convert'); var Address = require('./address'); -var Script = function (data) { - if (!data) { - this.buffer = []; - } else if ("string" == typeof data) { - this.buffer = conv.hexToBytes(data); - } else if (util.isArray(data)) { - this.buffer = data; - } else if (data instanceof Script) { - this.buffer = data.buffer; - } else { - throw new Error("Invalid script"); - } - - this.parse(); +var Script = function(data) { + if (!data) { + this.buffer = []; + } else if ('string' == typeof data) { + this.buffer = conv.hexToBytes(data); + } else if (util.isArray(data)) { + this.buffer = data; + } else if (data instanceof Script) { + this.buffer = data.buffer; + } else { + throw new Error('Invalid script'); + } + + this.parse(); }; Script.fromPubKey = function(str) { - var script = new Script(); - var s = str.split(" "); - for (var i in s) { - if (Opcode.map.hasOwnProperty(s[i])){ - script.writeOp(Opcode.map[s[i]]); - } else { - script.writeBytes(conv.hexToBytes(s[i])); + var script = new Script(); + var s = str.split(' '); + for (var i in s) { + if (Opcode.map.hasOwnProperty(s[i])) { + script.writeOp(Opcode.map[s[i]]); + } else { + script.writeBytes(conv.hexToBytes(s[i])); + } } - } - return script; + return script; }; Script.fromScriptSig = function(str) { - var script = new Script(); - var s = str.split(" "); - for (var i in s) { - if (Opcode.map.hasOwnProperty(s[i])){ - script.writeOp(Opcode.map[s[i]]); - } else { - script.writeBytes(conv.hexToBytes(s[i])); + var script = new Script(); + var s = str.split(' '); + for (var i in s) { + if (Opcode.map.hasOwnProperty(s[i])) { + script.writeOp(Opcode.map[s[i]]); + } else { + script.writeBytes(conv.hexToBytes(s[i])); + } } - } - return script; + return script; }; /** * Update the parsed script representation. * * Each Script object stores the script in two formats. First as a raw byte - * array and second as an array of "chunks", such as opcodes and pieces of + * array and second as an array of 'chunks', such as opcodes and pieces of * data. * * This method updates the chunks cache. Normally this is called by the * constructor and you don't need to worry about it. However, if you change * the script buffer manually, you should update the chunks using this method. */ -Script.prototype.parse = function () { - var self = this; - - this.chunks = []; +Script.prototype.parse = function() { + var self = this; - // Cursor - var i = 0; + this.chunks = []; - // Read n bytes and store result as a chunk - function readChunk(n) { - self.chunks.push(self.buffer.slice(i, i + n)); - i += n; - }; + // Cursor + var i = 0; - while (i < this.buffer.length) { - var opcode = this.buffer[i++]; - if (opcode >= 0xF0) { - // Two byte opcode - opcode = (opcode << 8) | this.buffer[i++]; + // Read n bytes and store result as a chunk + function readChunk(n) { + self.chunks.push(self.buffer.slice(i, i + n)); + i += n; } - var len; - if (opcode > 0 && opcode < Opcode.map.OP_PUSHDATA1) { - // Read some bytes of data, opcode value is the length of data - readChunk(opcode); - } else if (opcode == Opcode.map.OP_PUSHDATA1) { - len = this.buffer[i++]; - readChunk(len); - } else if (opcode == Opcode.map.OP_PUSHDATA2) { - len = (this.buffer[i++] << 8) | this.buffer[i++]; - readChunk(len); - } else if (opcode == Opcode.map.OP_PUSHDATA4) { - len = (this.buffer[i++] << 24) | - (this.buffer[i++] << 16) | - (this.buffer[i++] << 8) | - this.buffer[i++]; - readChunk(len); - } else { - this.chunks.push(opcode); + while (i < this.buffer.length) { + var opcode = this.buffer[i++]; + if (opcode >= 0xF0) { + // Two byte opcode + opcode = (opcode << 8) | this.buffer[i++]; + } + + var len; + if (opcode > 0 && opcode < Opcode.map.OP_PUSHDATA1) { + // Read some bytes of data, opcode value is the length of data + readChunk(opcode); + } else if (opcode == Opcode.map.OP_PUSHDATA1) { + len = this.buffer[i++]; + readChunk(len); + } else if (opcode == Opcode.map.OP_PUSHDATA2) { + len = (this.buffer[i++] << 8) | this.buffer[i++]; + readChunk(len); + } else if (opcode == Opcode.map.OP_PUSHDATA4) { + len = (this.buffer[i++] << 24) | + (this.buffer[i++] << 16) | + (this.buffer[i++] << 8) | + this.buffer[i++]; + readChunk(len); + } else { + this.chunks.push(opcode); + } } - } }; /** @@ -113,47 +113,58 @@ Script.prototype.parse = function () { * Pubkey: * Paying to a public key directly. * [pubKey] OP_CHECKSIG - * + * * Strange: * Any other script (no template matched). */ -Script.prototype.getOutType = function () { - if (this.chunks[this.chunks.length-1] == Opcode.map.OP_EQUAL && +Script.prototype.getOutType = function() { + if (this.chunks[this.chunks.length - 1] == Opcode.map.OP_EQUAL && this.chunks[0] == Opcode.map.OP_HASH160 && this.chunks.length == 3) { // Transfer to M-OF-N return 'P2SH'; - } - else if (this.chunks.length == 5 && + } else if (this.chunks.length == 5 && this.chunks[0] == Opcode.map.OP_DUP && this.chunks[1] == Opcode.map.OP_HASH160 && this.chunks[3] == Opcode.map.OP_EQUALVERIFY && this.chunks[4] == Opcode.map.OP_CHECKSIG) { // Transfer to Bitcoin address return 'Pubkey'; - } - else { + } else { return 'Strange'; - } + } } /** * Returns the address corresponding to this output in hash160 form. * Assumes strange scripts are P2SH */ -Script.prototype.toScriptHash = function () { +Script.prototype.toScriptHash = function() { var outType = this.getOutType(); - return outType == 'Pubkey' ? this.chunks[2] - : outType == 'P2SH' ? util.sha256ripe160(this.buffer) - : util.sha256ripe160(this.buffer) -}; + if (outType == 'Pubkey') { + return this.chunks[2] + } + + if (outType == 'P2SH') { + return util.sha256ripe160(this.buffer) + } + + return util.sha256ripe160(this.buffer) +} Script.prototype.toAddress = function() { var outType = this.getOutType(); - return outType == 'Pubkey' ? new Address(this.chunks[2]) - : outType == 'P2SH' ? new Address(this.chunks[1],5) - : new Address(this.chunks[1],5) + + if (outType == 'Pubkey') { + return new Address(this.chunks[2]) + } + + if (outType == 'P2SH') { + return new Address(this.chunks[1], 5) + } + + return new Address(this.chunks[1], 5) } /** @@ -177,30 +188,28 @@ Script.prototype.toAddress = function() { * * Multisig: * Paying to M-of-N public keys. - * + * * Strange: * Any other script (no template matched). */ -Script.prototype.getInType = function () -{ - if (this.chunks.length == 1 && - util.isArray(this.chunks[0])) { - // Direct IP to IP transactions only have the signature in their scriptSig. - // TODO: We could also check that the length of the data is correct. - return 'Pubkey'; - } else if (this.chunks.length == 2 && - util.isArray(this.chunks[0]) && - util.isArray(this.chunks[1])) { - return 'Address'; - } else if (this.chunks[0] == Opcode.map.OP_0 && - this.chunks.slice(1).reduce(function(t,chunk,i) { - return t && util.isArray(chunk) - && (chunk[0] == 48 || i == this.chunks.length - 1); - },true)) { - return 'Multisig'; - } else { - return 'Strange'; - } +Script.prototype.getInType = function() { + if (this.chunks.length == 1 && + util.isArray(this.chunks[0])) { + // Direct IP to IP transactions only have the signature in their scriptSig. + // TODO: We could also check that the length of the data is correct. + return 'Pubkey'; + } else if (this.chunks.length == 2 && + util.isArray(this.chunks[0]) && + util.isArray(this.chunks[1])) { + return 'Address'; + } else if (this.chunks[0] == Opcode.map.OP_0 && + this.chunks.slice(1).reduce(function(t, chunk, i) { + return t && util.isArray(chunk) && (chunk[0] == 48 || i == this.chunks.length - 1); + }, true)) { + return 'Multisig'; + } else { + return 'Strange'; + } }; /** @@ -216,18 +225,17 @@ Script.prototype.getInType = function () * * @deprecated */ -Script.prototype.simpleInPubKey = function () -{ - switch (this.getInType()) { - case 'Address': - return this.chunks[1]; - case 'Pubkey': - // TODO: Theoretically, we could recover the pubkey from the sig here. - // See https://bitcointalk.org/?topic=6430.0 - throw new Error("Script does not contain pubkey."); - default: - throw new Error("Encountered non-standard scriptSig"); - } +Script.prototype.simpleInPubKey = function() { + switch (this.getInType()) { + case 'Address': + return this.chunks[1]; + case 'Pubkey': + // TODO: Theoretically, we could recover the pubkey from the sig here. + // See https://bitcointalk.org/?topic=6430.0 + throw new Error('Script does not contain pubkey'); + default: + throw new Error('Encountered non-standard scriptSig'); + } }; /** @@ -245,9 +253,8 @@ Script.prototype.simpleInPubKey = function () * * This method is useful for indexing transactions. */ -Script.prototype.simpleInHash = function () -{ - return util.sha256ripe160(this.simpleInPubKey()); +Script.prototype.simpleInHash = function() { + return util.sha256ripe160(this.simpleInPubKey()); }; /** @@ -260,59 +267,56 @@ Script.prototype.simpleInPubKeyHash = Script.prototype.simpleInHash; /** * Add an op code to the script. */ -Script.prototype.writeOp = function (opcode) -{ - this.buffer.push(opcode); - this.chunks.push(opcode); +Script.prototype.writeOp = function(opcode) { + this.buffer.push(opcode); + this.chunks.push(opcode); }; /** * Add a data chunk to the script. */ -Script.prototype.writeBytes = function (data) -{ - if (data.length < Opcode.map.OP_PUSHDATA1) { - this.buffer.push(data.length); - } else if (data.length <= 0xff) { - this.buffer.push(Opcode.map.OP_PUSHDATA1); - this.buffer.push(data.length); - } else if (data.length <= 0xffff) { - this.buffer.push(Opcode.map.OP_PUSHDATA2); - this.buffer.push(data.length & 0xff); - this.buffer.push((data.length >>> 8) & 0xff); - } else { - this.buffer.push(Opcode.map.OP_PUSHDATA4); - this.buffer.push(data.length & 0xff); - this.buffer.push((data.length >>> 8) & 0xff); - this.buffer.push((data.length >>> 16) & 0xff); - this.buffer.push((data.length >>> 24) & 0xff); - } - this.buffer = this.buffer.concat(data); - this.chunks.push(data); +Script.prototype.writeBytes = function(data) { + if (data.length < Opcode.map.OP_PUSHDATA1) { + this.buffer.push(data.length); + } else if (data.length <= 0xff) { + this.buffer.push(Opcode.map.OP_PUSHDATA1); + this.buffer.push(data.length); + } else if (data.length <= 0xffff) { + this.buffer.push(Opcode.map.OP_PUSHDATA2); + this.buffer.push(data.length & 0xff); + this.buffer.push((data.length >>> 8) & 0xff); + } else { + this.buffer.push(Opcode.map.OP_PUSHDATA4); + this.buffer.push(data.length & 0xff); + this.buffer.push((data.length >>> 8) & 0xff); + this.buffer.push((data.length >>> 16) & 0xff); + this.buffer.push((data.length >>> 24) & 0xff); + } + this.buffer = this.buffer.concat(data); + this.chunks.push(data); }; /** * Create an output for an address */ -Script.createOutputScript = function (address) -{ - var script = new Script(); - address = new Address(address); - // Standard pay-to-pubkey-hash - if (!address.version) { - script.writeOp(Opcode.map.OP_DUP); - script.writeOp(Opcode.map.OP_HASH160); - script.writeBytes(address.hash); - script.writeOp(Opcode.map.OP_EQUALVERIFY); - script.writeOp(Opcode.map.OP_CHECKSIG); - } - // Standard pay-to-script-hash - else { - script.writeOp(Opcode.map.OP_HASH160); - script.writeBytes(address.hash); - script.writeOp(Opcode.map.OP_EQUAL); - } - return script; +Script.createOutputScript = function(address) { + var script = new Script(); + address = new Address(address); + // Standard pay-to-pubkey-hash + if (!address.version) { + script.writeOp(Opcode.map.OP_DUP); + script.writeOp(Opcode.map.OP_HASH160); + script.writeBytes(address.hash); + script.writeOp(Opcode.map.OP_EQUALVERIFY); + script.writeOp(Opcode.map.OP_CHECKSIG); + } + // Standard pay-to-script-hash + else { + script.writeOp(Opcode.map.OP_HASH160); + script.writeBytes(address.hash); + script.writeOp(Opcode.map.OP_EQUAL); + } + return script; }; /** @@ -321,62 +325,62 @@ Script.createOutputScript = function (address) Script.prototype.extractPubkeys = function() { return this.chunks.filter(function(chunk) { - return (chunk[0] == 4 && chunk.length == 65 - || chunk[0] < 4 && chunk.length == 33) + return(chunk[0] == 4 && chunk.length == 65 || chunk[0] < 4 && chunk.length == 33) }); } /** * Create an m-of-n output script */ -Script.createMultiSigOutputScript = function (m, pubkeys) -{ - var script = new Script(); - - pubkeys = pubkeys.sort(); - - script.writeOp(Opcode.map.OP_1 + m - 1); - - for (var i = 0; i < pubkeys.length; ++i) { - script.writeBytes(pubkeys[i]); - } - - script.writeOp(Opcode.map.OP_1 + pubkeys.length - 1); - - script.writeOp(Opcode.map.OP_CHECKMULTISIG); - - return script; +Script.createMultiSigOutputScript = function(m, pubkeys) { + var script = new Script(); + + pubkeys = pubkeys.sort(); + + script.writeOp(Opcode.map.OP_1 + m - 1); + + for (var i = 0; i < pubkeys.length; ++i) { + script.writeBytes(pubkeys[i]); + } + + script.writeOp(Opcode.map.OP_1 + pubkeys.length - 1); + + script.writeOp(Opcode.map.OP_CHECKMULTISIG); + + return script; }; /** * Create a standard payToPubKeyHash input. */ -Script.createInputScript = function (signature, pubKey) -{ - var script = new Script(); - script.writeBytes(signature); - script.writeBytes(pubKey); - return script; +Script.createInputScript = function(signature, pubKey) { + var script = new Script(); + script.writeBytes(signature); + script.writeBytes(pubKey); + return script; }; /** * Create a multisig input */ -Script.createMultiSigInputScript = function(signatures, script) -{ +Script.createMultiSigInputScript = function(signatures, script) { script = new Script(script); var k = script.chunks[0][0]; - if (signatures.length < k) return false; //Not enough sigs + + //Not enough sigs + if (signatures.length < k) return false; + var inScript = new Script(); inScript.writeOp(Opcode.map.OP_0); - signatures.map(function(sig) { inScript.writeBytes(sig) }); + signatures.map(function(sig) { + inScript.writeBytes(sig) + }); inScript.writeBytes(script.buffer); return inScript; } -Script.prototype.clone = function () -{ - return new Script(this.buffer); +Script.prototype.clone = function() { + return new Script(this.buffer); }; module.exports = Script;