diff --git a/src/address.js b/src/address.js index 5e15845..b1e6aeb 100644 --- a/src/address.js +++ b/src/address.js @@ -1,7 +1,7 @@ var assert = require('assert') var base58check = require('./base58check') var networks = require('./networks') -var Script = require('./script') +var scripts = require('./scripts') function findScriptTypeByVersion(queryVersion) { for (var networkName in networks) { @@ -35,17 +35,16 @@ Address.fromBase58Check = function(string) { return new Address(hash, version) } -Address.fromScriptPubKey = function(script, network) { +Address.fromOutputScript = function(script, network) { network = network || networks.bitcoin - var type = script.getOutType() + var type = scripts.classifyOutput(script) if (type === 'pubkeyhash') { - return new Address(new Buffer(script.chunks[2]), network.pubKeyHash) - } + return new Address(script.chunks[2], network.pubkeyhash) - else if (type === 'scripthash') { - return new Address(new Buffer(script.chunks[1]), network.scriptHash) + } else if (type === 'scripthash') { + return new Address(script.chunks[1], network.scripthash) } assert(false, type + ' has no matching Address') @@ -60,18 +59,18 @@ Address.prototype.toBase58Check = function () { return base58check.encode(payload) } -Address.prototype.toScriptPubKey = function() { +Address.prototype.toOutputScript = function() { var scriptType = findScriptTypeByVersion(this.version) - if (scriptType === 'pubKeyHash') { - return Script.createPubKeyHashScriptPubKey(this.hash) + if (scriptType === 'pubkeyhash') { + return scripts.pubKeyHashOutput(this.hash) + + } else if (scriptType === 'scripthash') { + return scripts.scriptHashOutput(this.hash) + } - else if (scriptType === 'scriptHash') { - return Script.createP2SHScriptPubKey(this.hash) - } - - assert(false, this.toString() + ' has no matching script') + assert(false, this.toString() + ' has no matching Script') } Address.prototype.toString = Address.prototype.toBase58Check diff --git a/src/ecpubkey.js b/src/ecpubkey.js index 3716729..61a6666 100644 --- a/src/ecpubkey.js +++ b/src/ecpubkey.js @@ -32,7 +32,7 @@ ECPubKey.fromHex = function(hex) { ECPubKey.prototype.getAddress = function(network) { network = network || networks.bitcoin - return new Address(crypto.hash160(this.toBuffer()), network.pubKeyHash) + return new Address(crypto.hash160(this.toBuffer()), network.pubkeyhash) } ECPubKey.prototype.verify = function(hash, signature) { diff --git a/src/index.js b/src/index.js index b46f2c5..5af8210 100644 --- a/src/index.js +++ b/src/index.js @@ -14,6 +14,7 @@ module.exports = { opcodes: require('./opcodes'), HDNode: require('./hdnode'), Script: require('./script'), + scripts: require('./scripts'), Transaction: T.Transaction, TransactionIn: T.TransactionIn, TransactionOut: T.TransactionOut, diff --git a/src/message.js b/src/message.js index 02b0b71..a70b9a2 100644 --- a/src/message.js +++ b/src/message.js @@ -13,7 +13,7 @@ var ecurve = require('ecurve') var ecparams = ecurve.getCurveByName('secp256k1') function magicHash(message, network) { - var magicPrefix = new Buffer(network.magicPrefix) + var magicPrefix = new Buffer(network.magicprefix) var messageBuffer = new Buffer(message) var lengthBuffer = new Buffer(bufferutils.varIntSize(messageBuffer.length)) bufferutils.writeVarInt(lengthBuffer, messageBuffer.length, 0) diff --git a/src/networks.js b/src/networks.js index da71d23..2b85818 100644 --- a/src/networks.js +++ b/src/networks.js @@ -2,43 +2,43 @@ // Dogecoin BIP32 is a proposed standard: https://bitcointalk.org/index.php?topic=409731 module.exports = { bitcoin: { - magicPrefix: '\x18Bitcoin Signed Message:\n', + magicprefix: '\x18Bitcoin Signed Message:\n', bip32: { public: 0x0488b21e, private: 0x0488ade4 }, - pubKeyHash: 0x00, - scriptHash: 0x05, + pubkeyhash: 0x00, + scripthash: 0x05, wif: 0x80 }, dogecoin: { - magicPrefix: '\x19Dogecoin Signed Message:\n', + magicprefix: '\x19Dogecoin Signed Message:\n', bip32: { public: 0x02facafd, private: 0x02fac398 }, - pubKeyHash: 0x1e, - scriptHash: 0x16, + pubkeyhash: 0x1e, + scripthash: 0x16, wif: 0x9e }, litecoin: { - magicPrefix: '\x19Litecoin Signed Message:\n', + magicprefix: '\x19Litecoin Signed Message:\n', bip32: { public: 0x019da462, private: 0x019d9cfe }, - pubKeyHash: 0x30, - scriptHash: 0x05, + pubkeyhash: 0x30, + scripthash: 0x05, wif: 0xb0 }, testnet: { - magicPrefix: '\x18Bitcoin Signed Message:\n', + magicprefix: '\x18Bitcoin Signed Message:\n', bip32: { public: 0x043587cf, private: 0x04358394 }, - pubKeyHash: 0x6f, - scriptHash: 0xc4, + pubkeyhash: 0x6f, + scripthash: 0xc4, wif: 0xef } } diff --git a/src/script.js b/src/script.js index cce5189..700b7ff 100644 --- a/src/script.js +++ b/src/script.js @@ -3,399 +3,61 @@ var bufferutils = require('./bufferutils') var crypto = require('./crypto') var opcodes = require('./opcodes') -function Script(data) { - data = data || [] - assert(Array.isArray(data), 'Expected Array, got ' + data) +function Script(buffer, chunks) { + assert(Buffer.isBuffer(buffer), 'Expected Buffer, got ' + buffer) + assert(Array.isArray(chunks), 'Expected Array, got ' + chunks) - this.buffer = data - this.parse() + this.buffer = buffer + this.chunks = chunks } // Import operations Script.fromBuffer = function(buffer) { - assert(Buffer.isBuffer(buffer)) // FIXME: transitionary + var chunks = [] - return new Script(Array.prototype.slice.call(buffer)) -} - -Script.fromHex = function(hex) { - return Script.fromBuffer(new Buffer(hex, 'hex')) -} - -// Export operations -Script.prototype.toBuffer = function() { - return new Buffer(this.buffer) -} - -Script.prototype.toHex = function() { - return this.toBuffer().toString('hex') -} - -/** - * 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 - * 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 = [] - - // Cursor var i = 0 - // Read n bytes and store result as a chunk - function readChunk(n) { - self.chunks.push(self.buffer.slice(i, i + n)) - i += n - } + while (i < buffer.length) { + var opcode = buffer.readUInt8(i) - while (i < this.buffer.length) { - var opcode = this.buffer[i++] - if (opcode >= 0xF0) { - // Two byte opcode - opcode = (opcode << 8) | this.buffer[i++] - } + if ((opcode > opcodes.OP_0) && (opcode <= opcodes.OP_PUSHDATA4)) { + var d = bufferutils.readPushDataInt(buffer, i) + i += d.size + + var data = buffer.slice(i, i + d.number) + i += d.number + + chunks.push(data) - var len - if (opcode > 0 && opcode < opcodes.OP_PUSHDATA1) { - // Read some bytes of data, opcode value is the length of data - readChunk(opcode) - } else if (opcode == opcodes.OP_PUSHDATA1) { - len = this.buffer[i++] - readChunk(len) - } else if (opcode == opcodes.OP_PUSHDATA2) { - len = (this.buffer[i++] << 8) | this.buffer[i++] - readChunk(len) - } else if (opcode == opcodes.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) + chunks.push(opcode) + + i += 1 } } -} - -/** - * Compare the script to known templates of scriptPubKey. - * - * This method will compare the script to a small number of standard script - * templates and return a string naming the detected type. - * - * Currently supported are: - * Pubkeyhash (address) - * Paying to a Bitcoin address which is the hash of a pubkey. - * OP_DUP OP_HASH160 [pubKeyHash] OP_EQUALVERIFY OP_CHECKSIG - * - * Pubkey - * Paying to a public key directly. - * [pubKey] OP_CHECKSIG - * - * Scripthash (P2SH) - * Paying to an address which is the hash of a script - * OP_HASH160 [Scripthash] OP_EQUAL - * - * Multisig - * Paying to multiple pubkeys and require a number of the signatures - * m [pubkey] [pubkey] [pubkey] n OP_CHECKMULTISIG - * - * Nulldata - * Provably prune-able outputs - * OP_RETURN [data] - * - * Nonstandard: - * Any other script (no template matched). - * - * https://github.com/bitcoin/bitcoin/blob/19e5b9d2dfcac4efadba636745485d9660fb1abe/src/script.cpp#L75 - */ - -Script.prototype.getOutType = function() { - if (isPubkeyhash.call(this)) { - return 'pubkeyhash' - } else if (isPubkey.call(this)) { - return 'pubkey' - } else if (isScripthash.call(this)) { - return 'scripthash' - } else if (isMultisig.call(this)) { - return 'multisig' - } else if (isNulldata.call(this)) { - return 'nulldata' - } else { - return 'nonstandard' - } -} - -function isPubkeyhash() { - return this.chunks.length == 5 && - this.chunks[0] == opcodes.OP_DUP && - this.chunks[1] == opcodes.OP_HASH160 && - Array.isArray(this.chunks[2]) && - this.chunks[2].length === 20 && - this.chunks[3] == opcodes.OP_EQUALVERIFY && - this.chunks[4] == opcodes.OP_CHECKSIG -} - -function isPubkey() { - return this.chunks.length === 2 && - Array.isArray(this.chunks[0]) && - this.chunks[1] === opcodes.OP_CHECKSIG -} - -function isScripthash() { - return this.chunks[this.chunks.length - 1] == opcodes.OP_EQUAL && - this.chunks[0] == opcodes.OP_HASH160 && - Array.isArray(this.chunks[1]) && - this.chunks[1].length === 20 && - this.chunks.length == 3 -} - -function isMultisig() { - return this.chunks.length > 3 && - // m is a smallint - isSmallIntOp(this.chunks[0]) && - // n is a smallint - isSmallIntOp(this.chunks[this.chunks.length - 2]) && - // n greater or equal to m - this.chunks[0] <= this.chunks[this.chunks.length - 2] && - // n cannot be 0 - this.chunks[this.chunks.length - 2] !== opcodes.OP_0 && - // n is the size of chunk length minus 3 (m, n, OP_CHECKMULTISIG) - this.chunks.length - 3 === this.chunks[this.chunks.length - 2] - opcodes.OP_RESERVED && - // last chunk is OP_CHECKMULTISIG - this.chunks[this.chunks.length - 1] == opcodes.OP_CHECKMULTISIG -} - -function isNulldata() { - return this.chunks[0] === opcodes.OP_RETURN -} - -function isSmallIntOp(opcode) { - return ((opcode == opcodes.OP_0) || - ((opcode >= opcodes.OP_1) && (opcode <= opcodes.OP_16))) -} - -Script.prototype.getHash = function() { - return crypto.hash160(new Buffer(this.buffer)) -} - -/** - * Compare the script to known templates of scriptSig. - * - * This method will compare the script to a small number of standard script - * templates and return a string naming the detected type. - * - * WARNING: Use this method with caution. It merely represents a heuristic - * based on common transaction formats. A non-standard transaction could - * very easily match one of these templates by accident. - * - * Currently supported are: - * Address: - * Paying to a Bitcoin address which is the hash of a pubkey. - * [sig] [pubKey] - * - * Pubkey: - * Paying to a public key directly. - * [sig] - * - * Multisig: - * Paying to M-of-N public keys. - * - * Nonstandard: - * Any other script (no template matched). - */ -Script.prototype.getInType = function() { - if (this.chunks.length == 1 && - Array.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 && - Array.isArray(this.chunks[0]) && - Array.isArray(this.chunks[1])) { - return 'pubkeyhash' - } else if (this.chunks[0] == opcodes.OP_0 && - this.chunks.slice(1).reduce(function(t, chunk, i) { - return t && Array.isArray(chunk) && (chunk[0] == 48 || i == this.chunks.length - 1) - }, true)) { - return 'multisig' - } else { - return 'nonstandard' - } -} - -/** - * Add an op code to the script. - */ -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) { - // FIXME: Script module doesn't support buffers yet - if (Buffer.isBuffer(data)) data = Array.prototype.slice.call(data); - assert(Array.isArray(data), 'Expected a byte array, got ' + data) - - if (data.length < opcodes.OP_PUSHDATA1) { - this.buffer.push(data.length) - } else if (data.length <= 0xff) { - this.buffer.push(opcodes.OP_PUSHDATA1) - this.buffer.push(data.length) - } else if (data.length <= 0xffff) { - this.buffer.push(opcodes.OP_PUSHDATA2) - this.buffer.push(data.length & 0xff) - this.buffer.push((data.length >>> 8) & 0xff) - } else { - this.buffer.push(opcodes.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) -} - -// {pubKey} OP_CHECKSIG -Script.createPubKeyScriptPubKey = function(pubKey) { - var script = new Script() - - script.writeBytes(pubKey.toBuffer()) - script.writeOp(opcodes.OP_CHECKSIG) - - return script -} - -// OP_DUP OP_HASH160 {pubKeyHash} OP_EQUALVERIFY OP_CHECKSIG -Script.createPubKeyHashScriptPubKey = function(hash) { - var script = new Script() - - script.writeOp(opcodes.OP_DUP) - script.writeOp(opcodes.OP_HASH160) - script.writeBytes(hash) - script.writeOp(opcodes.OP_EQUALVERIFY) - script.writeOp(opcodes.OP_CHECKSIG) - - return script -} - -// OP_HASH160 {scriptHash} OP_EQUAL -Script.createP2SHScriptPubKey = function(hash) { - var script = new Script() - - script.writeOp(opcodes.OP_HASH160) - script.writeBytes(hash) - script.writeOp(opcodes.OP_EQUAL) - - return script -} - -// m [pubKeys ...] n OP_CHECKMULTISIG -Script.createMultisigScriptPubKey = function(m, pubKeys) { - assert(Array.isArray(pubKeys), 'Expected Array, got ' + pubKeys) - assert(pubKeys.length >= m, 'Not enough pubKeys provided') - var script = new Script() - var n = pubKeys.length - - script.writeOp((opcodes.OP_1 - 1) + m) - - pubKeys.forEach(function(pubKey) { - script.writeBytes(pubKey.toBuffer()) - }) - - script.writeOp((opcodes.OP_1 - 1) + n) - script.writeOp(opcodes.OP_CHECKMULTISIG) - - return script -} - -// {signature} -Script.createPubKeyScriptSig = function(signature) { - var script = new Script() - script.writeBytes(signature) - return script -} - -// {signature} {pubKey} -Script.createPubKeyHashScriptSig = function(signature, pubKey) { - var script = new Script() - script.writeBytes(signature) - script.writeBytes(pubKey.toBuffer()) - return script -} - -// {serialized scriptPubKey script} -Script.createP2SHScriptSig = function(scriptSig, scriptPubKey) { - var inScript = new Script(scriptSig.buffer) - inScript.writeBytes(scriptPubKey.buffer) - return inScript -} - -// OP_0 [signatures ...] -Script.createMultisigScriptSig = function(signatures, scriptPubKey) { - if (scriptPubKey) { - assert(isMultisig.call(scriptPubKey)) - - var m = scriptPubKey.chunks[0] - var k = m - (opcodes.OP_1 - 1) - assert(k <= signatures.length, 'Not enough signatures provided') - } - - var inScript = new Script() - - inScript.writeOp(opcodes.OP_0) - signatures.map(function(sig) { - inScript.writeBytes(sig) - }) - - return inScript -} - -Script.prototype.clone = function() { - return new Script(this.buffer) + return new Script(buffer, chunks) } Script.fromChunks = function(chunks) { assert(Array.isArray(chunks), 'Expected Array, got ' + chunks) var bufferSize = chunks.reduce(function(accum, chunk) { - var chunkSize = 1 - - // FIXME: transitionary - if (Array.isArray(chunk) || Buffer.isBuffer(chunk)) { - chunkSize = bufferutils.pushDataSize(chunk.length) + chunk.length + if (Buffer.isBuffer(chunk)) { + return accum + bufferutils.pushDataSize(chunk.length) + chunk.length } - return accum + chunkSize + return accum + 1 }, 0.0) var buffer = new Buffer(bufferSize) var offset = 0 chunks.forEach(function(chunk) { - // FIXME: transitionary - if (Array.isArray(chunk) || Buffer.isBuffer(chunk)) { + if (Buffer.isBuffer(chunk)) { offset += bufferutils.writePushDataInt(buffer, chunk.length, offset) - // FIXME: transitionary -// chunk.copy(buffer, offset) - for (var i = 0; i < chunk.length; ++i) { - buffer[offset + i] = chunk[i] - } - + chunk.copy(buffer, offset) offset += chunk.length } else { @@ -404,7 +66,20 @@ Script.fromChunks = function(chunks) { } }) - return Script.fromBuffer(buffer) + assert.equal(offset, buffer.length, 'Could not decode chunks') + return new Script(buffer, chunks) +} + +Script.fromHex = function(hex) { + return Script.fromBuffer(new Buffer(hex, 'hex')) +} + +// Constants +Script.EMPTY = Script.fromChunks([]) + +// Operations +Script.prototype.getHash = function() { + return crypto.hash160(this.buffer) } // FIXME: doesn't work for data chunks, maybe time to use buffertools.compare... @@ -414,4 +89,13 @@ Script.prototype.without = function(needle) { })) } +// Export operations +Script.prototype.toBuffer = function() { + return this.buffer +} + +Script.prototype.toHex = function() { + return this.toBuffer().toString('hex') +} + module.exports = Script diff --git a/src/scripts.js b/src/scripts.js new file mode 100644 index 0000000..fc5d1e1 --- /dev/null +++ b/src/scripts.js @@ -0,0 +1,184 @@ +var assert = require('assert') +var opcodes = require('./opcodes') +var Script = require('./script') + +function classifyOutput(script) { + assert(script instanceof Script, 'Expected Script, got ', script) + + if (isPubkeyhash.call(script)) { + return 'pubkeyhash' + } else if (isPubkey.call(script)) { + return 'pubkey' + } else if (isScripthash.call(script)) { + return 'scripthash' + } else if (isMultisig.call(script)) { + return 'multisig' + } else if (isNulldata.call(script)) { + return 'nulldata' + } else { + return 'nonstandard' + } +} + +function classifyInput(script) { + assert(script instanceof Script, 'Expected Script, got ', script) + + if (script.chunks.length == 1 && Buffer.isBuffer(script.chunks[0])) { + return 'pubkey' + } else if (script.chunks.length == 2 && Buffer.isBuffer(script.chunks[0]) && Buffer.isBuffer(script.chunks[1])) { + return 'pubkeyhash' + } else if (script.chunks[0] == opcodes.OP_0 && script.chunks.slice(1).reduce(function(t, chunk, i) { + return t && Buffer.isBuffer(chunk) && (chunk[0] == 48 || i == script.chunks.length - 1) + }, true)) { + return 'multisig' + } else { + return 'nonstandard' + } +} + +function isPubkeyhash() { + return this.chunks.length == 5 && + this.chunks[0] == opcodes.OP_DUP && + this.chunks[1] == opcodes.OP_HASH160 && + Buffer.isBuffer(this.chunks[2]) && + this.chunks[2].length === 20 && + this.chunks[3] == opcodes.OP_EQUALVERIFY && + this.chunks[4] == opcodes.OP_CHECKSIG +} + +function isPubkey() { + return this.chunks.length === 2 && + Buffer.isBuffer(this.chunks[0]) && + this.chunks[1] === opcodes.OP_CHECKSIG +} + +function isScripthash() { + return this.chunks[this.chunks.length - 1] == opcodes.OP_EQUAL && + this.chunks[0] == opcodes.OP_HASH160 && + Buffer.isBuffer(this.chunks[1]) && + this.chunks[1].length === 20 && + this.chunks.length == 3 +} + +function isMultisig() { + return this.chunks.length > 3 && + // m is a smallint + isSmallIntOp(this.chunks[0]) && + // n is a smallint + isSmallIntOp(this.chunks[this.chunks.length - 2]) && + // n greater or equal to m + this.chunks[0] <= this.chunks[this.chunks.length - 2] && + // n cannot be 0 + this.chunks[this.chunks.length - 2] !== opcodes.OP_0 && + // n is the size of chunk length minus 3 (m, n, OP_CHECKMULTISIG) + this.chunks.length - 3 === this.chunks[this.chunks.length - 2] - opcodes.OP_RESERVED && + // last chunk is OP_CHECKMULTISIG + this.chunks[this.chunks.length - 1] == opcodes.OP_CHECKMULTISIG +} + +function isNulldata() { + return this.chunks[0] === opcodes.OP_RETURN +} + +function isSmallIntOp(opcode) { + return ((opcode == opcodes.OP_0) || ((opcode >= opcodes.OP_1) && (opcode <= opcodes.OP_16))) +} + +// Standard Script Templates +// {pubKey} OP_CHECKSIG +function pubKeyOutput(pubKey) { + return Script.fromChunks([ + pubKey.toBuffer(), + opcodes.OP_CHECKSIG + ]) +} + +// OP_DUP OP_HASH160 {pubKeyHash} OP_EQUALVERIFY OP_CHECKSIG +function pubKeyHashOutput(hash) { + assert(Buffer.isBuffer(hash), 'Expected Buffer, got ' + hash) + + return Script.fromChunks([ + opcodes.OP_DUP, + opcodes.OP_HASH160, + hash, + opcodes.OP_EQUALVERIFY, + opcodes.OP_CHECKSIG + ]) +} + +// OP_HASH160 {scriptHash} OP_EQUAL +function scriptHashOutput(hash) { + assert(Buffer.isBuffer(hash), 'Expected Buffer, got ' + hash) + + return Script.fromChunks([ + opcodes.OP_HASH160, + hash, + opcodes.OP_EQUAL + ]) +} + +// m [pubKeys ...] n OP_CHECKMULTISIG +function multisigOutput(m, pubKeys) { + assert(Array.isArray(pubKeys), 'Expected Array, got ' + pubKeys) + assert(pubKeys.length >= m, 'Not enough pubKeys provided') + + var pubKeyBuffers = pubKeys.map(function(pubKey) { + return pubKey.toBuffer() + }) + var n = pubKeys.length + + return Script.fromChunks([].concat( + (opcodes.OP_1 - 1) + m, + pubKeyBuffers, + (opcodes.OP_1 - 1) + n, + opcodes.OP_CHECKMULTISIG + )) +} + +// {signature} +function pubKeyInput(signature) { + assert(Buffer.isBuffer(signature), 'Expected Buffer, got ' + signature) + + return Script.fromChunks([signature]) +} + +// {signature} {pubKey} +function pubKeyHashInput(signature, pubKey) { + assert(Buffer.isBuffer(signature), 'Expected Buffer, got ' + signature) + + return Script.fromChunks([signature, pubKey.toBuffer()]) +} + +// {serialized scriptPubKey script} +function scriptHashInput(scriptSig, scriptPubKey) { + return Script.fromChunks([].concat( + scriptSig.chunks, + scriptPubKey.toBuffer() + )) +} + +// OP_0 [signatures ...] +function multisigInput(signatures, scriptPubKey) { + if (scriptPubKey) { + assert(isMultisig.call(scriptPubKey)) + + var m = scriptPubKey.chunks[0] + var k = m - (opcodes.OP_1 - 1) + assert(k <= signatures.length, 'Not enough signatures provided') + } + + return Script.fromChunks([].concat(opcodes.OP_0, signatures)) +} + +module.exports = { + classifyInput: classifyInput, + classifyOutput: classifyOutput, + multisigInput: multisigInput, + multisigOutput: multisigOutput, + pubKeyHashInput: pubKeyHashInput, + pubKeyHashOutput: pubKeyHashOutput, + pubKeyInput: pubKeyInput, + pubKeyOutput: pubKeyOutput, + scriptHashInput: scriptHashInput, + scriptHashOutput: scriptHashOutput +} diff --git a/src/transaction.js b/src/transaction.js index be0d0bb..d9c83a7 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -5,10 +5,11 @@ var bufferutils = require('./bufferutils') var crypto = require('./crypto') var ecdsa = require('./ecdsa') var opcodes = require('./opcodes') +var scripts = require('./scripts') var Address = require('./address') -var Script = require('./script') var ECKey = require('./eckey') +var Script = require('./script') var DEFAULT_SEQUENCE = 0xffffffff @@ -76,7 +77,7 @@ Transaction.prototype.addInput = function (tx, outIndex) { hash: hash, index: outIndex }, - script: new Script() + script: Script.EMPTY })) } @@ -109,7 +110,7 @@ Transaction.prototype.addOutput = function (address, value) { this.outs.push(new TransactionOut({ value: value, - script: address.toScriptPubKey(), + script: address.toOutputScript(), address: address // TODO: Remove me })) } @@ -133,7 +134,6 @@ Transaction.prototype.toBuffer = function () { var offset = 0 function writeSlice(slice) { - if (Array.isArray(slice)) slice = new Buffer(slice) // FIXME: Performance: transitionary only slice.copy(buffer, offset) offset += slice.length } @@ -196,17 +196,17 @@ var SIGHASH_ANYONECANPAY = 0x80 * hashType, serializes and finally hashes the result. This hash can then be * used to sign the transaction input in question. */ -Transaction.prototype.hashForSignature = function(scriptPubKey, inIndex, hashType) { +Transaction.prototype.hashForSignature = function(prevOutScript, inIndex, hashType) { assert(inIndex >= 0, 'Invalid vin index') assert(inIndex < this.ins.length, 'Invalid vin index') - assert(scriptPubKey instanceof Script, 'Invalid Script object') + assert(prevOutScript instanceof Script, 'Invalid Script object') var txTmp = this.clone() - var hashScript = scriptPubKey.without(opcodes.OP_CODESEPARATOR) + var hashScript = prevOutScript.without(opcodes.OP_CODESEPARATOR) // Blank out other inputs' signatures txTmp.ins.forEach(function(txin) { - txin.script = new Script() + txin.script = Script.EMPTY }) txTmp.ins[inIndex].script = hashScript @@ -239,8 +239,7 @@ Transaction.prototype.getHash = function () { return buffer.toString('hex') } -Transaction.prototype.clone = function () -{ +Transaction.prototype.clone = function () { var newTx = new Transaction() newTx.version = this.version newTx.locktime = this.locktime @@ -337,24 +336,22 @@ Transaction.fromHex = function(hex) { } /** - * Signs a standard output at some index with the given key + * Signs a pubKeyHash output at some index with the given key */ Transaction.prototype.sign = function(index, key, type) { - assert(key instanceof ECKey) - - var script = key.pub.getAddress().toScriptPubKey() - var signature = this.signScriptSig(index, script, key, type) + var prevOutScript = key.pub.getAddress().toOutputScript() + var signature = this.signInput(index, prevOutScript, key, type) // FIXME: Assumed prior TX was pay-to-pubkey-hash - var scriptSig = Script.createPubKeyHashScriptSig(signature, key.pub) - this.setScriptSig(index, scriptSig) + var scriptSig = scripts.pubKeyHashInput(signature, key.pub) + this.setInputScript(index, scriptSig) } -Transaction.prototype.signScriptSig = function(index, scriptPubKey, key, type) { +Transaction.prototype.signInput = function(index, prevOutScript, key, type) { type = type || SIGHASH_ALL assert(key instanceof ECKey, 'Invalid private key') - var hash = this.hashForSignature(scriptPubKey, index, type) + var hash = this.hashForSignature(prevOutScript, index, type) var signature = key.sign(hash) var DERencoded = ecdsa.serializeSig(signature) @@ -364,11 +361,12 @@ Transaction.prototype.signScriptSig = function(index, scriptPubKey, key, type) { ]) } -Transaction.prototype.setScriptSig = function(index, script) { +Transaction.prototype.setInputScript = function(index, script) { this.ins[index].script = script } -Transaction.prototype.validateSig = function(index, script, pub, DERsig) { +// FIXME: should probably be validateInput(index, pub) +Transaction.prototype.validateInput = function(index, script, pub, DERsig) { var type = DERsig.readUInt8(DERsig.length - 1) DERsig = DERsig.slice(0, -1) @@ -410,7 +408,7 @@ TransactionIn.prototype.clone = function () { hash: this.outpoint.hash, index: this.outpoint.index }, - script: this.script.clone(), + script: this.script, sequence: this.sequence }) } @@ -425,7 +423,7 @@ function TransactionOut(data) { TransactionOut.prototype.clone = function() { return new TransactionOut({ - script: this.script.clone(), + script: this.script, value: this.value, address: this.address }) diff --git a/src/wallet.js b/src/wallet.js index 01dc03a..2b708e6 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -152,7 +152,7 @@ function Wallet(seed, network) { var address try { - address = Address.fromScriptPubKey(txOut.script, network).toString() + address = Address.fromOutputScript(txOut.script, network).toString() } catch(e) { if (!(e.message.match(/has no matching Address/))) throw e } diff --git a/test/address.js b/test/address.js index 4e09322..50ff895 100644 --- a/test/address.js +++ b/test/address.js @@ -1,6 +1,7 @@ var assert = require('assert') -var Address = require('../src/address') var networks = require('../src/networks') + +var Address = require('../src/address') var Script = require('../src/script') var fixtures = require('./fixtures/address.json') @@ -37,23 +38,23 @@ describe('Address', function() { }) }) - describe('fromScriptPubKey', function() { + describe('fromOutputScript', function() { fixtures.valid.forEach(function(f) { it('imports ' + f.description + '(' + f.network + ') correctly', function() { var script = Script.fromHex(f.script) - var addr = Address.fromScriptPubKey(script, networks[f.network]) + var addr = Address.fromOutputScript(script, networks[f.network]) assert.equal(addr.version, f.version) assert.equal(addr.hash.toString('hex'), f.hex) }) }) - fixtures.invalid.fromScriptPubKey.forEach(function(f) { + fixtures.invalid.fromOutputScript.forEach(function(f) { it('throws when ' + f.description, function() { var script = Script.fromHex(f.hex) assert.throws(function() { - Address.fromScriptPubKey(script) + Address.fromOutputScript(script) }, new RegExp(f.description)) }) }) @@ -70,22 +71,22 @@ describe('Address', function() { }) }) - describe('toScriptPubKey', function() { + describe('toOutputScript', function() { fixtures.valid.forEach(function(f) { it('imports ' + f.description + '(' + f.network + ') correctly', function() { var addr = Address.fromBase58Check(f.base58check) - var script = addr.toScriptPubKey() + var script = addr.toOutputScript() assert.equal(script.toHex(), f.script) }) }) - fixtures.invalid.toScriptPubKey.forEach(function(f) { + fixtures.invalid.toOutputScript.forEach(function(f) { it('throws when ' + f.description, function() { var addr = new Address(new Buffer(f.hex, 'hex'), f.version) assert.throws(function() { - addr.toScriptPubKey() + addr.toOutputScript() }, new RegExp(f.description)) }) }) diff --git a/test/bitcoin.core.js b/test/bitcoin.core.js index b246f64..4cd8238 100644 --- a/test/bitcoin.core.js +++ b/test/bitcoin.core.js @@ -56,10 +56,10 @@ describe('Bitcoin-core', function() { assert.equal(address.hash.toString('hex'), hex) if (params.addrType === 'pubkey') { - assert.equal(address.version, network.pubKeyHash) + assert.equal(address.version, network.pubkeyhash) } else if (params.addrType === 'script') { - assert.equal(address.version, network.scriptHash) + assert.equal(address.version, network.scripthash) } }) }) @@ -68,10 +68,10 @@ describe('Bitcoin-core', function() { // base58_keys_invalid describe('Address', function() { var allowedNetworks = [ - networks.bitcoin.pubKeyHash, - networks.bitcoin.scriptHash, - networks.testnet.pubKeyHash, - networks.testnet.scriptHash + networks.bitcoin.pubkeyhash, + networks.bitcoin.scripthash, + networks.testnet.pubkeyhash, + networks.testnet.scripthash ] base58_keys_invalid.forEach(function(f) { diff --git a/test/ecpubkey.js b/test/ecpubkey.js index 62609ef..e6dcbbd 100644 --- a/test/ecpubkey.js +++ b/test/ecpubkey.js @@ -72,7 +72,7 @@ describe('ECPubKey', function() { var pubKey = new ECPubKey(Q) var address = pubKey.getAddress(networks.testnet) - assert.equal(address.version, networks.testnet.pubKeyHash) + assert.equal(address.version, networks.testnet.pubkeyhash) assert.equal(address.hash.toString('hex'), fixtures.compressed.hash160) }) }) diff --git a/test/fixtures/address.json b/test/fixtures/address.json index 06557af..cedd17d 100644 --- a/test/fixtures/address.json +++ b/test/fixtures/address.json @@ -46,7 +46,7 @@ "exception": "Invalid hash length" } ], - "fromScriptPubKey": [ + "fromOutputScript": [ { "description": "pubkey has no matching Address", "hex": "21031f1e68f82112b373f0fe980b3a89d212d2b5c01fb51eb25acb8b4c4b4299ce95ac" @@ -60,9 +60,9 @@ "hex": "6a2606deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474" } ], - "toScriptPubKey": [ + "toOutputScript": [ { - "description": "24kPZCmVgzfkpGdXExy56234MRHrsqQxNWE has no matching script", + "description": "24kPZCmVgzfkpGdXExy56234MRHrsqQxNWE has no matching Script", "hex": "751e76e8199196d454941c45d1b3a323f1433bd6", "version": 153 } diff --git a/test/fixtures/script.json b/test/fixtures/script.json index bb42dbe..a393444 100644 --- a/test/fixtures/script.json +++ b/test/fixtures/script.json @@ -33,6 +33,15 @@ "asm": "72 304502206becda98cecf7a545d1a640221438ff8912d9b505ede67e0138485111099f696022100ccd616072501310acba10feb97cecc918e21c8e92760cd35144efec7622938f301 65 040cd2d2ce17a1e9b2b3b2cb294d40eecf305a25b7e7bfdafae6bb2639f4ee399b3637706c3d377ec4ab781355add443ae864b134c5e523001c442186ea60f0eb8", "scriptPubKey": false }, + { + "description": "pubKey scriptSig", + "hex": "48304502206becda98cecf7a545d1a640221438ff8912d9b505ede67e0138485111099f696022100ccd616072501310acba10feb97cecc918e21c8e92760cd35144efec7622938f301", + "type": "pubkey", + "hash": "44d9982c3e79452e02ef5816976a0e20a0ec1cba", + "signature": "304502206becda98cecf7a545d1a640221438ff8912d9b505ede67e0138485111099f696022100ccd616072501310acba10feb97cecc918e21c8e92760cd35144efec7622938f301", + "asm": "72 304502206becda98cecf7a545d1a640221438ff8912d9b505ede67e0138485111099f696022100ccd616072501310acba10feb97cecc918e21c8e92760cd35144efec7622938f301", + "scriptPubKey": false + }, { "description": "Valid multisig script", "hex": "5121032487c2a32f7c8d57d2a93906a6457afd00697925b0e6e145d89af6d3bca330162102308673d16987eaa010e540901cc6fe3695e758c19f46ce604e174dac315e685a52ae", @@ -41,6 +50,14 @@ "asm": "OP_TRUE 33 032487c2a32f7c8d57d2a93906a6457afd00697925b0e6e145d89af6d3bca33016 33 02308673d16987eaa010e540901cc6fe3695e758c19f46ce604e174dac315e685a OP_2 OP_CHECKMULTISIG", "scriptPubKey": true }, + { + "description": "mutisig scriptSig", + "hex": "0047304402202b29881db1b4cc128442d955e906d41c77365ed9a8392b584be12d980b236459022009da4bc60d09280aa26f4f981bfbed94eb7263d92920961e48a7f3f0991895b101483045022100871708a7597c1dbebff2a5527a56a1f2b49d73e35cd825a07285f5f29f5766d8022003bd7ac25334e9a6d6020cc8ba1be67a8c70dca8e7063ea0547d79c45b9bc12601", + "type": "multisig", + "hash": "b1ef3ae2c77b356eff81049aad7dfd2eeb34c6f5", + "asm": "00 47 304402202b29881db1b4cc128442d955e906d41c77365ed9a8392b584be12d980b236459022009da4bc60d09280aa26f4f981bfbed94eb7263d92920961e48a7f3f0991895b101 48 3045022100871708a7597c1dbebff2a5527a56a1f2b49d73e35cd825a07285f5f29f5766d8022003bd7ac25334e9a6d6020cc8ba1be67a8c70dca8e7063ea0547d79c45b9bc12601", + "scriptPubKey": false + }, { "description": "OP_RETURN script", "hex":"6a2606deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474", diff --git a/test/hdnode.js b/test/hdnode.js index 44ef320..fb9a29f 100644 --- a/test/hdnode.js +++ b/test/hdnode.js @@ -204,7 +204,7 @@ describe('HDNode', function() { var hd = HDNode.fromBase58(f.master.base58) hd.network = networks.testnet - assert.equal(hd.getAddress().version, networks.testnet.pubKeyHash) + assert.equal(hd.getAddress().version, networks.testnet.pubkeyhash) }) }) diff --git a/test/integration/p2sh.js b/test/integration/p2sh.js index dbc8fe8..ff7946c 100644 --- a/test/integration/p2sh.js +++ b/test/integration/p2sh.js @@ -3,6 +3,7 @@ var assert = require('assert') var bitcoin = require('../../') var crypto = bitcoin.crypto var networks = bitcoin.networks +var scripts = bitcoin.scripts var Address = bitcoin.Address var ECKey = bitcoin.ECKey @@ -28,10 +29,10 @@ describe('Bitcoin-js', function() { var outputAmount = 1e4 var pubKeys = privKeys.map(function(eck) { return eck.pub }) - var redeemScript = Script.createMultisigScriptPubKey(2, pubKeys) - var scriptPubKey = Script.createP2SHScriptPubKey(redeemScript.getHash()) + var redeemScript = scripts.multisigOutput(2, pubKeys) + var scriptPubKey = scripts.scriptHashOutput(redeemScript.getHash()) - var multisigAddress = Address.fromScriptPubKey(scriptPubKey, networks.testnet).toString() + var multisigAddress = Address.fromOutputScript(scriptPubKey, networks.testnet).toString() // Attempt to send funds to the source address, providing some unspents for later helloblock.faucet.withdraw(multisigAddress, coldAmount, function(err) { @@ -54,12 +55,12 @@ describe('Bitcoin-js', function() { tx.addOutput(targetAddress, spendAmount) var signatures = privKeys.map(function(privKey) { - return tx.signScriptSig(0, redeemScript, privKey) + return tx.signInput(0, redeemScript, privKey) }) - var redeemScriptSig = Script.createMultisigScriptSig(signatures) - var scriptSig = Script.createP2SHScriptSig(redeemScriptSig, redeemScript) - tx.setScriptSig(0, scriptSig) + var redeemScriptSig = scripts.multisigInput(signatures) + var scriptSig = scripts.scriptHashInput(redeemScriptSig, redeemScript) + tx.setInputScript(0, scriptSig) // broadcast our transaction helloblock.transactions.propagate(tx.toHex(), function(err, resp, resource) { diff --git a/test/script.js b/test/script.js index a1ff8cc..bb9b9b9 100644 --- a/test/script.js +++ b/test/script.js @@ -1,29 +1,23 @@ var assert = require('assert') -var crypto = require('../src/crypto') -var networks = require('../src/networks') var opcodes = require('../src/opcodes') -var Address = require('../src/address') -var ECPubKey = require('../src/ecpubkey') var Script = require('../src/script') var fixtures = require('./fixtures/script.json') -function b2h(b) { return new Buffer(b).toString('hex') } -function h2b(h) { return new Buffer(h, 'hex') } - describe('Script', function() { describe('constructor', function() { - it('works for a byte array', function() { - assert.ok(new Script([])) - }) + it('accepts valid parameters', function() { + var buffer = new Buffer([1]) + var chunks = [1] + var script = new Script(buffer, chunks) - it('works when nothing is passed in', function() { - assert.ok(new Script()) + assert.equal(script.buffer, buffer) + assert.equal(script.chunks, chunks) }) it('throws an error when input is not an array', function() { - assert.throws(function(){ new Script({}) }, /Expected Array, got/) + assert.throws(function(){ new Script({}) }, /Expected Buffer, got/) }) }) @@ -45,129 +39,18 @@ describe('Script', function() { }) }) - describe('getInType', function() { - fixtures.valid.forEach(function(f) { - if (!f.scriptPubKey) { - it('supports ' + f.description, function() { - var script = Script.fromHex(f.hex) - - assert.equal(script.getInType(), f.type) - }) - } - }) - }) - - describe('getOutType', function() { - fixtures.valid.forEach(function(f) { - if (f.scriptPubKey) { - it('supports ' + f.description, function() { - var script = Script.fromHex(f.hex) - - assert.equal(script.getOutType(), f.type) - }) - } - }) - }) - - describe('pay-to-pubKeyHash', function() { - it('matches the test data', function() { - // FIXME: bad - var f = fixtures.valid[2] - var address = Address.fromBase58Check('19E6FV3m3kEPoJD5Jz6dGKdKwTVvjsWUvu') - var script = Script.createPubKeyHashScriptPubKey(address.hash) - - assert.equal(script.toHex(), f.hex) - }) - }) - - describe('pay-to-pubkey', function() { - it('matches the test data', function() { - // FIXME: bad - var f = fixtures.valid[0] - var pubKey = ECPubKey.fromHex(f.pubKey) - var script = Script.createPubKeyScriptPubKey(pubKey) - - assert.equal(script.toHex(), f.hex) - }) - }) - - describe('pay-to-scriptHash', function() { - it('matches the test data', function() { - // FIXME: bad - var f = fixtures.valid[1] - var address = Address.fromBase58Check('3NukJ6fYZJ5Kk8bPjycAnruZkE5Q7UW7i8') - var script = Script.createP2SHScriptPubKey(address.hash) - - assert.equal(script.toHex(), f.hex) - }) - }) - - describe('2-of-3 Multi-Signature scriptPubKey', function() { - var pubKeys - - beforeEach(function() { - pubKeys = [ - '02ea1297665dd733d444f31ec2581020004892cdaaf3dd6c0107c615afb839785f', - '02fab2dea1458990793f56f42e4a47dbf35a12a351f26fa5d7e0cc7447eaafa21f', - '036c6802ce7e8113723dd92cdb852e492ebb157a871ca532c3cb9ed08248ff0e19' - ].map(ECPubKey.fromHex) - }) - - it('should create valid redeemScript', function() { - var redeemScript = Script.createMultisigScriptPubKey(2, pubKeys) - - var hash160 = crypto.hash160(new Buffer(redeemScript.buffer)) - var multisigAddress = new Address(hash160, networks.bitcoin.scriptHash) - - assert.equal(multisigAddress.toString(), '32vYjxBb7pHJJyXgNk8UoK3BdRDxBzny2v') - }) - - it('should throw on not enough pubKeys provided', function() { - assert.throws(function() { - Script.createMultisigScriptPubKey(4, pubKeys) - }, /Not enough pubKeys provided/) - }) - }) - - describe('2-of-2 Multisig scriptSig', function() { - var pubKeys = [ - '02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1', - '0395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a' - ].map(ECPubKey.fromHex) - var signatures = [ - '304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801', - '3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501' - ].map(h2b) - var expected = '0047304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801483045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d14050147522102359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1210395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a52ae' - - it('should create a valid P2SH multisig scriptSig', function() { - var redeemScript = Script.createMultisigScriptPubKey(2, pubKeys) - var redeemScriptSig = Script.createMultisigScriptSig(signatures) - - var scriptSig = Script.createP2SHScriptSig(redeemScriptSig, redeemScript) - - assert.equal(b2h(scriptSig.buffer), expected) - }) - - it('should throw on not enough signatures', function() { - var redeemScript = Script.createMultisigScriptPubKey(2, pubKeys) - - assert.throws(function() { - Script.createMultisigScriptSig(signatures.slice(1), redeemScript) - }, /Not enough signatures provided/) - }) - }) - describe('fromChunks', function() { it('should match expected behaviour', function() { var hash = new Buffer(32) + hash.fill(0) + var script = Script.fromChunks([ opcodes.OP_HASH160, hash, opcodes.OP_EQUAL ]) - assert.deepEqual(script, Script.createP2SHScriptPubKey(hash)) + assert.equal(script.toHex(), 'a920000000000000000000000000000000000000000000000000000000000000000087') }) }) diff --git a/test/scripts.js b/test/scripts.js new file mode 100644 index 0000000..19222e5 --- /dev/null +++ b/test/scripts.js @@ -0,0 +1,144 @@ +var assert = require('assert') +var crypto = require('../src/crypto') +var networks = require('../src/networks') +var scripts = require('../src/scripts') + +var Address = require('../src/address') +var ECPubKey = require('../src/ecpubkey') +var Script = require('../src/script') + +var fixtures = require('./fixtures/script.json') + +function b2h(b) { return new Buffer(b).toString('hex') } +function h2b(h) { return new Buffer(h, 'hex') } + +describe('Scripts', function() { + describe('classifyInput', function() { + fixtures.valid.forEach(function(f) { + if (f.scriptPubKey) return + + it('supports ' + f.type, function() { + var script = Script.fromHex(f.hex) + var type = scripts.classifyInput(script) + + assert.equal(type, f.type) + }) + }) + }) + + describe('classifyOutput', function() { + fixtures.valid.forEach(function(f) { + if (!f.scriptPubKey) return + + it('supports ' + f.type, function() { + var script = Script.fromHex(f.hex) + var type = scripts.classifyOutput(script) + + assert.equal(type, f.type) + }) + }) + }) + + // FIXME: bad + describe('pay-to-pubKeyHash', function() { + it('matches the test data', function() { + var f = fixtures.valid[2] + var address = Address.fromBase58Check('19E6FV3m3kEPoJD5Jz6dGKdKwTVvjsWUvu') + var script = scripts.pubKeyHashOutput(address.hash) + + assert.equal(script.toHex(), f.hex) + }) + }) + + // FIXME: bad + describe('pay-to-pubkey', function() { + describe('input', function() { + it('matches the test data', function() { + var f = fixtures.valid[4] + var signature = new Buffer(f.signature, 'hex') + var script = scripts.pubKeyInput(signature) + + assert.equal(script.toHex(), f.hex) + }) + }) + + describe('output', function() { + it('matches the test data', function() { + var f = fixtures.valid[0] + var pubKey = ECPubKey.fromHex(f.pubKey) + var script = scripts.pubKeyOutput(pubKey) + + assert.equal(script.toHex(), f.hex) + }) + }) + }) + + // FIXME: bad + describe('pay-to-scriptHash', function() { + it('matches the test data', function() { + var f = fixtures.valid[1] + var address = Address.fromBase58Check('3NukJ6fYZJ5Kk8bPjycAnruZkE5Q7UW7i8') + var script = scripts.scriptHashOutput(address.hash) + + assert.equal(script.toHex(), f.hex) + }) + }) + + // FIXME: bad + describe('2-of-3 Multi-Signature scriptPubKey', function() { + var pubKeys + + beforeEach(function() { + pubKeys = [ + '02ea1297665dd733d444f31ec2581020004892cdaaf3dd6c0107c615afb839785f', + '02fab2dea1458990793f56f42e4a47dbf35a12a351f26fa5d7e0cc7447eaafa21f', + '036c6802ce7e8113723dd92cdb852e492ebb157a871ca532c3cb9ed08248ff0e19' + ].map(ECPubKey.fromHex) + }) + + it('should create valid redeemScript', function() { + var redeemScript = scripts.multisigOutput(2, pubKeys) + + var hash160 = crypto.hash160(new Buffer(redeemScript.buffer)) + var multisigAddress = new Address(hash160, networks.bitcoin.scripthash) + + assert.equal(multisigAddress.toString(), '32vYjxBb7pHJJyXgNk8UoK3BdRDxBzny2v') + }) + + it('should throw on not enough pubKeys provided', function() { + assert.throws(function() { + scripts.multisigOutput(4, pubKeys) + }, /Not enough pubKeys provided/) + }) + }) + + // FIXME: bad + describe('2-of-2 Multisig scriptSig', function() { + var pubKeys = [ + '02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1', + '0395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a' + ].map(ECPubKey.fromHex) + var signatures = [ + '304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801', + '3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501' + ].map(h2b) + var expected = '0047304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801483045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d14050147522102359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1210395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a52ae' + + it('should create a valid P2SH multisig scriptSig', function() { + var redeemScript = scripts.multisigOutput(2, pubKeys) + var redeemInput = scripts.multisigInput(signatures) + + var scriptSig = scripts.scriptHashInput(redeemInput, redeemScript) + + assert.equal(b2h(scriptSig.buffer), expected) + }) + + it('should throw on not enough signatures', function() { + var redeemScript = scripts.multisigOutput(2, pubKeys) + + assert.throws(function() { + scripts.multisigInput(signatures.slice(1), redeemScript) + }, /Not enough signatures provided/) + }) + }) +}) diff --git a/test/transaction.js b/test/transaction.js index e22f7f8..e9bbec6 100644 --- a/test/transaction.js +++ b/test/transaction.js @@ -1,5 +1,6 @@ var assert = require('assert') var networks = require('../src/networks') +var scripts = require('../src/scripts') var Address = require('../src/address') var ECKey = require('../src/eckey') @@ -70,7 +71,7 @@ describe('Transaction', function() { var output = tx.outs[0] assert.equal(output.value, 5000000000) - assert.deepEqual(output.script, Address.fromBase58Check('n1gqLjZbRH1biT5o4qiVMiNig8wcCPQeB9').toScriptPubKey()) + assert.deepEqual(output.script, Address.fromBase58Check('n1gqLjZbRH1biT5o4qiVMiNig8wcCPQeB9').toOutputScript()) }) it('assigns hash to deserialized object', function(){ @@ -131,7 +132,7 @@ describe('Transaction', function() { verifyTransactionIn() }) - function verifyTransactionIn(){ + function verifyTransactionIn() { assert.equal(tx.ins.length, 1) var input = tx.ins[0] @@ -140,7 +141,7 @@ describe('Transaction', function() { assert.equal(input.outpoint.index, 0) assert.equal(input.outpoint.hash, "0cb859105100ebc3344f749c835c7af7d7103ec0d8cbc3d8ccbd5d28c3c36b57") - assert.deepEqual(input.script.buffer, []) + assert.equal(input.script, Script.EMPTY) } }) @@ -199,11 +200,11 @@ describe('Transaction', function() { var script = prevTx.outs[0].script var sig = new Buffer(tx.ins[0].script.chunks[0]) - assert.equal(tx.validateSig(0, script, key.pub, sig), true) + assert.equal(tx.validateInput(0, script, key.pub, sig), true) }) }) - describe('validateSig', function(){ + describe('validateInput', function(){ var validTx beforeEach(function() { @@ -215,7 +216,7 @@ describe('Transaction', function() { var script = prevTx.outs[0].script var sig = new Buffer(validTx.ins[0].script.chunks[0]) - assert.equal(validTx.validateSig(0, script, key.pub, sig), true) + assert.equal(validTx.validateInput(0, script, key.pub, sig), true) }) }) @@ -242,7 +243,7 @@ describe('Transaction', function() { }) }) - describe('signScriptSig', function() { + describe('signInput', function() { it('works for multi-sig redeem script', function() { var tx = new Transaction() tx.addInput('d6f72aab8ff86ff6289842a0424319bf2ddba85dc7c52757912297f948286389', 0) @@ -255,18 +256,18 @@ describe('Transaction', function() { return ECKey.fromWIF(wif) }) var pubKeys = privKeys.map(function(eck) { return eck.pub }) - var redeemScript = Script.createMultisigScriptPubKey(2, pubKeys) + var redeemScript = scripts.multisigOutput(2, pubKeys) var signatures = privKeys.map(function(privKey) { - return tx.signScriptSig(0, redeemScript, privKey) + return tx.signInput(0, redeemScript, privKey) }) - var redeemScriptSig = Script.createMultisigScriptSig(signatures) - var scriptSig = Script.createP2SHScriptSig(redeemScriptSig, redeemScript) - tx.setScriptSig(0, scriptSig) + var redeemScriptSig = scripts.multisigInput(signatures) + var scriptSig = scripts.scriptHashInput(redeemScriptSig, redeemScript) + tx.setInputScript(0, scriptSig) signatures.forEach(function(sig, i){ - assert(tx.validateSig(0, redeemScript, privKeys[i].pub, sig)) + assert(tx.validateInput(0, redeemScript, privKeys[i].pub, sig)) }) var expected = '010000000189632848f99722915727c5c75da8db2dbf194342a0429828f66ff88fab2af7d600000000fd1b0100483045022100e5be20d440b2bbbc886161f9095fa6d0bca749a4e41d30064f30eb97adc7a1f5022061af132890d8e4e90fedff5e9365aeeb77021afd8ef1d5c114d575512e9a130a0147304402205054e38e9d7b5c10481b6b4991fde5704cd94d49e344406e3c2ce4d18a43bf8e022051d7ba8479865b53a48bee0cce86e89a25633af5b2918aa276859489e232f51c014c8752410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52aeffffffff0101000000000000001976a914751e76e8199196d454941c45d1b3a323f1433bd688ac00000000' diff --git a/test/wallet.js b/test/wallet.js index cd35236..0d857ad 100644 --- a/test/wallet.js +++ b/test/wallet.js @@ -2,10 +2,10 @@ var assert = require('assert') var crypto = require('../src/crypto') var networks = require('../src/networks') var sinon = require('sinon') +var scripts = require('../src/scripts') var Address = require('../src/address') var HDNode = require('../src/hdnode') -var Script = require('../src/script') var Transaction = require('../src/transaction').Transaction var Wallet = require('../src/wallet') @@ -266,7 +266,7 @@ describe('Wallet', function() { it('does not fail on scripts with no corresponding Address', function() { var pubKey = wallet.getPrivateKey(0).pub - var script = Script.createPubKeyScriptPubKey(pubKey) + var script = scripts.pubKeyOutput(pubKey) var tx2 = new Transaction() tx2.addInput(fakeTxHash(1), 0) @@ -346,7 +346,7 @@ describe('Wallet', function() { assert.equal(output.value, txOut.value) assert.equal(output.pending, pending) - var txOutAddress = Address.fromScriptPubKey(txOut.script).toString() + var txOutAddress = Address.fromOutputScript(txOut.script).toString() assert.equal(output.address, txOutAddress) } })