From 2384164ab8b110d9bfac5fddfd53844862c13ca9 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Mon, 7 Mar 2016 20:49:55 -0500 Subject: [PATCH 01/23] transaction: start to implement segwit serialization --- lib/transaction/transaction.js | 37 +++++++++++++++++++++++++----- test/data/bitcoind/tx_invalid.json | 3 --- test/transaction/transaction.js | 17 ++++++++++++++ 3 files changed, 48 insertions(+), 9 deletions(-) diff --git a/lib/transaction/transaction.js b/lib/transaction/transaction.js index 276b7fb1c..3b6eb527e 100644 --- a/lib/transaction/transaction.js +++ b/lib/transaction/transaction.js @@ -38,6 +38,8 @@ function Transaction(serialized) { } this.inputs = []; this.outputs = []; + this.witness = []; + this._hasWitness = false; this._inputAmount = undefined; this._outputAmount = undefined; @@ -57,7 +59,7 @@ function Transaction(serialized) { this._newTransaction(); } } - +var SERIALIZE_TRANSACTION_WITNESS = 0x40000000; var CURRENT_VERSION = 1; var DEFAULT_NLOCKTIME = 0; var MAX_BLOCK_SIZE = 1000000; @@ -299,22 +301,45 @@ Transaction.prototype.fromBuffer = function(buffer) { Transaction.prototype.fromBufferReader = function(reader) { $.checkArgument(!reader.finished(), 'No transaction data received'); - var i, sizeTxIns, sizeTxOuts; this.version = reader.readInt32LE(); - sizeTxIns = reader.readVarintNum(); - for (i = 0; i < sizeTxIns; i++) { + var sizeTxIns = reader.readVarintNum(); + + // check for segwit + this._hasWitness = false; + if (sizeTxIns === 0 && reader.buf[reader.pos] !== 0) { + reader.pos += 1; + this._hasWitness = true; + sizeTxIns = reader.readVarintNum(); + } + + for (var i = 0; i < sizeTxIns; i++) { var input = Input.fromBufferReader(reader); this.inputs.push(input); } - sizeTxOuts = reader.readVarintNum(); - for (i = 0; i < sizeTxOuts; i++) { + + var sizeTxOuts = reader.readVarintNum(); + for (var j = 0; j < sizeTxOuts; j++) { this.outputs.push(Output.fromBufferReader(reader)); } + + if (this._hasWitness) { + this._fromBufferReaderScriptWitnesses(reader); + } + this.nLockTime = reader.readUInt32LE(); return this; }; +Transaction.prototype._fromBufferReaderScriptWitnesses = function(reader) { + var itemCount = reader.readVarintNum(); + for (var i = 0; i < itemCount; i++) { + var size = reader.readVarintNum(); + var item = reader.read(size); + this.witness.push(item); + } +}; + Transaction.prototype.toObject = Transaction.prototype.toJSON = function toObject() { var inputs = []; this.inputs.forEach(function(input) { diff --git a/test/data/bitcoind/tx_invalid.json b/test/data/bitcoind/tx_invalid.json index a9f5ff0a2..e30498533 100644 --- a/test/data/bitcoind/tx_invalid.json +++ b/test/data/bitcoind/tx_invalid.json @@ -24,9 +24,6 @@ "010000000100010000000000000000000000000000000000000000000000000000000000000000000009085768617420697320ffffffff010000000000000000015100000000", "P2SH"], ["Tests for CheckTransaction()"], -["No inputs"], -[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0x7a052c840ba73af26755de42cf01cc9e0a49fef0 EQUAL"]], -"0100000000010000000000000000015100000000", "P2SH"], ["No outputs"], [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0x05ab9e14d983742513f0f451e105ffb4198d1dd4 EQUAL"]], diff --git a/test/transaction/transaction.js b/test/transaction/transaction.js index 994150dec..0504b5c4e 100644 --- a/test/transaction/transaction.js +++ b/test/transaction/transaction.js @@ -1226,6 +1226,23 @@ describe('Transaction', function() { }); }); }); + + describe('Segregated Witness', function() { + it('identify as segwit transaction', function() { + // https://github.com/bitcoin/bips/blob/master/bip-0144.mediawiki + var version = new Buffer('01000000', 'hex'); + var marker = new Buffer('00', 'hex'); //always zero + var flag = new Buffer('01', 'hex'); //non zero + var inputCount = new Buffer('00', 'hex'); + var outputCount = new Buffer('00', 'hex'); + var witness = new Buffer('00', 'hex'); + var locktime = new Buffer('00000000', 'hex'); + var txBuffer = Buffer.concat([version, marker, flag, inputCount, outputCount, witness, locktime]); + var tx = bitcore.Transaction().fromBuffer(txBuffer); + tx._hasWitness.should.equal(true); + }); + }); + }); From cccf07674a4838a39c412c235cf1efa0324d8994 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Tue, 8 Mar 2016 18:16:24 -0500 Subject: [PATCH 02/23] transaction: calculate witness hash --- lib/transaction/transaction.js | 43 +++++++++++++++++++++++++++++---- test/transaction/transaction.js | 6 +++++ 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/lib/transaction/transaction.js b/lib/transaction/transaction.js index 3b6eb527e..4dc66f4a7 100644 --- a/lib/transaction/transaction.js +++ b/lib/transaction/transaction.js @@ -107,6 +107,16 @@ var hashProperty = { return new BufferReader(this._getHash()).readReverse().toString('hex'); } }; + +var witnessHashProperty = { + configurable: false, + enumerable: true, + get: function() { + return new BufferReader(this._getWitnessHash()).readReverse().toString('hex'); + } +}; + +Object.defineProperty(Transaction.prototype, 'witnessHash', witnessHashProperty); Object.defineProperty(Transaction.prototype, 'hash', hashProperty); Object.defineProperty(Transaction.prototype, 'id', hashProperty); @@ -128,7 +138,15 @@ Object.defineProperty(Transaction.prototype, 'outputAmount', ioProperty); * @return {Buffer} */ Transaction.prototype._getHash = function() { - return Hash.sha256sha256(this.toBuffer()); + return Hash.sha256sha256(this.toBuffer(true)); +}; + +/** + * Retrieve the little endian hash of the transaction including witness data + * @return {Buffer} + */ +Transaction.prototype._getWitnessHash = function() { + return Hash.sha256sha256(this.toBuffer(false)); }; /** @@ -275,13 +293,17 @@ Transaction.prototype.inspect = function() { return ''; }; -Transaction.prototype.toBuffer = function() { +Transaction.prototype.toBuffer = function(nonWitness) { var writer = new BufferWriter(); - return this.toBufferWriter(writer).toBuffer(); + return this.toBufferWriter(writer, nonWitness).toBuffer(); }; -Transaction.prototype.toBufferWriter = function(writer) { - writer.writeInt32LE(this.version); +Transaction.prototype.toBufferWriter = function(writer, nonWitness) { + writer.writeInt32LE(this.version); + writer.writeUInt32LE(this.version); + if (this._hasWitness && !nonWitness) { + writer.write(new Buffer('0001', 'hex')); + } writer.writeVarintNum(this.inputs.length); _.each(this.inputs, function(input) { input.toBufferWriter(writer); @@ -290,10 +312,21 @@ Transaction.prototype.toBufferWriter = function(writer) { _.each(this.outputs, function(output) { output.toBufferWriter(writer); }); + if (this._hasWitness && !nonWitness) { + this._toBufferWriterWitness(writer); + } writer.writeUInt32LE(this.nLockTime); return writer; }; +Transaction.prototype._toBufferWriterWitness = function(writer) { + writer.writeVarintNum(this.witness.length); + for (var i = 0; i < this.witness.length; i++) { + writer.writeVarintNum(this.witness[i].length); + writer.write(this.witness[i]); + } +}; + Transaction.prototype.fromBuffer = function(buffer) { var reader = new BufferReader(buffer); return this.fromBufferReader(reader); diff --git a/test/transaction/transaction.js b/test/transaction/transaction.js index 0504b5c4e..58d16fe4f 100644 --- a/test/transaction/transaction.js +++ b/test/transaction/transaction.js @@ -1241,6 +1241,12 @@ describe('Transaction', function() { var tx = bitcore.Transaction().fromBuffer(txBuffer); tx._hasWitness.should.equal(true); }); + it('correctly calculate hash for segwit transaction', function() { + var txBuffer = new Buffer('01000000000101b0e5caa7e37d4b8530c3e1071a36dd5e05d1065cf7224ddff42c69e3387689870000000000ffffffff017b911100000000001600144ff831574da8bef07f8bc97244a1666147b071570247304402203fcbcfddbd6ca3a90252610dd63f1be50b2d926b8d87c912da0a3e42bb03fba002202a90c8aad75da22b0549c72618b754114583e934c0b0d2ccd6c13fcd859ba4ed01210363f3f47f4555779de405eab8d0dc8c2a4f3e09f4171a3fa47c7a77715795319800000000', 'hex'); + var tx = bitcore.Transaction().fromBuffer(txBuffer); + tx.hash.should.equal('7f1a2d46746f1bfbb22ab797d5aad1fd9723477b417fa34dff73d8a7dbb14570'); + tx.witnessHash.should.equal('3c26fc8b5cfe65f96d955cecfe4d11db2659d052171f9f31af043e9f5073e46b'); + }); }); }); From 1e749e00554845f9cdc25e74c10fb35564eb0c74 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Wed, 9 Mar 2016 10:36:06 -0500 Subject: [PATCH 03/23] transaction: refactor witness properties --- lib/transaction/transaction.js | 26 ++++++++++++++------------ test/transaction/transaction.js | 7 ++++--- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/lib/transaction/transaction.js b/lib/transaction/transaction.js index 4dc66f4a7..3fe454bbe 100644 --- a/lib/transaction/transaction.js +++ b/lib/transaction/transaction.js @@ -39,7 +39,6 @@ function Transaction(serialized) { this.inputs = []; this.outputs = []; this.witness = []; - this._hasWitness = false; this._inputAmount = undefined; this._outputAmount = undefined; @@ -59,7 +58,6 @@ function Transaction(serialized) { this._newTransaction(); } } -var SERIALIZE_TRANSACTION_WITNESS = 0x40000000; var CURRENT_VERSION = 1; var DEFAULT_NLOCKTIME = 0; var MAX_BLOCK_SIZE = 1000000; @@ -293,15 +291,18 @@ Transaction.prototype.inspect = function() { return ''; }; -Transaction.prototype.toBuffer = function(nonWitness) { +Transaction.prototype.toBuffer = function(noWitness) { var writer = new BufferWriter(); - return this.toBufferWriter(writer, nonWitness).toBuffer(); + return this.toBufferWriter(writer, noWitness).toBuffer(); }; -Transaction.prototype.toBufferWriter = function(writer, nonWitness) { - writer.writeInt32LE(this.version); - writer.writeUInt32LE(this.version); - if (this._hasWitness && !nonWitness) { +Transaction.prototype.hasWitness = function() { + return this.witness && this.witness.length > 0; +}; + +Transaction.prototype.toBufferWriter = function(writer, noWitness) { + writer.writeInt32LE(this.version); + if (this.hasWitness() && !noWitness) { writer.write(new Buffer('0001', 'hex')); } writer.writeVarintNum(this.inputs.length); @@ -312,7 +313,7 @@ Transaction.prototype.toBufferWriter = function(writer, nonWitness) { _.each(this.outputs, function(output) { output.toBufferWriter(writer); }); - if (this._hasWitness && !nonWitness) { + if (this.hasWitness() && !noWitness) { this._toBufferWriterWitness(writer); } writer.writeUInt32LE(this.nLockTime); @@ -339,10 +340,10 @@ Transaction.prototype.fromBufferReader = function(reader) { var sizeTxIns = reader.readVarintNum(); // check for segwit - this._hasWitness = false; + var hasWitness = false; if (sizeTxIns === 0 && reader.buf[reader.pos] !== 0) { reader.pos += 1; - this._hasWitness = true; + hasWitness = true; sizeTxIns = reader.readVarintNum(); } @@ -356,7 +357,7 @@ Transaction.prototype.fromBufferReader = function(reader) { this.outputs.push(Output.fromBufferReader(reader)); } - if (this._hasWitness) { + if (hasWitness) { this._fromBufferReaderScriptWitnesses(reader); } @@ -366,6 +367,7 @@ Transaction.prototype.fromBufferReader = function(reader) { Transaction.prototype._fromBufferReaderScriptWitnesses = function(reader) { var itemCount = reader.readVarintNum(); + this.witness = []; for (var i = 0; i < itemCount; i++) { var size = reader.readVarintNum(); var item = reader.read(size); diff --git a/test/transaction/transaction.js b/test/transaction/transaction.js index 58d16fe4f..b13e61012 100644 --- a/test/transaction/transaction.js +++ b/test/transaction/transaction.js @@ -1235,11 +1235,12 @@ describe('Transaction', function() { var flag = new Buffer('01', 'hex'); //non zero var inputCount = new Buffer('00', 'hex'); var outputCount = new Buffer('00', 'hex'); - var witness = new Buffer('00', 'hex'); + var witness = new Buffer('01', 'hex'); + var witnessItems = new Buffer('00', 'hex'); var locktime = new Buffer('00000000', 'hex'); - var txBuffer = Buffer.concat([version, marker, flag, inputCount, outputCount, witness, locktime]); + var txBuffer = Buffer.concat([version, marker, flag, inputCount, outputCount, witness, witnessItems, locktime]); var tx = bitcore.Transaction().fromBuffer(txBuffer); - tx._hasWitness.should.equal(true); + tx.hasWitness().should.equal(true); }); it('correctly calculate hash for segwit transaction', function() { var txBuffer = new Buffer('01000000000101b0e5caa7e37d4b8530c3e1071a36dd5e05d1065cf7224ddff42c69e3387689870000000000ffffffff017b911100000000001600144ff831574da8bef07f8bc97244a1666147b071570247304402203fcbcfddbd6ca3a90252610dd63f1be50b2d926b8d87c912da0a3e42bb03fba002202a90c8aad75da22b0549c72618b754114583e934c0b0d2ccd6c13fcd859ba4ed01210363f3f47f4555779de405eab8d0dc8c2a4f3e09f4171a3fa47c7a77715795319800000000', 'hex'); From 88aed87f911cb00b61f3d37049495ce83a04fc1d Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Thu, 10 Mar 2016 11:08:23 -0500 Subject: [PATCH 04/23] script: added p2wsh and p2wpkh script identification methods --- lib/script/script.js | 16 ++++++++++++++++ test/script/script.js | 25 +++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/lib/script/script.js b/lib/script/script.js index eded4d1e1..e64754715 100644 --- a/lib/script/script.js +++ b/lib/script/script.js @@ -388,6 +388,22 @@ Script.prototype.isScriptHashOut = function() { buf[buf.length - 1] === Opcode.OP_EQUAL); }; +/** + * @returns {boolean} if this is a p2wsh output script + */ +Script.prototype.isWitnessScriptHashOut = function() { + var buf = this.toBuffer(); + return (buf.length === 34 && buf[0] === 0 && buf[1] === 32); +}; + +/** + * @returns {boolean} if this is a p2wpkh output script + */ +Script.prototype.isWitnessPublicKeyHashOut = function() { + var buf = this.toBuffer(); + return (buf.length === 22 && buf[0] === 0 && buf[1] === 20); +}; + /** * @returns {boolean} if this is a p2sh input script * Note that these are frequently indistinguishable from pubkeyhashin diff --git a/test/script/script.js b/test/script/script.js index 4430ce246..e9b0d30e8 100644 --- a/test/script/script.js +++ b/test/script/script.js @@ -402,6 +402,31 @@ describe('Script', function() { }); + describe('#isWitnessScriptHashOut', function() { + it('should recognize this script as p2wsh', function() { + Script('OP_0 32 0xa99d08fbec6958f4d4a3776c3728ec448934d25fe1142054b8b68bac866dfc3a') + .isWitnessScriptHashOut().should.equal(true); + Script('0020a99d08fbec6958f4d4a3776c3728ec448934d25fe1142054b8b68bac866dfc3a') + .isWitnessScriptHashOut().should.equal(true); + }); + it('should NOT identify as p2wsh', function() { + Script('OP_0 20 0x799d283e7f92af1dd242bf4eea513c6efd117de2') + .isWitnessScriptHashOut().should.equal(false); + }); + }); + + describe('#isWitnessPublicKeyHashOut', function() { + it('should identify as p2wpkh', function() { + Script('OP_0 20 0x799d283e7f92af1dd242bf4eea513c6efd117de2') + .isWitnessPublicKeyHashOut().should.equal(true); + Script('0014799d283e7f92af1dd242bf4eea513c6efd117de2').isWitnessPublicKeyHashOut().should.equal(true); + }); + it('should NOT identify as p2wpkh', function() { + Script('OP_0 32 0xa99d08fbec6958f4d4a3776c3728ec448934d25fe1142054b8b68bac866dfc3a') + .isWitnessPublicKeyHashOut().should.equal(false); + }); + }); + describe('#isPushOnly', function() { it('should know these scripts are or aren\'t push only', function() { Script('OP_NOP 1 0x01').isPushOnly().should.equal(false); From 317d249758ca6126b072e55ab82ca672e67ebabe Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Thu, 10 Mar 2016 11:50:28 -0500 Subject: [PATCH 05/23] input: added nested p2sh witness --- lib/address.js | 10 ++- lib/networks.js | 18 +++++ lib/script/script.js | 11 +++ lib/transaction/input/input.js | 16 +++++ lib/transaction/input/multisigscripthash.js | 27 ++++++-- lib/transaction/transaction.js | 74 ++++++++++++--------- test/address.js | 5 ++ test/script/script.js | 12 ++++ test/transaction/transaction.js | 37 ++++++++++- 9 files changed, 168 insertions(+), 42 deletions(-) diff --git a/lib/address.js b/lib/address.js index b017bc7f4..8f1227a21 100644 --- a/lib/address.js +++ b/lib/address.js @@ -255,11 +255,16 @@ Address._transformScript = function(script, network) { * @param {Array} publicKeys - a set of public keys to create an address * @param {number} threshold - the number of signatures needed to release the funds * @param {String|Network} network - either a Network instance, 'livenet', or 'testnet' + * @param {boolean=} nestedWitness - if the address uses a nested p2sh witness * @return {Address} */ -Address.createMultisig = function(publicKeys, threshold, network) { +Address.createMultisig = function(publicKeys, threshold, network, nestedWitness) { network = network || publicKeys[0].network || Networks.defaultNetwork; - return Address.payingTo(Script.buildMultisigOut(publicKeys, threshold), network); + var redeemScript = Script.buildMultisigOut(publicKeys, threshold); + if (nestedWitness) { + return Address.payingTo(Script.buildWitnessMultisigOutFromScript(redeemScript), network); + } + return Address.payingTo(redeemScript, network); }; /** @@ -332,7 +337,6 @@ Address.fromScriptHash = function(hash, network) { Address.payingTo = function(script, network) { $.checkArgument(script, 'script is required'); $.checkArgument(script instanceof Script, 'script must be instance of Script'); - return Address.fromScriptHash(Hash.sha256ripemd160(script.toBuffer()), network); }; diff --git a/lib/networks.js b/lib/networks.js index 3a9381c5d..7c0fe545e 100644 --- a/lib/networks.js +++ b/lib/networks.js @@ -235,6 +235,24 @@ Object.defineProperty(testnet, 'dnsSeeds', { } }); +//TODO add networkMagic +addNetwork({ + name: 'segnet', + pubkeyhash: 0x1e, + privatekey: 0x9e, + scripthash: 0x32, + xpubkey: 0x053587CF, + xprivkey: 0x05358394, + port: 28333, + dnsSeeds: [] +}); + +/** + * @instance + * @member Networks#testnet + */ +var testnet = get('segnet'); + /** * @function * @member Networks#enableRegtest diff --git a/lib/script/script.js b/lib/script/script.js index e64754715..6bb5a7a7a 100644 --- a/lib/script/script.js +++ b/lib/script/script.js @@ -733,6 +733,17 @@ Script.buildMultisigOut = function(publicKeys, threshold, opts) { return script; }; +Script.buildWitnessMultisigOutFromScript = function(script) { + if (script instanceof Script) { + var s = new Script(); + s.add(Opcode.OP_0); + s.add(Hash.sha256(script.toBuffer())); + return s; + } else { + throw new TypeError('First argument is expected to be a p2sh script'); + } +}; + /** * A new Multisig input script for the given public keys, requiring m of those public keys to spend * diff --git a/lib/transaction/input/input.js b/lib/transaction/input/input.js index 9d86e6656..6d0635f36 100644 --- a/lib/transaction/input/input.js +++ b/lib/transaction/input/input.js @@ -58,6 +58,7 @@ Input.prototype._fromObject = function(params) { } else { prevTxId = params.prevTxId; } + this.witnesses = []; this.output = params.output ? (params.output instanceof Output ? params.output : new Output(params.output)) : undefined; this.prevTxId = prevTxId || params.txidbuf; @@ -169,6 +170,21 @@ Input.prototype.clearSignatures = function() { throw new errors.AbstractMethodInvoked('Input#clearSignatures'); }; +Input.prototype.hasWitnesses = function() { + if (this.witnesses && this.witnesses.length > 0) { + return true; + } + return false; +}; + +Input.prototype.getWitnesses = function() { + return this.witnesses; +}; + +Input.prototype.setWitnesses = function(witnesses) { + this.witnesses = witnesses; +}; + Input.prototype.isValidSignature = function(transaction, signature) { // FIXME: Refactor signature so this is not necessary signature.signature.nhashtype = signature.sigtype; diff --git a/lib/transaction/input/multisigscripthash.js b/lib/transaction/input/multisigscripthash.js index 8b5db740d..d5b91aeab 100644 --- a/lib/transaction/input/multisigscripthash.js +++ b/lib/transaction/input/multisigscripthash.js @@ -1,5 +1,7 @@ 'use strict'; +/* jshint maxparams:5 */ + var _ = require('lodash'); var inherits = require('inherits'); var Input = require('./input'); @@ -9,23 +11,33 @@ var $ = require('../../util/preconditions'); var Script = require('../../script'); var Signature = require('../../crypto/signature'); var Sighash = require('../sighash'); -var PublicKey = require('../../publickey'); var BufferUtil = require('../../util/buffer'); var TransactionSignature = require('../signature'); /** * @constructor */ -function MultiSigScriptHashInput(input, pubkeys, threshold, signatures) { +function MultiSigScriptHashInput(input, pubkeys, threshold, signatures, nestedWitness) { + /* jshint maxstatements:20 */ Input.apply(this, arguments); var self = this; pubkeys = pubkeys || input.publicKeys; threshold = threshold || input.threshold; signatures = signatures || input.signatures; + this.nestedWitness = nestedWitness ? true : false; this.publicKeys = _.sortBy(pubkeys, function(publicKey) { return publicKey.toString('hex'); }); this.redeemScript = Script.buildMultisigOut(this.publicKeys, threshold); - $.checkState(Script.buildScriptHashOut(this.redeemScript).equals(this.output.script), + + if (this.nestedWitness) { + var nested = Script.buildWitnessMultisigOutFromScript(this.redeemScript); + $.checkState(Script.buildScriptHashOut(nested).equals(this.output.script), + 'Provided public keys don\'t hash to the provided output (nested witness)'); + this.setScript(nested); + } else { + $.checkState(Script.buildScriptHashOut(this.redeemScript).equals(this.output.script), 'Provided public keys don\'t hash to the provided output'); + } + this.publicKeyIndex = {}; _.each(this.publicKeys, function(publicKey, index) { self.publicKeyIndex[publicKey.toString()] = index; @@ -94,12 +106,17 @@ MultiSigScriptHashInput.prototype.addSignature = function(transaction, signature }; MultiSigScriptHashInput.prototype._updateScript = function() { - this.setScript(Script.buildP2SHMultisigIn( + var scriptSig = Script.buildP2SHMultisigIn( this.publicKeys, this.threshold, this._createSignatures(), { cachedMultisig: this.redeemScript } - )); + ); + if (this.nestedWitness) { + this.setWitnesses([scriptSig.toBuffer()]); + } else { + this.setScript(scriptSig); + } return this; }; diff --git a/lib/transaction/transaction.js b/lib/transaction/transaction.js index 3fe454bbe..c23ab2bd4 100644 --- a/lib/transaction/transaction.js +++ b/lib/transaction/transaction.js @@ -38,7 +38,6 @@ function Transaction(serialized) { } this.inputs = []; this.outputs = []; - this.witness = []; this._inputAmount = undefined; this._outputAmount = undefined; @@ -296,38 +295,50 @@ Transaction.prototype.toBuffer = function(noWitness) { return this.toBufferWriter(writer, noWitness).toBuffer(); }; -Transaction.prototype.hasWitness = function() { - return this.witness && this.witness.length > 0; +Transaction.prototype.hasWitnesses = function() { + for (var i = 0; i < this.inputs.length; i++) { + if (this.inputs[i].hasWitnesses()) { + return true; + } + } + return false; }; Transaction.prototype.toBufferWriter = function(writer, noWitness) { writer.writeInt32LE(this.version); - if (this.hasWitness() && !noWitness) { + + var hasWitnesses = this.hasWitnesses(); + + if (hasWitnesses && !noWitness) { writer.write(new Buffer('0001', 'hex')); } + writer.writeVarintNum(this.inputs.length); + _.each(this.inputs, function(input) { input.toBufferWriter(writer); }); + writer.writeVarintNum(this.outputs.length); _.each(this.outputs, function(output) { output.toBufferWriter(writer); }); - if (this.hasWitness() && !noWitness) { - this._toBufferWriterWitness(writer); + + if (hasWitnesses && !noWitness) { + _.each(this.inputs, function(input) { + var witnesses = input.getWitnesses(); + writer.writeVarintNum(witnesses.length); + for (var j = 0; j < witnesses.length; j++) { + writer.writeVarintNum(witnesses[j].length); + writer.write(witnesses[j]); + } + }); } + writer.writeUInt32LE(this.nLockTime); return writer; }; -Transaction.prototype._toBufferWriterWitness = function(writer) { - writer.writeVarintNum(this.witness.length); - for (var i = 0; i < this.witness.length; i++) { - writer.writeVarintNum(this.witness[i].length); - writer.write(this.witness[i]); - } -}; - Transaction.prototype.fromBuffer = function(buffer) { var reader = new BufferReader(buffer); return this.fromBufferReader(reader); @@ -340,10 +351,10 @@ Transaction.prototype.fromBufferReader = function(reader) { var sizeTxIns = reader.readVarintNum(); // check for segwit - var hasWitness = false; + var hasWitnesses = false; if (sizeTxIns === 0 && reader.buf[reader.pos] !== 0) { reader.pos += 1; - hasWitness = true; + hasWitnesses = true; sizeTxIns = reader.readVarintNum(); } @@ -357,23 +368,23 @@ Transaction.prototype.fromBufferReader = function(reader) { this.outputs.push(Output.fromBufferReader(reader)); } - if (hasWitness) { - this._fromBufferReaderScriptWitnesses(reader); + if (hasWitnesses) { + for (var k = 0; k < sizeTxIns; k++) { + var itemCount = reader.readVarintNum(); + var witnesses = []; + for (var l = 0; l < itemCount; l++) { + var size = reader.readVarintNum(); + var item = reader.read(size); + witnesses.push(item); + } + this.inputs[k].setWitnesses(witnesses); + } } this.nLockTime = reader.readUInt32LE(); return this; }; -Transaction.prototype._fromBufferReaderScriptWitnesses = function(reader) { - var itemCount = reader.readVarintNum(); - this.witness = []; - for (var i = 0; i < itemCount; i++) { - var size = reader.readVarintNum(); - var item = reader.read(size); - this.witness.push(item); - } -}; Transaction.prototype.toObject = Transaction.prototype.toJSON = function toObject() { var inputs = []; @@ -592,8 +603,9 @@ Transaction.prototype._newTransaction = function() { * @param {(Array.|Transaction~fromObject)} utxo * @param {Array=} pubkeys * @param {number=} threshold + * @param {boolean=} nestedWitness - Indicates that the utxo is nested witness p2sh */ -Transaction.prototype.from = function(utxo, pubkeys, threshold) { +Transaction.prototype.from = function(utxo, pubkeys, threshold, nestedWitness) { if (_.isArray(utxo)) { var self = this; _.each(utxo, function(utxo) { @@ -609,7 +621,7 @@ Transaction.prototype.from = function(utxo, pubkeys, threshold) { return this; } if (pubkeys && threshold) { - this._fromMultisigUtxo(utxo, pubkeys, threshold); + this._fromMultisigUtxo(utxo, pubkeys, threshold, nestedWitness); } else { this._fromNonP2SH(utxo); } @@ -637,7 +649,7 @@ Transaction.prototype._fromNonP2SH = function(utxo) { })); }; -Transaction.prototype._fromMultisigUtxo = function(utxo, pubkeys, threshold) { +Transaction.prototype._fromMultisigUtxo = function(utxo, pubkeys, threshold, nestedWitness) { $.checkArgument(threshold <= pubkeys.length, 'Number of required signatures must be greater than the number of public keys'); var clazz; @@ -657,7 +669,7 @@ Transaction.prototype._fromMultisigUtxo = function(utxo, pubkeys, threshold) { prevTxId: utxo.txId, outputIndex: utxo.outputIndex, script: Script.empty() - }, pubkeys, threshold)); + }, pubkeys, threshold, false, nestedWitness)); }; /** diff --git a/test/address.js b/test/address.js index 4982ad701..6cac08752 100644 --- a/test/address.js +++ b/test/address.js @@ -545,6 +545,11 @@ describe('Address', function() { address.toString().should.equal('2N7T3TAetJrSCruQ39aNrJvYLhG1LJosujf'); }); + it('can create an address from a set of public keys with a nested witness program', function() { + var address = Address.createMultisig(publics, 2, Networks.livenet, true); + address.toString().should.equal('3PpK1bBqUmPK3Q6QPSUK7BQSZ1DMWL6aes'); + }); + it('can also be created by Address.createMultisig', function() { var address = Address.createMultisig(publics, 2); var address2 = Address.createMultisig(publics, 2); diff --git a/test/script/script.js b/test/script/script.js index e9b0d30e8..6f5519650 100644 --- a/test/script/script.js +++ b/test/script/script.js @@ -682,6 +682,18 @@ describe('Script', function() { } } }); + + describe('#buildWitnessMultisigOutFromScript', function() { + it('it will build nested witness scriptSig', function() { + var redeemScript = bitcore.Script(); + var redeemHash = bitcore.crypto.Hash.sha256(redeemScript.toBuffer()); + var s = Script.buildWitnessMultisigOutFromScript(redeemScript); + var buf = s.toBuffer(); + buf[0].should.equal(0); + buf.slice(2, 34).toString('hex').should.equal(redeemHash.toString('hex')); + }); + }); + describe('#buildPublicKeyHashOut', function() { it('should create script from livenet address', function() { var address = Address.fromString('1NaTVwXDDUJaXDQajoa9MqHhz4uTxtgK14'); diff --git a/test/transaction/transaction.js b/test/transaction/transaction.js index b13e61012..ab80f371e 100644 --- a/test/transaction/transaction.js +++ b/test/transaction/transaction.js @@ -1233,14 +1233,16 @@ describe('Transaction', function() { var version = new Buffer('01000000', 'hex'); var marker = new Buffer('00', 'hex'); //always zero var flag = new Buffer('01', 'hex'); //non zero - var inputCount = new Buffer('00', 'hex'); + var inputCount = new Buffer('01', 'hex'); + var inputDummy = new Buffer('2052cda8bc0c2cb743f154881fc85cb675527dcf2f7a5938241020c33341b3f70000000000ffffffff', 'hex'); var outputCount = new Buffer('00', 'hex'); var witness = new Buffer('01', 'hex'); var witnessItems = new Buffer('00', 'hex'); var locktime = new Buffer('00000000', 'hex'); - var txBuffer = Buffer.concat([version, marker, flag, inputCount, outputCount, witness, witnessItems, locktime]); + var txBuffer = Buffer.concat([version, marker, flag, inputCount, inputDummy, outputCount, witness, + witnessItems, locktime]); var tx = bitcore.Transaction().fromBuffer(txBuffer); - tx.hasWitness().should.equal(true); + tx.hasWitnesses().should.equal(true); }); it('correctly calculate hash for segwit transaction', function() { var txBuffer = new Buffer('01000000000101b0e5caa7e37d4b8530c3e1071a36dd5e05d1065cf7224ddff42c69e3387689870000000000ffffffff017b911100000000001600144ff831574da8bef07f8bc97244a1666147b071570247304402203fcbcfddbd6ca3a90252610dd63f1be50b2d926b8d87c912da0a3e42bb03fba002202a90c8aad75da22b0549c72618b754114583e934c0b0d2ccd6c13fcd859ba4ed01210363f3f47f4555779de405eab8d0dc8c2a4f3e09f4171a3fa47c7a77715795319800000000', 'hex'); @@ -1248,6 +1250,35 @@ describe('Transaction', function() { tx.hash.should.equal('7f1a2d46746f1bfbb22ab797d5aad1fd9723477b417fa34dff73d8a7dbb14570'); tx.witnessHash.should.equal('3c26fc8b5cfe65f96d955cecfe4d11db2659d052171f9f31af043e9f5073e46b'); }); + describe('signing', function() { + var privateKey1 = PrivateKey.fromWIF('cNuW8LX2oeQXfKKCGxajGvqwhCgBtacwTQqiCGHzzKfmpHGY4TE9'); + var publicKey1 = p2shPrivateKey1.toPublicKey(); + var privateKey2 = PrivateKey.fromWIF('cTtLHt4mv6zuJytSnM7Vd6NLxyNauYLMxD818sBC8PJ1UPiVTRSs'); + var publicKey2 = p2shPrivateKey2.toPublicKey(); + var privateKey3 = PrivateKey.fromWIF('cQFMZ5gP9CJtUZPc9X3yFae89qaiQLspnftyxxLGvVNvM6tS6mYY'); + var publicKey3 = p2shPrivateKey3.toPublicKey(); + var address = Address.createMultisig([ + publicKey1, + publicKey2, + publicKey3 + ], 2, 'segnet', true); + var utxo = { + address: address.toString(), + txId: '72a0b3d6dcbba9d2ae74c11eec05cdfc2a03e1a01b3bbb09af0cc2dc8c3dbefa', + outputIndex: 0, + script: Script.buildScriptHashOut(address).toString(), + satoshis: 1e8 + }; + it('will sign with nested p2sh witness program', function() { + var tx = new Transaction() + .from(utxo, [publicKey1, publicKey2, publicKey3], 2, true) + .to([{address: 'DSy1mtq7NeVEWKjBoXb4wgujgLv7cLE9Vb', satoshis: 50000}]) + .fee(150000) + .change('DF8MrzMiDkTvT4qpeCAdW6Y37WGgbo9Cmu') + .sign(privateKey1) + .sign(privateKey2); + }); + }); }); }); From a8515ad81b31a502469b72ff2028b04ef7c8a3d1 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Thu, 10 Mar 2016 16:47:52 -0500 Subject: [PATCH 06/23] transaction: added new sighash for witness --- lib/networks.js | 3 +- lib/transaction/index.js | 1 + lib/transaction/sighashwitness.js | 147 +++++++++++++++++++++++++++++ test/transaction/sighashwitness.js | 24 +++++ 4 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 lib/transaction/sighashwitness.js create mode 100644 test/transaction/sighashwitness.js diff --git a/lib/networks.js b/lib/networks.js index 7c0fe545e..957d6f87a 100644 --- a/lib/networks.js +++ b/lib/networks.js @@ -251,7 +251,7 @@ addNetwork({ * @instance * @member Networks#testnet */ -var testnet = get('segnet'); +var segnet = get('segnet'); /** * @function @@ -281,6 +281,7 @@ module.exports = { livenet: livenet, mainnet: livenet, testnet: testnet, + segnet: segnet, get: get, enableRegtest: enableRegtest, disableRegtest: disableRegtest diff --git a/lib/transaction/index.js b/lib/transaction/index.js index 79c9574d2..b7a27df18 100644 --- a/lib/transaction/index.js +++ b/lib/transaction/index.js @@ -5,3 +5,4 @@ module.exports.Output = require('./output'); module.exports.UnspentOutput = require('./unspentoutput'); module.exports.Signature = require('./signature'); module.exports.Sighash = require('./sighash'); +module.exports.SighashWitness = require('./sighashwitness'); diff --git a/lib/transaction/sighashwitness.js b/lib/transaction/sighashwitness.js new file mode 100644 index 000000000..12e36f2b2 --- /dev/null +++ b/lib/transaction/sighashwitness.js @@ -0,0 +1,147 @@ +'use strict'; + +/* jshint maxparams:5 */ + +var Signature = require('../crypto/signature'); +var Script = require('../script'); +var Output = require('./output'); +var BufferReader = require('../encoding/bufferreader'); +var BufferWriter = require('../encoding/bufferwriter'); +var BN = require('../crypto/bn'); +var Hash = require('../crypto/hash'); +var ECDSA = require('../crypto/ecdsa'); +var $ = require('../util/preconditions'); +var _ = require('lodash'); + +/** + * Returns a buffer of length 32 bytes with the hash that needs to be signed + * for witness programs as defined by: + * https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki + * + * @name Signing.sighash + * @param {Transaction} transaction the transaction to sign + * @param {number} sighashType the type of the hash + * @param {number} inputNumber the input index for the signature + * @param {Buffer} scriptCode + * @param {Buffer} satoshisBuffer + */ +var sighash = function sighash(transaction, sighashType, inputNumber, scriptCode, satoshisBuffer) { + /* jshint maxstatements: 50 */ + + var hashPrevouts; + var hashSequence; + var hashOutputs; + + if (!(sighashType & Signature.SIGHASH_ANYONECANPAY)) { + var buffers = []; + for (var n = 0; n < transaction.inputs.length; n++) { + var input = transaction.inputs[n]; + var prevTxIdBuffer = new BufferReader(input.prevTxId).readReverse(); + buffers.push(prevTxIdBuffer); + var outputIndexBuffer = new Buffer(new Array(4)); + outputIndexBuffer.writeUInt32LE(input.outputIndex); + buffers.push(outputIndexBuffer); + } + hashPrevouts = Hash.sha256sha256(Buffer.concat(buffers)); + } + + if (!(sighashType & Signature.SIGHASH_ANYONECANPAY) && + (sighashType & 0x1f) !== Signature.SIGHASH_SINGLE && (sighashType & 0x1f) !== Signature.SIGHASH_NONE) { + + var sequenceBuffers = []; + for (var m = 0; m < transaction.inputs.length; m++) { + var sequenceBuffer = new Buffer(new Array(4)); + sequenceBuffer.writeUInt32LE(transaction.inputs[m].sequenceNumber); + sequenceBuffers.push(sequenceBuffer); + } + hashSequence = Hash.sha256sha256(Buffer.concat(sequenceBuffers)); + } + + var outputWriter = new BufferWriter(); + if ((sighashType & 0x1f) !== Signature.SIGHASH_SINGLE && (sighashType & 0x1f) !== Signature.SIGHASH_NONE) { + for (var p = 0; p < transaction.outputs.length; p++) { + transaction.outputs[p].toBufferWriter(outputWriter); + } + hashOutputs = Hash.sha256sha256(outputWriter.toBuffer()); + } else if ((sighashType & 0x1f) === Signature.SIGHASH_SINGLE && inputNumber < transaction.outputs.length) { + transaction.outputs[inputNumber].toBufferWriter(outputWriter); + hashOutputs = Hash.sha256sha256(outputWriter.toBuffer()); + } + + // Version + var writer = new BufferWriter(); + writer.writeUInt32LE(transaction.version); + + // Input prevouts/nSequence (none/all, depending on flags) + writer.write(hashPrevouts); + writer.write(hashSequence); + + // The input being signed (replacing the scriptSig with scriptCode + amount) + // The prevout may already be contained in hashPrevout, and the nSequence + // may already be contain in hashSequence. + var outpointId = new BufferReader(transaction.inputs[inputNumber].prevTxId).readReverse(); + writer.write(outpointId); + writer.writeUInt32LE(transaction.inputs[inputNumber].outputIndex); + + writer.write(scriptCode); + writer.write(satoshisBuffer); + writer.writeUInt32LE(transaction.inputs[inputNumber].sequenceNumber); + + // Outputs (none/one/all, depending on flags) + writer.write(hashOutputs); + + // Locktime + writer.writeUInt32LE(transaction.nLockTime); + + // Sighash type + writer.writeInt32LE(sighashType); + + return Hash.sha256sha256(writer.toBuffer()); + +}; + +/** + * Create a signature + * + * @name Signing.sign + * @param {Transaction} transaction + * @param {PrivateKey} privateKey + * @param {number} sighash + * @param {number} inputIndex + * @param {Script} subscript + * @return {Signature} + */ +function sign(transaction, privateKey, sighashType, inputIndex, subscript) { + var hashbuf = sighash(transaction, sighashType, inputIndex, subscript); + var sig = ECDSA.sign(hashbuf, privateKey, 'little').set({ + nhashtype: sighashType + }); + return sig; +} + +/** + * Verify a signature + * + * @name Signing.verify + * @param {Transaction} transaction + * @param {Signature} signature + * @param {PublicKey} publicKey + * @param {number} inputIndex + * @param {Script} subscript + * @return {boolean} + */ +function verify(transaction, signature, publicKey, inputIndex, subscript) { + $.checkArgument(!_.isUndefined(transaction)); + $.checkArgument(!_.isUndefined(signature) && !_.isUndefined(signature.nhashtype)); + var hashbuf = sighash(transaction, signature.nhashtype, inputIndex, subscript); + return ECDSA.verify(hashbuf, signature, publicKey, 'little'); +} + +/** + * @namespace Signing + */ +module.exports = { + sighash: sighash, + sign: sign, + verify: verify +}; diff --git a/test/transaction/sighashwitness.js b/test/transaction/sighashwitness.js new file mode 100644 index 000000000..4ea76f310 --- /dev/null +++ b/test/transaction/sighashwitness.js @@ -0,0 +1,24 @@ +'use strict'; + +var chai = require('chai'); +var should = chai.should(); +var bitcore = require('../../'); +var Transaction = bitcore.Transaction; +var Signature = bitcore.crypto.Signature; +var SighashWitness = Transaction.SighashWitness; + +describe('Sighash Witness Program Version 0', function() { + + it('should create hash for sighash all', function() { + // https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki + var unsignedTx = bitcore.Transaction('0100000002fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f0000000000eeffffffef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffffff02202cb206000000001976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac9093510d000000001976a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac11000000'); + + var scriptCode = new Buffer('1976a9141d0f172a0ecb48aee1be1f2687d2963ae33f71a188ac', 'hex'); + var satoshisBuffer = new Buffer('0046c32300000000', 'hex'); + + var hash = SighashWitness.sighash(unsignedTx, Signature.SIGHASH_ALL, 1, scriptCode, satoshisBuffer); + + hash.toString('hex').should.equal('c37af31116d1b27caf68aae9e3ac82f1477929014d5b917657d0eb49478cb670'); + }); + +}); From 93002249da860c8db2f467a60b9924a735e2b30d Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Thu, 10 Mar 2016 19:25:27 -0500 Subject: [PATCH 07/23] transaction: start to use new sighash for nested witness --- lib/script/script.js | 9 ++++ lib/transaction/input/multisigscripthash.js | 52 ++++++++++++++++++-- lib/transaction/sighashwitness.js | 8 +-- test/transaction/input/multisigscripthash.js | 33 +++++++++++++ test/transaction/transaction.js | 3 +- 5 files changed, 95 insertions(+), 10 deletions(-) diff --git a/lib/script/script.js b/lib/script/script.js index 6bb5a7a7a..4fd45a1bd 100644 --- a/lib/script/script.js +++ b/lib/script/script.js @@ -689,6 +689,15 @@ Script.prototype._addBuffer = function(buf, prepend) { return this; }; +Script.prototype.hasCodeseparators = function() { + for (var i = 0; i < this.chunks.length; i++) { + if (this.chunks[i].opcodenum === Opcode.OP_CODESEPARATOR) { + return true; + } + } + return false; +}; + Script.prototype.removeCodeseparators = function() { var chunks = []; for (var i = 0; i < this.chunks.length; i++) { diff --git a/lib/transaction/input/multisigscripthash.js b/lib/transaction/input/multisigscripthash.js index d5b91aeab..498aacf29 100644 --- a/lib/transaction/input/multisigscripthash.js +++ b/lib/transaction/input/multisigscripthash.js @@ -11,6 +11,8 @@ var $ = require('../../util/preconditions'); var Script = require('../../script'); var Signature = require('../../crypto/signature'); var Sighash = require('../sighash'); +var SighashWitness = require('../sighashwitness'); +var BufferWriter = require('../../encoding/bufferwriter'); var BufferUtil = require('../../util/buffer'); var TransactionSignature = require('../signature'); @@ -74,6 +76,24 @@ MultiSigScriptHashInput.prototype._serializeSignatures = function() { }); }; +MultiSigScriptHashInput.prototype.getScriptCode = function() { + var writer = new BufferWriter(); + if (!this.script.hasCodeseparators()) { + writer.writeVarintNum(this._scriptBuffer.length); + writer.write(this._scriptBuffer); + } else { + throw new Error('@TODO'); + } + return writer.toBuffer(); +}; + +MultiSigScriptHashInput.prototype.getSatoshisBuffer = function() { + $.checkState(this.output instanceof Output); + $.checkState(this.output._satoshisBN); + var buffer = new BufferWriter().writeUInt64LEBN(this.output._satoshisBN).toBuffer(); + return buffer; +}; + MultiSigScriptHashInput.prototype.getSignatures = function(transaction, privateKey, index, sigtype) { $.checkState(this.output instanceof Output); sigtype = sigtype || Signature.SIGHASH_ALL; @@ -82,12 +102,20 @@ MultiSigScriptHashInput.prototype.getSignatures = function(transaction, privateK var results = []; _.each(this.publicKeys, function(publicKey) { if (publicKey.toString() === privateKey.publicKey.toString()) { + var signature; + if (self.nestedWitness) { + var scriptCode = self.getScriptCode(); + var satoshisBuffer = self.getSatoshisBuffer(); + signature = SighashWitness.sign(transaction, privateKey, sigtype, index, scriptCode, satoshisBuffer); + } else { + signature = Sighash.sign(transaction, privateKey, sigtype, index, self.redeemScript); + } results.push(new TransactionSignature({ publicKey: privateKey.publicKey, prevTxId: self.prevTxId, outputIndex: self.outputIndex, inputIndex: index, - signature: Sighash.sign(transaction, privateKey, sigtype, index, self.redeemScript), + signature: signature, sigtype: sigtype })); } @@ -159,15 +187,29 @@ MultiSigScriptHashInput.prototype.publicKeysWithoutSignature = function() { }; MultiSigScriptHashInput.prototype.isValidSignature = function(transaction, signature) { - // FIXME: Refactor signature so this is not necessary - signature.signature.nhashtype = signature.sigtype; - return Sighash.verify( + if (this.nestedWitness) { + signature.signature.nhashtype = signature.sigtype; + var scriptCode = this.getScriptCode(); + var satoshisBuffer = this.getSatoshisBuffer(); + return SighashWitness.verify( + transaction, + signature.signature, + signature.publicKey, + signature.inputIndex, + scriptCode, + satoshisBuffer + ); + } else { + // FIXME: Refactor signature so this is not necessary + signature.signature.nhashtype = signature.sigtype; + return Sighash.verify( transaction, signature.signature, signature.publicKey, signature.inputIndex, this.redeemScript - ); + ); + } }; MultiSigScriptHashInput.OPCODES_SIZE = 7; // serialized size (<=3) + 0 .. N .. M OP_CHECKMULTISIG diff --git a/lib/transaction/sighashwitness.js b/lib/transaction/sighashwitness.js index 12e36f2b2..0aff29db0 100644 --- a/lib/transaction/sighashwitness.js +++ b/lib/transaction/sighashwitness.js @@ -111,8 +111,8 @@ var sighash = function sighash(transaction, sighashType, inputNumber, scriptCode * @param {Script} subscript * @return {Signature} */ -function sign(transaction, privateKey, sighashType, inputIndex, subscript) { - var hashbuf = sighash(transaction, sighashType, inputIndex, subscript); +function sign(transaction, privateKey, sighashType, inputIndex, scriptCode, satoshisBuffer) { + var hashbuf = sighash(transaction, sighashType, inputIndex, scriptCode, satoshisBuffer); var sig = ECDSA.sign(hashbuf, privateKey, 'little').set({ nhashtype: sighashType }); @@ -130,10 +130,10 @@ function sign(transaction, privateKey, sighashType, inputIndex, subscript) { * @param {Script} subscript * @return {boolean} */ -function verify(transaction, signature, publicKey, inputIndex, subscript) { +function verify(transaction, signature, publicKey, inputIndex, scriptCode, satoshisBuffer) { $.checkArgument(!_.isUndefined(transaction)); $.checkArgument(!_.isUndefined(signature) && !_.isUndefined(signature.nhashtype)); - var hashbuf = sighash(transaction, signature.nhashtype, inputIndex, subscript); + var hashbuf = sighash(transaction, signature.nhashtype, inputIndex, scriptCode, satoshisBuffer); return ECDSA.verify(hashbuf, signature, publicKey, 'little'); } diff --git a/test/transaction/input/multisigscripthash.js b/test/transaction/input/multisigscripthash.js index 60a82c7a5..911865c21 100644 --- a/test/transaction/input/multisigscripthash.js +++ b/test/transaction/input/multisigscripthash.js @@ -111,4 +111,37 @@ describe('MultiSigScriptHashInput', function() { var roundtrip = new MultiSigScriptHashInput(input.toObject()); roundtrip.toObject().should.deep.equal(input.toObject()); }); + it('will get the scriptCode for nested witness', function() { + var address = Address.createMultisig([public1, public2, public3], 2, 'testnet', true); + var utxo = { + address: address.toString(), + txId: '66e64ef8a3b384164b78453fa8c8194de9a473ba14f89485a0e433699daec140', + outputIndex: 0, + script: new Script(address), + satoshis: 1000000 + }; + var transaction = new Transaction() + .from(utxo, [public1, public2, public3], 2, true) + .to(address, 1000000); + var input = transaction.inputs[0]; + var scriptCode = input.getScriptCode(); + scriptCode.toString('hex').should.equal('2200206aac01b83b1537afe2aa96d13a339b8a109b05f6eaf37d5840fe5f227daedacc'); + }); + it('will get the satoshis buffer for nested witness', function() { + var address = Address.createMultisig([public1, public2, public3], 2, 'testnet', true); + var utxo = { + address: address.toString(), + txId: '66e64ef8a3b384164b78453fa8c8194de9a473ba14f89485a0e433699daec140', + outputIndex: 0, + script: new Script(address), + satoshis: 1000000 + }; + var transaction = new Transaction() + .from(utxo, [public1, public2, public3], 2, true) + .to(address, 1000000); + var input = transaction.inputs[0]; + var satoshisBuffer = input.getSatoshisBuffer(); + satoshisBuffer.toString('hex').should.equal('40420f0000000000'); + }); + }); diff --git a/test/transaction/transaction.js b/test/transaction/transaction.js index ab80f371e..7f3ca5b42 100644 --- a/test/transaction/transaction.js +++ b/test/transaction/transaction.js @@ -1266,7 +1266,7 @@ describe('Transaction', function() { address: address.toString(), txId: '72a0b3d6dcbba9d2ae74c11eec05cdfc2a03e1a01b3bbb09af0cc2dc8c3dbefa', outputIndex: 0, - script: Script.buildScriptHashOut(address).toString(), + script: Script.buildScriptHashOut(address).toHex(), satoshis: 1e8 }; it('will sign with nested p2sh witness program', function() { @@ -1277,6 +1277,7 @@ describe('Transaction', function() { .change('DF8MrzMiDkTvT4qpeCAdW6Y37WGgbo9Cmu') .sign(privateKey1) .sign(privateKey2); + // TODO: check correct signature }); }); }); From 95098e4dc04ec14a974e0829863acc6db38a7a0c Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Sat, 12 Mar 2016 17:59:55 -0500 Subject: [PATCH 08/23] transaction: transaction signing for nested p2sh witness --- lib/transaction/input/multisigscripthash.js | 47 ++++++++--- lib/transaction/sighashwitness.js | 6 +- test/transaction/input/multisigscripthash.js | 2 +- test/transaction/transaction.js | 88 ++++++++++++++++++-- 4 files changed, 119 insertions(+), 24 deletions(-) diff --git a/lib/transaction/input/multisigscripthash.js b/lib/transaction/input/multisigscripthash.js index 498aacf29..43dd49051 100644 --- a/lib/transaction/input/multisigscripthash.js +++ b/lib/transaction/input/multisigscripthash.js @@ -29,12 +29,13 @@ function MultiSigScriptHashInput(input, pubkeys, threshold, signatures, nestedWi this.nestedWitness = nestedWitness ? true : false; this.publicKeys = _.sortBy(pubkeys, function(publicKey) { return publicKey.toString('hex'); }); this.redeemScript = Script.buildMultisigOut(this.publicKeys, threshold); - if (this.nestedWitness) { var nested = Script.buildWitnessMultisigOutFromScript(this.redeemScript); $.checkState(Script.buildScriptHashOut(nested).equals(this.output.script), 'Provided public keys don\'t hash to the provided output (nested witness)'); - this.setScript(nested); + var scriptSig = new Script(); + scriptSig.add(nested.toBuffer()); + this.setScript(scriptSig); } else { $.checkState(Script.buildScriptHashOut(this.redeemScript).equals(this.output.script), 'Provided public keys don\'t hash to the provided output'); @@ -78,9 +79,10 @@ MultiSigScriptHashInput.prototype._serializeSignatures = function() { MultiSigScriptHashInput.prototype.getScriptCode = function() { var writer = new BufferWriter(); - if (!this.script.hasCodeseparators()) { - writer.writeVarintNum(this._scriptBuffer.length); - writer.write(this._scriptBuffer); + if (!this.redeemScript.hasCodeseparators()) { + var redeemScriptBuffer = this.redeemScript.toBuffer(); + writer.writeVarintNum(redeemScriptBuffer.length); + writer.write(redeemScriptBuffer); } else { throw new Error('@TODO'); } @@ -94,6 +96,19 @@ MultiSigScriptHashInput.prototype.getSatoshisBuffer = function() { return buffer; }; +MultiSigScriptHashInput.prototype.getSighash = function(transaction, privateKey, index, sigtype) { + var self = this; + var hash; + if (self.nestedWitness) { + var scriptCode = self.getScriptCode(); + var satoshisBuffer = self.getSatoshisBuffer(); + hash = SighashWitness.sighash(transaction, sigtype, index, scriptCode, satoshisBuffer); + } else { + hash = Sighash.sighash(transaction, sigtype, index, self.redeemScript); + } + return hash; +}; + MultiSigScriptHashInput.prototype.getSignatures = function(transaction, privateKey, index, sigtype) { $.checkState(this.output instanceof Output); sigtype = sigtype || Signature.SIGHASH_ALL; @@ -134,15 +149,23 @@ MultiSigScriptHashInput.prototype.addSignature = function(transaction, signature }; MultiSigScriptHashInput.prototype._updateScript = function() { - var scriptSig = Script.buildP2SHMultisigIn( - this.publicKeys, - this.threshold, - this._createSignatures(), - { cachedMultisig: this.redeemScript } - ); if (this.nestedWitness) { - this.setWitnesses([scriptSig.toBuffer()]); + var stack = [ + new Buffer(0), + ]; + var signatures = this._createSignatures(); + for (var i = 0; i < signatures.length; i++) { + stack.push(signatures[i]); + } + stack.push(this.redeemScript.toBuffer()); + this.setWitnesses(stack); } else { + var scriptSig = Script.buildP2SHMultisigIn( + this.publicKeys, + this.threshold, + this._createSignatures(), + { cachedMultisig: this.redeemScript } + ); this.setScript(scriptSig); } return this; diff --git a/lib/transaction/sighashwitness.js b/lib/transaction/sighashwitness.js index 0aff29db0..d2eede86e 100644 --- a/lib/transaction/sighashwitness.js +++ b/lib/transaction/sighashwitness.js @@ -84,7 +84,9 @@ var sighash = function sighash(transaction, sighashType, inputNumber, scriptCode writer.writeUInt32LE(transaction.inputs[inputNumber].outputIndex); writer.write(scriptCode); + writer.write(satoshisBuffer); + writer.writeUInt32LE(transaction.inputs[inputNumber].sequenceNumber); // Outputs (none/one/all, depending on flags) @@ -113,7 +115,7 @@ var sighash = function sighash(transaction, sighashType, inputNumber, scriptCode */ function sign(transaction, privateKey, sighashType, inputIndex, scriptCode, satoshisBuffer) { var hashbuf = sighash(transaction, sighashType, inputIndex, scriptCode, satoshisBuffer); - var sig = ECDSA.sign(hashbuf, privateKey, 'little').set({ + var sig = ECDSA.sign(hashbuf, privateKey).set({ nhashtype: sighashType }); return sig; @@ -134,7 +136,7 @@ function verify(transaction, signature, publicKey, inputIndex, scriptCode, satos $.checkArgument(!_.isUndefined(transaction)); $.checkArgument(!_.isUndefined(signature) && !_.isUndefined(signature.nhashtype)); var hashbuf = sighash(transaction, signature.nhashtype, inputIndex, scriptCode, satoshisBuffer); - return ECDSA.verify(hashbuf, signature, publicKey, 'little'); + return ECDSA.verify(hashbuf, signature, publicKey); } /** diff --git a/test/transaction/input/multisigscripthash.js b/test/transaction/input/multisigscripthash.js index 911865c21..1f85f3ce1 100644 --- a/test/transaction/input/multisigscripthash.js +++ b/test/transaction/input/multisigscripthash.js @@ -125,7 +125,7 @@ describe('MultiSigScriptHashInput', function() { .to(address, 1000000); var input = transaction.inputs[0]; var scriptCode = input.getScriptCode(); - scriptCode.toString('hex').should.equal('2200206aac01b83b1537afe2aa96d13a339b8a109b05f6eaf37d5840fe5f227daedacc'); + scriptCode.toString('hex').should.equal('695221025c95ec627038e85b5688a9b3d84d28c5ebe66e8c8d697d498e20fe96e3b1ab1d2102cdddfc974d41a62f1f80081deee70592feb7d6e6cf6739d6592edbe7946720e72103c95924e02c240b5545089c69c6432447412b58be43fd671918bd184a5009834353ae'); }); it('will get the satoshis buffer for nested witness', function() { var address = Address.createMultisig([public1, public2, public3], 2, 'testnet', true); diff --git a/test/transaction/transaction.js b/test/transaction/transaction.js index 7f3ca5b42..edb12e315 100644 --- a/test/transaction/transaction.js +++ b/test/transaction/transaction.js @@ -1250,6 +1250,77 @@ describe('Transaction', function() { tx.hash.should.equal('7f1a2d46746f1bfbb22ab797d5aad1fd9723477b417fa34dff73d8a7dbb14570'); tx.witnessHash.should.equal('3c26fc8b5cfe65f96d955cecfe4d11db2659d052171f9f31af043e9f5073e46b'); }); + it('round trip nested witness p2sh', function() { + var txBuffer = new Buffer('010000000001010894bb2bbfd5249b1c55f7bc64352bb64894938bc6439f43f28a58bfa7c73205000000002322002077b16b966ee6a4b8a0901351221d279afd31d3f90df52a3fc53436ea9abde5b0ffffffff01010000000000000000030047304402200fa23efa9a8d6ae285cfc82f81e6c2196d14167553b10da1845abd2c9fe38dc502207a40a58ee5b739e902b275018dfa1bee0d608736ff4317b028fbc29391f4554f01475221037b8dc5861a0ef7b0a97b41d2d1e27186f019d4834dbc99f24952b6f5080f5cce21027152378182102b68b5fce42f9f365ec272c48afda6b0816e735c1dc4b96dd45a52ae00000000', 'hex'); + var tx = bitcore.Transaction().fromBuffer(txBuffer); + tx.toBuffer().toString('hex').should.equal(txBuffer.toString('hex')); + }); + describe('verifying', function() { + it('will verify these signatures', function() { + var signedTxBuffer = new Buffer('0100000000010103752b9d2baadb95480e2571a4854a68ffd8264462168346461b7cdda76beac20000000023220020fde78ea47ae10cc93c6a850d8a86d8575ddacff38ee9b0bc6535dc016a197068ffffffff010100000000000000000400483045022100ea1508225a6d37c0545d22acaee88d29d1675696953f93d657a419613bcee9b802207b8d80ca8176586878f51e001cb9e92f7640b8c9dc530fabf9087142c752de89014830450221008c6f4a9ebdee89968ec00ecc12fda67442b589296e86bf3e9bde19f4ba923406022048c3409831a55bf61f2d5defffd3b91767643b6c5981cb32338dd7e9f02821b1014752210236c8204d62fd70e7ca206a36d39f9674fa832964d787c60d44250624242bada4210266cd5a3507d6df5346aa42bd23d4c44c079aef0d7a59534758a0dabb82345c2052ae00000000', 'hex'); + var unsignedBuffer = new Buffer('0100000000010103752b9d2baadb95480e2571a4854a68ffd8264462168346461b7cdda76beac20000000023220020fde78ea47ae10cc93c6a850d8a86d8575ddacff38ee9b0bc6535dc016a197068ffffffff010100000000000000000300483045022100ea1508225a6d37c0545d22acaee88d29d1675696953f93d657a419613bcee9b802207b8d80ca8176586878f51e001cb9e92f7640b8c9dc530fabf9087142c752de89014752210236c8204d62fd70e7ca206a36d39f9674fa832964d787c60d44250624242bada4210266cd5a3507d6df5346aa42bd23d4c44c079aef0d7a59534758a0dabb82345c2052ae00000000', 'hex'); + var signedTx = bitcore.Transaction().fromBuffer(signedTxBuffer); + + var signatures = [ + { + publicKey: '0236c8204d62fd70e7ca206a36d39f9674fa832964d787c60d44250624242bada4', + prevTxId: 'c2ea6ba7dd7c1b46468316624426d8ff684a85a471250e4895dbaa2b9d2b7503', + outputIndex: 0, + inputIndex: 0, + signature: '3045022100ea1508225a6d37c0545d22acaee88d29d1675696953f93d657a419613bcee9b802207b8d80ca8176586878f51e001cb9e92f7640b8c9dc530fabf9087142c752de89', + sigtype: bitcore.crypto.Signature.SIGHASH_ALL + }, + { + publicKey: '0266cd5a3507d6df5346aa42bd23d4c44c079aef0d7a59534758a0dabb82345c20', + prevTxId: 'c2ea6ba7dd7c1b46468316624426d8ff684a85a471250e4895dbaa2b9d2b7503', + outputIndex: 0, + inputIndex: 0, + signature: '30450221008c6f4a9ebdee89968ec00ecc12fda67442b589296e86bf3e9bde19f4ba923406022048c3409831a55bf61f2d5defffd3b91767643b6c5981cb32338dd7e9f02821b1', + sigtype: bitcore.crypto.Signature.SIGHASH_ALL + } + ]; + + var pubkey1 = bitcore.PublicKey('0236c8204d62fd70e7ca206a36d39f9674fa832964d787c60d44250624242bada4'); + var pubkey3 = bitcore.PublicKey('0266cd5a3507d6df5346aa42bd23d4c44c079aef0d7a59534758a0dabb82345c20'); + var expectedDestScript = bitcore.Script('a914382ead50307554bcdda12e1238368e9f0e10b11787'); + var expectedMultiSigString = '52210236c8204d62fd70e7ca206a36d39f9674fa832964d787c60d44250624242bada4210266cd5a3507d6df5346aa42bd23d4c44c079aef0d7a59534758a0dabb82345c2052ae'; + var expectedMultiSig = bitcore.Script(expectedMultiSigString); + var multiSig = bitcore.Script.buildMultisigOut([pubkey1, pubkey3], 2, { + noSorting: true + }); + multiSig.toBuffer().toString('hex').should.equal(expectedMultiSigString); + var wits = bitcore.Script.buildWitnessMultisigOutFromScript(multiSig); + + var expectedWits = bitcore.Script('0020fde78ea47ae10cc93c6a850d8a86d8575ddacff38ee9b0bc6535dc016a197068'); + wits.toBuffer().toString('hex').should.equal('0020fde78ea47ae10cc93c6a850d8a86d8575ddacff38ee9b0bc6535dc016a197068'); + + var address = Address.payingTo(wits); + address.hashBuffer.toString('hex').should.equal('382ead50307554bcdda12e1238368e9f0e10b117'); + + var destScript = Script.buildScriptHashOut(wits); + destScript.toBuffer().toString('hex').should.equal('a914382ead50307554bcdda12e1238368e9f0e10b11787'); + + var input = new Transaction.Input.MultiSigScriptHash({ + output: new Output({ + script: destScript, + satoshis: 1 + }), + prevTxId: 'c2ea6ba7dd7c1b46468316624426d8ff684a85a471250e4895dbaa2b9d2b7503', + outputIndex: 0, + script: Script('220020fde78ea47ae10cc93c6a850d8a86d8575ddacff38ee9b0bc6535dc016a197068') + }, [pubkey1, pubkey3], 2, signatures, true); + + signedTx.inputs[0] = input; + signedTx.inputs[0]._updateScript(); + signedTx.toBuffer().toString('hex').should.equal(signedTxBuffer.toString('hex')); + + var valid1 = signedTx.inputs[0].isValidSignature(signedTx, signedTx.inputs[0].signatures[1]); + valid1.should.equal(true); + + var valid = signedTx.inputs[0].isValidSignature(signedTx, signedTx.inputs[0].signatures[0]); + valid.should.equal(true); + }); + }); describe('signing', function() { var privateKey1 = PrivateKey.fromWIF('cNuW8LX2oeQXfKKCGxajGvqwhCgBtacwTQqiCGHzzKfmpHGY4TE9'); var publicKey1 = p2shPrivateKey1.toPublicKey(); @@ -1258,26 +1329,25 @@ describe('Transaction', function() { var privateKey3 = PrivateKey.fromWIF('cQFMZ5gP9CJtUZPc9X3yFae89qaiQLspnftyxxLGvVNvM6tS6mYY'); var publicKey3 = p2shPrivateKey3.toPublicKey(); var address = Address.createMultisig([ - publicKey1, - publicKey2, - publicKey3 - ], 2, 'segnet', true); + publicKey1 + ], 1, 'segnet', true); var utxo = { address: address.toString(), - txId: '72a0b3d6dcbba9d2ae74c11eec05cdfc2a03e1a01b3bbb09af0cc2dc8c3dbefa', - outputIndex: 0, + txId: '1d732950d99f821b8a8d11972ea56000b0666e4d31fa71861ffd80a83797dc61', + outputIndex: 1, script: Script.buildScriptHashOut(address).toHex(), satoshis: 1e8 }; it('will sign with nested p2sh witness program', function() { var tx = new Transaction() - .from(utxo, [publicKey1, publicKey2, publicKey3], 2, true) + .from(utxo, [publicKey1], 1, true) .to([{address: 'DSy1mtq7NeVEWKjBoXb4wgujgLv7cLE9Vb', satoshis: 50000}]) .fee(150000) .change('DF8MrzMiDkTvT4qpeCAdW6Y37WGgbo9Cmu') .sign(privateKey1) - .sign(privateKey2); - // TODO: check correct signature + var sighash = tx.inputs[0].getSighash(tx, privateKey1, 0, bitcore.crypto.Signature.SIGHASH_ALL); + sighash.toString('hex').should.equal('51b7c5271ae04071a6d3d4c4cde28003d8e9a09e51931ebae4003539767a4955'); + tx.toBuffer().toString('hex').should.equal('0100000000010161dc9737a880fd1f8671fa314d6e66b00060a52e97118d8a1b829fd95029731d010000002322002028ba8620c84df12e3283de37d02cfa7bcae3894e118388d6b3ae50f9aeb38798ffffffff0250c30000000000001976a914ef6aa14d8f5ba65a12c327a9659681c44cd821b088acc0d3f205000000001976a9146d8da2015c6d2890896485edd5897b3b2ec9ebb188ac030047304402203fdbd6604939ed9b46bd07bea993b102336a6fbc0a0c987f05b8522a2079037f022064466db4b0c6cc6697a28e0ba9b28c9738ecba56033a60aab7f04d5da2a8241e0125512102feab7deafbdb39885ef92a285dfa0f4ada0feefce43685e6551c95e71496d98051ae00000000'); }); }); }); From ba54810df897d10d06f2c28b1e5dd36a48f1c185 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Sat, 12 Mar 2016 19:01:18 -0500 Subject: [PATCH 09/23] transaction: support nodejs 0.10.x for sighash witness --- lib/transaction/sighashwitness.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/transaction/sighashwitness.js b/lib/transaction/sighashwitness.js index d2eede86e..7fe04a986 100644 --- a/lib/transaction/sighashwitness.js +++ b/lib/transaction/sighashwitness.js @@ -39,7 +39,7 @@ var sighash = function sighash(transaction, sighashType, inputNumber, scriptCode var prevTxIdBuffer = new BufferReader(input.prevTxId).readReverse(); buffers.push(prevTxIdBuffer); var outputIndexBuffer = new Buffer(new Array(4)); - outputIndexBuffer.writeUInt32LE(input.outputIndex); + outputIndexBuffer.writeUInt32LE(input.outputIndex, 0); buffers.push(outputIndexBuffer); } hashPrevouts = Hash.sha256sha256(Buffer.concat(buffers)); @@ -51,7 +51,7 @@ var sighash = function sighash(transaction, sighashType, inputNumber, scriptCode var sequenceBuffers = []; for (var m = 0; m < transaction.inputs.length; m++) { var sequenceBuffer = new Buffer(new Array(4)); - sequenceBuffer.writeUInt32LE(transaction.inputs[m].sequenceNumber); + sequenceBuffer.writeUInt32LE(transaction.inputs[m].sequenceNumber, 0); sequenceBuffers.push(sequenceBuffer); } hashSequence = Hash.sha256sha256(Buffer.concat(sequenceBuffers)); From ad7785d3dd390946847268390116c56d1ff95f44 Mon Sep 17 00:00:00 2001 From: Thomas Kerin Date: Sun, 13 Mar 2016 17:26:46 +0000 Subject: [PATCH 10/23] Start of verification support --- lib/script/script.js | 39 ++++++++++++++++++++++++++++++++++ lib/transaction/transaction.js | 20 ++++++++++++++++- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/lib/script/script.js b/lib/script/script.js index 4fd45a1bd..529c200df 100644 --- a/lib/script/script.js +++ b/lib/script/script.js @@ -404,6 +404,30 @@ Script.prototype.isWitnessPublicKeyHashOut = function() { return (buf.length === 22 && buf[0] === 0 && buf[1] === 20); }; +/** + * @returns {boolean} if this is a p2wpkh output script + */ +Script.prototype.isWitnessProgram = function() { + var buf = this.toBuffer(); + if (buf.length < 4 || buf.length > 34) { + return false; + } + if (buf[0] !== Opcode.OP_0 && (buf[0] < Opcode.OP_1 && buf[1] > Opcode.OP_16)) { + return false; + } + + if (buf.length == buf[1] + 2) { + return true; + } + + return false; +}; + +Script.prototype.toProgram = function () { + var buf = this.toBuffer(); + return buf.slice(2, buf.end); +}; + /** * @returns {boolean} if this is a p2sh input script * Note that these are frequently indistinguishable from pubkeyhashin @@ -1117,4 +1141,19 @@ Script.prototype.getSignatureOperationsCount = function(accurate) { return n; }; +Script.prototype.witnessSignatureOperationsCount = function (version, program, witness, flags) { + if (version == 0) { + if (program.length == 20) { + return 1; + } + + if (program.length == 32 && witness.size > 0) { + var subscript = new Script(witness.back); + return subscript.getSignatureOperationsCount(true); + } + } + + return 0; +}; + module.exports = Script; diff --git a/lib/transaction/transaction.js b/lib/transaction/transaction.js index c23ab2bd4..e49d647b7 100644 --- a/lib/transaction/transaction.js +++ b/lib/transaction/transaction.js @@ -13,6 +13,7 @@ var BufferWriter = require('../encoding/bufferwriter'); var Hash = require('../crypto/hash'); var Signature = require('../crypto/signature'); var Sighash = require('./sighash'); +var SighashWitness = require('./sighashwitness'); var Address = require('../address'); var UnspentOutput = require('./unspentoutput'); @@ -1193,7 +1194,24 @@ Transaction.prototype.isValidSignature = function(signature) { /** * @returns {bool} whether the signature is valid for this transaction input */ -Transaction.prototype.verifySignature = function(sig, pubkey, nin, subscript) { +Transaction.prototype.verifySignature = function(sig, pubkey, nin, subscript, sigversion, satoshisBuffer) { + + if (_.isUndefined(sigversion)) { + sigversion = 0; + } + + if (_.isUndefined(satoshisBuffer)) { + if (sigversion == 1) { + throw new errors.Transaction.UnableToVerifySignature( + 'satoshisBuffer is required when sigversion is 1' + ); + } + } + + if (sigversion == 1) { + return SighashWitness.verify(this, sig, pubkey, nin, subscript, satoshisBuffer); + } + return Sighash.verify(this, sig, pubkey, nin, subscript); }; From 2b2ca0944a5d1e43104b30d064e5b486eaa4915b Mon Sep 17 00:00:00 2001 From: Thomas Kerin Date: Sun, 13 Mar 2016 18:09:10 +0000 Subject: [PATCH 11/23] Input: add getSatoshisBuffer method --- lib/transaction/input/input.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/transaction/input/input.js b/lib/transaction/input/input.js index 6d0635f36..91d6542ae 100644 --- a/lib/transaction/input/input.js +++ b/lib/transaction/input/input.js @@ -154,6 +154,13 @@ Input.prototype.getSignatures = function() { ); }; +Input.prototype.getSatoshisBuffer = function () { + $.checkState(this.output instanceof Output); + $.checkState(this.output._satoshisBN); + return new BufferWriter().writeUInt64LEBN(this.output._satoshisBN).toBuffer(); +}; + + Input.prototype.isFullySigned = function() { throw new errors.AbstractMethodInvoked('Input#isFullySigned'); }; From 1aa55790aed7cb2a958cf67548fbbe854ed719b0 Mon Sep 17 00:00:00 2001 From: Thomas Kerin Date: Sun, 13 Mar 2016 18:09:56 +0000 Subject: [PATCH 12/23] Transaction.verifySignature(): no longer require satoshisBuffer param, can call Input method --- lib/transaction/transaction.js | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/lib/transaction/transaction.js b/lib/transaction/transaction.js index e49d647b7..1c5202fbf 100644 --- a/lib/transaction/transaction.js +++ b/lib/transaction/transaction.js @@ -1194,22 +1194,14 @@ Transaction.prototype.isValidSignature = function(signature) { /** * @returns {bool} whether the signature is valid for this transaction input */ -Transaction.prototype.verifySignature = function(sig, pubkey, nin, subscript, sigversion, satoshisBuffer) { +Transaction.prototype.verifySignature = function(sig, pubkey, nin, subscript, sigversion) { if (_.isUndefined(sigversion)) { sigversion = 0; } - if (_.isUndefined(satoshisBuffer)) { - if (sigversion == 1) { - throw new errors.Transaction.UnableToVerifySignature( - 'satoshisBuffer is required when sigversion is 1' - ); - } - } - if (sigversion == 1) { - return SighashWitness.verify(this, sig, pubkey, nin, subscript, satoshisBuffer); + return SighashWitness.verify(this, sig, pubkey, nin, subscript, this.inputs[nin].getSatoshisBuffer()); } return Sighash.verify(this, sig, pubkey, nin, subscript); From b48bb429536beeabf7a2d025ee6f1cb43e81ca4f Mon Sep 17 00:00:00 2001 From: Thomas Kerin Date: Sun, 13 Mar 2016 18:11:55 +0000 Subject: [PATCH 13/23] Add support for verifying segwit scripts --- lib/script/interpreter.js | 132 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 127 insertions(+), 5 deletions(-) diff --git a/lib/script/interpreter.js b/lib/script/interpreter.js index 31e132f71..e7e164b97 100644 --- a/lib/script/interpreter.js +++ b/lib/script/interpreter.js @@ -31,6 +31,70 @@ var Interpreter = function Interpreter(obj) { } }; +Interpreter.prototype.verifyWitnessProgram = function(version, program, witness, satoshis, flags) { + var scriptPubKey = new Script; + var stack = []; + + if (version == 0) { + if (program.length == 32) { + if (witness.length == 0) { + this.errstr = 'v0 scripthash program empty'; + return false; + } + + scriptPubKey = witness[witness.length - 1]; + var hash = Hash.sha256(scriptPubKey); + if (hash !== program.script) { + this.errstr = 'witness program mismatch'; + return false; + } + + stack = witness.slice(0, -1); + } else if (program.script.length == 20) { + if (witness.length != 2) { + this.errstr = 'witness program mismatch'; + return false; + } + + scriptPubKey.add(Opcode.OP_DUP).add(Opcode.OP_HASH160).add(program.script).add(Opcode.OP_EQUALVERIFY).add(Opcode.OP_CHECKSIG); + stack = witness; + } else { + this.errstr = 'Witness program wrong length'; + return false; + } + } else if ((flags & this.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)) { + this.errstr = 'Upgradeable witness program discouraged'; + return false; + } else { + return true; + } + + this.set({ + script: scriptPubKey, + stack: stack, + sigversion: 1, + satoshis: satoshis + }); + + if (!this.evaluate()) { + return false; + } + + if (this.stack.length !== 1) { + this.errstr = 'SCRIPT_ERR_EVAL_FALSE'; + return false; + } + var buf = this.stack[this.stack.length - 1]; + if (!Interpreter.castToBool(buf)) { + this.errstr = 'SCRIPT_ERR_EVAL_FALSE_IN_STACK'; + return false; + } + + return true; +}; + + + /** * Verifies a Script by executing it and returns true if it is valid. * This function needs to be provided with the scriptSig and the scriptPubkey @@ -41,10 +105,13 @@ var Interpreter = function Interpreter(obj) { * to check signature validity for some opcodes like OP_CHECKSIG) * @param {number} nin - index of the transaction input containing the scriptSig verified. * @param {number} flags - evaluation flags. See Interpreter.SCRIPT_* constants + * @param {number} witness - array of witness data + * @param {number} satoshis - number of satoshis created by this output * * Translated from bitcoind's VerifyScript */ -Interpreter.prototype.verify = function(scriptSig, scriptPubkey, tx, nin, flags) { + Interpreter.prototype.verify = function(scriptSig, scriptPubkey, tx, nin, flags, witness, satoshis) { + var Transaction = require('../transaction'); if (_.isUndefined(tx)) { tx = new Transaction(); @@ -55,10 +122,19 @@ Interpreter.prototype.verify = function(scriptSig, scriptPubkey, tx, nin, flags) if (_.isUndefined(flags)) { flags = 0; } + if (_.isUndefined(witness)) { + witness = null; + } + if (_.isUndefined(satoshis)) { + satoshis = 0; + } + this.set({ script: scriptSig, tx: tx, nin: nin, + sigversion: 0, + satoshis: 0, flags: flags }); var stackCopy; @@ -103,6 +179,24 @@ Interpreter.prototype.verify = function(scriptSig, scriptPubkey, tx, nin, flags) return false; } + var hadWitness = false; + var version, program; + + if ((flags & Interpreter.SCRIPT_VERIFY_WITNESS)) { + if (scriptPubkey.isWitnessProgram()) { + version = scriptPubkey[0]; + program = s.toProgram(); + hadWitness = true; + if (scriptSig.toBuffer().length != 0) { + return false; + } + + if (!this.verifyWitnessProgram(version, program, witness, satoshis, flags)) { + return false; + } + } + } + // Additional validation for spend-to-script-hash transactions: if ((flags & Interpreter.SCRIPT_VERIFY_P2SH) && scriptPubkey.isScriptHashOut()) { // scriptSig must be literals-only or validation fails @@ -144,8 +238,30 @@ Interpreter.prototype.verify = function(scriptSig, scriptPubkey, tx, nin, flags) if (!Interpreter.castToBool(stackCopy[stackCopy.length - 1])) { this.errstr = 'SCRIPT_ERR_EVAL_FALSE_IN_P2SH_STACK'; return false; - } else { - return true; + } + if ((flags & this.SCRIPT_VERIFY_WITNESS)) { + if (redeemScript.isWitnessOut()) { + version = redeemScript[0]; + program = redeemScript.toProgram(); + hadWitness = true; + if (scriptSig !== redeemScript) { + this.errstr = 'Malleated scriptSig'; + return false; + } + + if (!this.verifyWitnessProgram(version, program, witness, satoshis, flags)) { + return false; + } + + stack = [stack[0]]; + } + } + + if ((flags & this.SCRIPT_VERIFY_WITNESS)) { + if (!hadWitness && witness.length > 0) { + this.errstr = 'Witness unexpected'; + return false; + } } } @@ -158,6 +274,8 @@ Interpreter.prototype.initialize = function(obj) { this.stack = []; this.altstack = []; this.pc = 0; + this.satoshis = 0; + this.sigversion = 0; this.pbegincodehash = 0; this.nOpCount = 0; this.vfExec = []; @@ -173,6 +291,8 @@ Interpreter.prototype.set = function(obj) { this.altstack = obj.altack || this.altstack; this.pc = typeof obj.pc !== 'undefined' ? obj.pc : this.pc; this.pbegincodehash = typeof obj.pbegincodehash !== 'undefined' ? obj.pbegincodehash : this.pbegincodehash; + this.sigversion = typeof obj.sigversion !== 'undefined' ? obj.sigversion : this.sigversion; + this.satoshis = typeof obj.satoshis !== 'undefined' ? obj.satoshis : this.satoshis; this.nOpCount = typeof obj.nOpCount !== 'undefined' ? obj.nOpCount : this.nOpCount; this.vfExec = obj.vfExec || this.vfExec; this.errstr = obj.errstr || this.errstr; @@ -231,6 +351,8 @@ Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS = (1 << 7); // CLTV See BIP65 for details. Interpreter.SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY = (1 << 9); +Interpreter.SCRIPT_VERIFY_WITNESS = (1 << 10); +Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS = (1 << 11); Interpreter.castToBool = function(buf) { for (var i = 0; i < buf.length; i++) { @@ -1111,7 +1233,7 @@ Interpreter.prototype.step = function() { try { sig = Signature.fromTxFormat(bufSig); pubkey = PublicKey.fromBuffer(bufPubkey, false); - fSuccess = this.tx.verifySignature(sig, pubkey, this.nin, subscript); + fSuccess = this.tx.verifySignature(sig, pubkey, this.nin, subscript, this.sigversion); } catch (e) { //invalid sig or pubkey fSuccess = false; @@ -1200,7 +1322,7 @@ Interpreter.prototype.step = function() { try { sig = Signature.fromTxFormat(bufSig); pubkey = PublicKey.fromBuffer(bufPubkey, false); - fOk = this.tx.verifySignature(sig, pubkey, this.nin, subscript); + fOk = this.tx.verifySignature(sig, pubkey, this.nin, subscript, this.sigversion); } catch (e) { //invalid sig or pubkey fOk = false; From b0bbcb3dc7865948c9c02a1e63e924439361cd86 Mon Sep 17 00:00:00 2001 From: Thomas Kerin Date: Sun, 13 Mar 2016 18:17:30 +0000 Subject: [PATCH 14/23] test case of multisig|witness|p2sh --- test/transaction/transaction.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/test/transaction/transaction.js b/test/transaction/transaction.js index edb12e315..b09a2a5b2 100644 --- a/test/transaction/transaction.js +++ b/test/transaction/transaction.js @@ -14,6 +14,7 @@ var Input = bitcore.Transaction.Input; var Output = bitcore.Transaction.Output; var PrivateKey = bitcore.PrivateKey; var Script = bitcore.Script; +var Interpreter = bitcore.Script.Interpreter; var Address = bitcore.Address; var Networks = bitcore.Networks; var Opcode = bitcore.Opcode; @@ -1300,10 +1301,11 @@ describe('Transaction', function() { var destScript = Script.buildScriptHashOut(wits); destScript.toBuffer().toString('hex').should.equal('a914382ead50307554bcdda12e1238368e9f0e10b11787'); + var signedamount = 1; var input = new Transaction.Input.MultiSigScriptHash({ output: new Output({ script: destScript, - satoshis: 1 + satoshis: signedamount }), prevTxId: 'c2ea6ba7dd7c1b46468316624426d8ff684a85a471250e4895dbaa2b9d2b7503', outputIndex: 0, @@ -1314,6 +1316,15 @@ describe('Transaction', function() { signedTx.inputs[0]._updateScript(); signedTx.toBuffer().toString('hex').should.equal(signedTxBuffer.toString('hex')); + var interpreter = new Interpreter(); + var flags = Interpreter.SCRIPT_VERIFY_P2SH | Interpreter.SCRIPT_VERIFY_WITNESS; + + var check = interpreter.verify(signedTx.inputs[0].script, destScript, signedTx, 0, flags, input.getWitnesses(), signedamount); + check.should.equal(true); + + check = interpreter.verify(signedTx.inputs[0].script, destScript, signedTx, 0, flags, input.getWitnesses(), 1999199); + check.should.equal(false); + var valid1 = signedTx.inputs[0].isValidSignature(signedTx, signedTx.inputs[0].signatures[1]); valid1.should.equal(true); From c6079e95560571a6404c414ffe12b8099cb1bbeb Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Mon, 14 Mar 2016 21:16:40 -0400 Subject: [PATCH 15/23] script: interpreter witness program fixes --- lib/script/interpreter.js | 38 ++++++++++++++++++---------------- lib/script/script.js | 19 ++++++++++------- lib/transaction/transaction.js | 7 ++++++- 3 files changed, 37 insertions(+), 27 deletions(-) diff --git a/lib/script/interpreter.js b/lib/script/interpreter.js index e7e164b97..341e3c754 100644 --- a/lib/script/interpreter.js +++ b/lib/script/interpreter.js @@ -32,25 +32,26 @@ var Interpreter = function Interpreter(obj) { }; Interpreter.prototype.verifyWitnessProgram = function(version, program, witness, satoshis, flags) { - var scriptPubKey = new Script; + var scriptPubKey = new Script(); var stack = []; - if (version == 0) { + if (version === 0) { if (program.length == 32) { if (witness.length == 0) { this.errstr = 'v0 scripthash program empty'; return false; } - scriptPubKey = witness[witness.length - 1]; - var hash = Hash.sha256(scriptPubKey); - if (hash !== program.script) { + var scriptPubKeyBuffer = witness[witness.length - 1]; + scriptPubKey = new Script(scriptPubKeyBuffer); + var hash = Hash.sha256(scriptPubKeyBuffer); + if (hash.toString('hex') !== program.toString('hex')) { this.errstr = 'witness program mismatch'; return false; } stack = witness.slice(0, -1); - } else if (program.script.length == 20) { + } else if (program.length == 20) { if (witness.length != 2) { this.errstr = 'witness program mismatch'; return false; @@ -69,6 +70,7 @@ Interpreter.prototype.verifyWitnessProgram = function(version, program, witness, return true; } + this.initialize(); this.set({ script: scriptPubKey, stack: stack, @@ -110,7 +112,7 @@ Interpreter.prototype.verifyWitnessProgram = function(version, program, witness, * * Translated from bitcoind's VerifyScript */ - Interpreter.prototype.verify = function(scriptSig, scriptPubkey, tx, nin, flags, witness, satoshis) { +Interpreter.prototype.verify = function(scriptSig, scriptPubkey, tx, nin, flags, witness, satoshis) { var Transaction = require('../transaction'); if (_.isUndefined(tx)) { @@ -183,15 +185,14 @@ Interpreter.prototype.verifyWitnessProgram = function(version, program, witness, var version, program; if ((flags & Interpreter.SCRIPT_VERIFY_WITNESS)) { - if (scriptPubkey.isWitnessProgram()) { - version = scriptPubkey[0]; - program = s.toProgram(); + var witnessValues = {}; + if (scriptPubkey.isWitnessProgram(witnessValues)) { hadWitness = true; if (scriptSig.toBuffer().length != 0) { return false; } - if (!this.verifyWitnessProgram(version, program, witness, satoshis, flags)) { + if (!this.verifyWitnessProgram(witnessValues.version, witnessValues.program, witness, satoshis, flags)) { return false; } } @@ -239,17 +240,18 @@ Interpreter.prototype.verifyWitnessProgram = function(version, program, witness, this.errstr = 'SCRIPT_ERR_EVAL_FALSE_IN_P2SH_STACK'; return false; } - if ((flags & this.SCRIPT_VERIFY_WITNESS)) { - if (redeemScript.isWitnessOut()) { - version = redeemScript[0]; - program = redeemScript.toProgram(); + if ((flags & Interpreter.SCRIPT_VERIFY_WITNESS)) { + var witnessValues = {}; + if (redeemScript.isWitnessProgram(witnessValues)) { hadWitness = true; - if (scriptSig !== redeemScript) { + var redeemScriptPush = new Script(); + redeemScriptPush.add(redeemScript.toBuffer()); + if (scriptSig.toHex() !== redeemScriptPush.toHex()) { this.errstr = 'Malleated scriptSig'; return false; } - if (!this.verifyWitnessProgram(version, program, witness, satoshis, flags)) { + if (!this.verifyWitnessProgram(witnessValues.version, witnessValues.program, witness, satoshis, flags)) { return false; } @@ -257,7 +259,7 @@ Interpreter.prototype.verifyWitnessProgram = function(version, program, witness, } } - if ((flags & this.SCRIPT_VERIFY_WITNESS)) { + if ((flags & Interpreter.SCRIPT_VERIFY_WITNESS)) { if (!hadWitness && witness.length > 0) { this.errstr = 'Witness unexpected'; return false; diff --git a/lib/script/script.js b/lib/script/script.js index 529c200df..ab7291766 100644 --- a/lib/script/script.js +++ b/lib/script/script.js @@ -405,29 +405,32 @@ Script.prototype.isWitnessPublicKeyHashOut = function() { }; /** + * @param {Object=} values - The return values + * @param {Number} values.version - Set with the witness version + * @param {Buffer} values.program - Set with the witness program * @returns {boolean} if this is a p2wpkh output script */ -Script.prototype.isWitnessProgram = function() { +Script.prototype.isWitnessProgram = function(values) { + if (!values) { + values = {}; + } var buf = this.toBuffer(); if (buf.length < 4 || buf.length > 34) { return false; } - if (buf[0] !== Opcode.OP_0 && (buf[0] < Opcode.OP_1 && buf[1] > Opcode.OP_16)) { + if (buf[0] >= Opcode.OP_0 && (buf[0] < 1 && buf[0] > 16)) { return false; } - if (buf.length == buf[1] + 2) { + if (buf.length === buf[1] + 2) { + values.version = buf[0]; + values.program = buf.slice(2, buf.length); return true; } return false; }; -Script.prototype.toProgram = function () { - var buf = this.toBuffer(); - return buf.slice(2, buf.end); -}; - /** * @returns {boolean} if this is a p2sh input script * Note that these are frequently indistinguishable from pubkeyhashin diff --git a/lib/transaction/transaction.js b/lib/transaction/transaction.js index 1c5202fbf..a72e64365 100644 --- a/lib/transaction/transaction.js +++ b/lib/transaction/transaction.js @@ -1201,7 +1201,12 @@ Transaction.prototype.verifySignature = function(sig, pubkey, nin, subscript, si } if (sigversion == 1) { - return SighashWitness.verify(this, sig, pubkey, nin, subscript, this.inputs[nin].getSatoshisBuffer()); + var subscriptBuffer = subscript.toBuffer(); + var scriptCodeWriter = new BufferWriter(); + scriptCodeWriter.writeVarintNum(subscriptBuffer.length); + scriptCodeWriter.write(subscriptBuffer); + + return SighashWitness.verify(this, sig, pubkey, nin, scriptCodeWriter.toBuffer(), this.inputs[nin].getSatoshisBuffer()); } return Sighash.verify(this, sig, pubkey, nin, subscript); From ce9d092a10feeb8914b178ebebec52d66457dde1 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Tue, 22 Mar 2016 15:27:12 -0400 Subject: [PATCH 16/23] input: moved getSatoshisBuffer to general input --- lib/transaction/input/multisigscripthash.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/transaction/input/multisigscripthash.js b/lib/transaction/input/multisigscripthash.js index 43dd49051..0007a0097 100644 --- a/lib/transaction/input/multisigscripthash.js +++ b/lib/transaction/input/multisigscripthash.js @@ -89,13 +89,6 @@ MultiSigScriptHashInput.prototype.getScriptCode = function() { return writer.toBuffer(); }; -MultiSigScriptHashInput.prototype.getSatoshisBuffer = function() { - $.checkState(this.output instanceof Output); - $.checkState(this.output._satoshisBN); - var buffer = new BufferWriter().writeUInt64LEBN(this.output._satoshisBN).toBuffer(); - return buffer; -}; - MultiSigScriptHashInput.prototype.getSighash = function(transaction, privateKey, index, sigtype) { var self = this; var hash; From 42b3dc93d0eadc9bb8be14152bd64ee7656700de Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Fri, 30 Sep 2016 12:06:16 -0400 Subject: [PATCH 17/23] Switch segnet to testnet --- lib/networks.js | 19 ------------------- test/transaction/transaction.js | 8 ++++---- 2 files changed, 4 insertions(+), 23 deletions(-) diff --git a/lib/networks.js b/lib/networks.js index 957d6f87a..3a9381c5d 100644 --- a/lib/networks.js +++ b/lib/networks.js @@ -235,24 +235,6 @@ Object.defineProperty(testnet, 'dnsSeeds', { } }); -//TODO add networkMagic -addNetwork({ - name: 'segnet', - pubkeyhash: 0x1e, - privatekey: 0x9e, - scripthash: 0x32, - xpubkey: 0x053587CF, - xprivkey: 0x05358394, - port: 28333, - dnsSeeds: [] -}); - -/** - * @instance - * @member Networks#testnet - */ -var segnet = get('segnet'); - /** * @function * @member Networks#enableRegtest @@ -281,7 +263,6 @@ module.exports = { livenet: livenet, mainnet: livenet, testnet: testnet, - segnet: segnet, get: get, enableRegtest: enableRegtest, disableRegtest: disableRegtest diff --git a/test/transaction/transaction.js b/test/transaction/transaction.js index b09a2a5b2..c38a4bf9d 100644 --- a/test/transaction/transaction.js +++ b/test/transaction/transaction.js @@ -1341,7 +1341,7 @@ describe('Transaction', function() { var publicKey3 = p2shPrivateKey3.toPublicKey(); var address = Address.createMultisig([ publicKey1 - ], 1, 'segnet', true); + ], 1, 'testnet', true); var utxo = { address: address.toString(), txId: '1d732950d99f821b8a8d11972ea56000b0666e4d31fa71861ffd80a83797dc61', @@ -1352,10 +1352,10 @@ describe('Transaction', function() { it('will sign with nested p2sh witness program', function() { var tx = new Transaction() .from(utxo, [publicKey1], 1, true) - .to([{address: 'DSy1mtq7NeVEWKjBoXb4wgujgLv7cLE9Vb', satoshis: 50000}]) + .to([{address: 'n3LsXgyStG2CkS2CnWZtDqxTfCnXB8PvD9', satoshis: 50000}]) .fee(150000) - .change('DF8MrzMiDkTvT4qpeCAdW6Y37WGgbo9Cmu') - .sign(privateKey1) + .change('mqWDcnW3jMzthB8qdB9SnFam6N96GDqM4W') + .sign(privateKey1); var sighash = tx.inputs[0].getSighash(tx, privateKey1, 0, bitcore.crypto.Signature.SIGHASH_ALL); sighash.toString('hex').should.equal('51b7c5271ae04071a6d3d4c4cde28003d8e9a09e51931ebae4003539767a4955'); tx.toBuffer().toString('hex').should.equal('0100000000010161dc9737a880fd1f8671fa314d6e66b00060a52e97118d8a1b829fd95029731d010000002322002028ba8620c84df12e3283de37d02cfa7bcae3894e118388d6b3ae50f9aeb38798ffffffff0250c30000000000001976a914ef6aa14d8f5ba65a12c327a9659681c44cd821b088acc0d3f205000000001976a9146d8da2015c6d2890896485edd5897b3b2ec9ebb188ac030047304402203fdbd6604939ed9b46bd07bea993b102336a6fbc0a0c987f05b8522a2079037f022064466db4b0c6cc6697a28e0ba9b28c9738ecba56033a60aab7f04d5da2a8241e0125512102feab7deafbdb39885ef92a285dfa0f4ada0feefce43685e6551c95e71496d98051ae00000000'); From cf5a76091987ad4ff436cf55298939337e77b84a Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Fri, 30 Sep 2016 16:21:34 -0400 Subject: [PATCH 18/23] Add test for "Witness pay-to-compressed-pubkey (v0)" Using this code in the test to get test data: ``` string TxHexStr(CTransaction tx) { CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); ssTx << tx; std::string ssTxStr = ssTx.str(); return HexStr(ssTxStr); } std::cout << "scriptPubkey1: " + HexStr(scriptPubkey1) + "\n"; std::cout << "output1: " + TxHexStr(output1) + "\n"; ``` From commit bitcoin core 0.13.0 commit: a402396dce64c42ea73535b7dde4a9164d430438 In the file: src/test/transaction_tests.cpp#L493 --- lib/script/interpreter.js | 23 +++++++++++++++-------- lib/transaction/input/input.js | 2 +- lib/transaction/transaction.js | 21 ++++++++++++++++++--- test/transaction/transaction.js | 22 ++++++++++++++++++++++ 4 files changed, 56 insertions(+), 12 deletions(-) diff --git a/lib/script/interpreter.js b/lib/script/interpreter.js index 341e3c754..3839dae7e 100644 --- a/lib/script/interpreter.js +++ b/lib/script/interpreter.js @@ -36,8 +36,8 @@ Interpreter.prototype.verifyWitnessProgram = function(version, program, witness, var stack = []; if (version === 0) { - if (program.length == 32) { - if (witness.length == 0) { + if (program.length === 32) { + if (witness.length === 0) { this.errstr = 'v0 scripthash program empty'; return false; } @@ -51,14 +51,20 @@ Interpreter.prototype.verifyWitnessProgram = function(version, program, witness, } stack = witness.slice(0, -1); - } else if (program.length == 20) { - if (witness.length != 2) { + } else if (program.length === 20) { + if (witness.length !== 2) { this.errstr = 'witness program mismatch'; return false; } - scriptPubKey.add(Opcode.OP_DUP).add(Opcode.OP_HASH160).add(program.script).add(Opcode.OP_EQUALVERIFY).add(Opcode.OP_CHECKSIG); + scriptPubKey.add(Opcode.OP_DUP); + scriptPubKey.add(Opcode.OP_HASH160); + scriptPubKey.add(program); + scriptPubKey.add(Opcode.OP_EQUALVERIFY); + scriptPubKey.add(Opcode.OP_CHECKSIG); + stack = witness; + } else { this.errstr = 'Witness program wrong length'; return false; @@ -71,6 +77,7 @@ Interpreter.prototype.verifyWitnessProgram = function(version, program, witness, } this.initialize(); + this.set({ script: scriptPubKey, stack: stack, @@ -86,6 +93,7 @@ Interpreter.prototype.verifyWitnessProgram = function(version, program, witness, this.errstr = 'SCRIPT_ERR_EVAL_FALSE'; return false; } + var buf = this.stack[this.stack.length - 1]; if (!Interpreter.castToBool(buf)) { this.errstr = 'SCRIPT_ERR_EVAL_FALSE_IN_STACK'; @@ -182,13 +190,12 @@ Interpreter.prototype.verify = function(scriptSig, scriptPubkey, tx, nin, flags, } var hadWitness = false; - var version, program; if ((flags & Interpreter.SCRIPT_VERIFY_WITNESS)) { var witnessValues = {}; if (scriptPubkey.isWitnessProgram(witnessValues)) { hadWitness = true; - if (scriptSig.toBuffer().length != 0) { + if (scriptSig.toBuffer().length !== 0) { return false; } @@ -1235,7 +1242,7 @@ Interpreter.prototype.step = function() { try { sig = Signature.fromTxFormat(bufSig); pubkey = PublicKey.fromBuffer(bufPubkey, false); - fSuccess = this.tx.verifySignature(sig, pubkey, this.nin, subscript, this.sigversion); + fSuccess = this.tx.verifySignature(sig, pubkey, this.nin, subscript, this.sigversion, this.satoshis); } catch (e) { //invalid sig or pubkey fSuccess = false; diff --git a/lib/transaction/input/input.js b/lib/transaction/input/input.js index 91d6542ae..1c27a2e08 100644 --- a/lib/transaction/input/input.js +++ b/lib/transaction/input/input.js @@ -154,7 +154,7 @@ Input.prototype.getSignatures = function() { ); }; -Input.prototype.getSatoshisBuffer = function () { +Input.prototype.getSatoshisBuffer = function() { $.checkState(this.output instanceof Output); $.checkState(this.output._satoshisBN); return new BufferWriter().writeUInt64LEBN(this.output._satoshisBN).toBuffer(); diff --git a/lib/transaction/transaction.js b/lib/transaction/transaction.js index a72e64365..c2e9961f8 100644 --- a/lib/transaction/transaction.js +++ b/lib/transaction/transaction.js @@ -1194,19 +1194,34 @@ Transaction.prototype.isValidSignature = function(signature) { /** * @returns {bool} whether the signature is valid for this transaction input */ -Transaction.prototype.verifySignature = function(sig, pubkey, nin, subscript, sigversion) { +Transaction.prototype.verifySignature = function(sig, pubkey, nin, subscript, sigversion, satoshis) { if (_.isUndefined(sigversion)) { sigversion = 0; } - if (sigversion == 1) { + if (sigversion === 1) { var subscriptBuffer = subscript.toBuffer(); var scriptCodeWriter = new BufferWriter(); scriptCodeWriter.writeVarintNum(subscriptBuffer.length); scriptCodeWriter.write(subscriptBuffer); - return SighashWitness.verify(this, sig, pubkey, nin, scriptCodeWriter.toBuffer(), this.inputs[nin].getSatoshisBuffer()); + var satoshisBuffer; + if (satoshis) { + $.checkState(JSUtil.isNaturalNumber(satoshis)); + satoshisBuffer = new BufferWriter().writeUInt64LEBN(new BN(satoshis)).toBuffer(); + } else { + satoshisBuffer = this.inputs[nin].getSatoshisBuffer(); + } + var verified = SighashWitness.verify( + this, + sig, + pubkey, + nin, + scriptCodeWriter.toBuffer(), + satoshisBuffer + ); + return verified; } return Sighash.verify(this, sig, pubkey, nin, subscript); diff --git a/test/transaction/transaction.js b/test/transaction/transaction.js index c38a4bf9d..28418879e 100644 --- a/test/transaction/transaction.js +++ b/test/transaction/transaction.js @@ -1331,6 +1331,28 @@ describe('Transaction', function() { var valid = signedTx.inputs[0].isValidSignature(signedTx, signedTx.inputs[0].signatures[0]); valid.should.equal(true); }); + describe('Bitcoin Core tests', function() { + // from bitcoin core tests at src/test/transaction_tests.cpp + it('will verify pay-to-compressed publickey (v0)', function() { + var check; + var flags; + var interpreter = new Interpreter(); + var output1 = bitcore.Transaction('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff01010000000000000016001457d5e8f4701ae218576e4fdbcf702e4716808f5f00000000'); + var input1 = bitcore.Transaction('01000000000101da3ca8fe74ee2f6cc6ed02927a5fc8e9832f4ff6ad10521598f7985dcd5d17740000000000ffffffff010100000000000000000247304402202eee148a880846e3ebf9b61b5875a0c5121428d272a8336d10bae745ec401042022063b65baea1adc0e7a15801922242ab89d103143071680cfd4ba6072f8685a76c0121031fa0febd51842888a36c43873d1520c5b186894c5ac04520b096f8a3b49f8a5b00000000'); + var scriptPubkey = output1.outputs[0].script; + var scriptSig = input1.inputs[0].script; + var witnesses = input1.inputs[0].getWitnesses(); + var satoshis = 1; + + flags = Interpreter.SCRIPT_VERIFY_P2SH; + check = interpreter.verify(scriptSig, scriptPubkey, input1, 0, flags, witnesses, satoshis); + check.should.equal(true); + + flags = Interpreter.SCRIPT_VERIFY_P2SH | Interpreter.SCRIPT_VERIFY_WITNESS; + check = interpreter.verify(scriptSig, scriptPubkey, input1, 0, flags, witnesses, satoshis); + check.should.equal(true); + }); + }); }); describe('signing', function() { var privateKey1 = PrivateKey.fromWIF('cNuW8LX2oeQXfKKCGxajGvqwhCgBtacwTQqiCGHzzKfmpHGY4TE9'); From d40752ca9427c4cc2d3f12811990a11ff3375679 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Mon, 3 Oct 2016 17:00:00 -0400 Subject: [PATCH 19/23] Add more tests from bitcoin core From commit bitcoin core 0.13.0 commit: a402396dce64c42ea73535b7dde4a9164d430438 In the file: src/test/transaction_tests.cpp - pay-to-compressed publickey (v0) - p2sh witness pay-to-compressed pubkey (v0) - witness 2-of-2 multisig - p2sh witness 2-of-2 multisig --- lib/script/interpreter.js | 2 +- test/transaction/transaction.js | 185 +++++++++++++++++++++++++++++++- 2 files changed, 184 insertions(+), 3 deletions(-) diff --git a/lib/script/interpreter.js b/lib/script/interpreter.js index 3839dae7e..ffcec43fc 100644 --- a/lib/script/interpreter.js +++ b/lib/script/interpreter.js @@ -1331,7 +1331,7 @@ Interpreter.prototype.step = function() { try { sig = Signature.fromTxFormat(bufSig); pubkey = PublicKey.fromBuffer(bufPubkey, false); - fOk = this.tx.verifySignature(sig, pubkey, this.nin, subscript, this.sigversion); + fOk = this.tx.verifySignature(sig, pubkey, this.nin, subscript, this.sigversion, this.satoshis); } catch (e) { //invalid sig or pubkey fOk = false; diff --git a/test/transaction/transaction.js b/test/transaction/transaction.js index 28418879e..1d4fb52ae 100644 --- a/test/transaction/transaction.js +++ b/test/transaction/transaction.js @@ -1333,10 +1333,10 @@ describe('Transaction', function() { }); describe('Bitcoin Core tests', function() { // from bitcoin core tests at src/test/transaction_tests.cpp - it('will verify pay-to-compressed publickey (v0)', function() { + it('will verify pay-to-compressed publickey (v0) part 1', function() { var check; var flags; - var interpreter = new Interpreter(); + var interpreter; var output1 = bitcore.Transaction('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff01010000000000000016001457d5e8f4701ae218576e4fdbcf702e4716808f5f00000000'); var input1 = bitcore.Transaction('01000000000101da3ca8fe74ee2f6cc6ed02927a5fc8e9832f4ff6ad10521598f7985dcd5d17740000000000ffffffff010100000000000000000247304402202eee148a880846e3ebf9b61b5875a0c5121428d272a8336d10bae745ec401042022063b65baea1adc0e7a15801922242ab89d103143071680cfd4ba6072f8685a76c0121031fa0febd51842888a36c43873d1520c5b186894c5ac04520b096f8a3b49f8a5b00000000'); var scriptPubkey = output1.outputs[0].script; @@ -1344,10 +1344,191 @@ describe('Transaction', function() { var witnesses = input1.inputs[0].getWitnesses(); var satoshis = 1; + interpreter = new Interpreter(); flags = Interpreter.SCRIPT_VERIFY_P2SH; check = interpreter.verify(scriptSig, scriptPubkey, input1, 0, flags, witnesses, satoshis); check.should.equal(true); + interpreter = new Interpreter(); + flags = Interpreter.SCRIPT_VERIFY_P2SH | Interpreter.SCRIPT_VERIFY_WITNESS; + check = interpreter.verify(scriptSig, scriptPubkey, input1, 0, flags, witnesses, satoshis); + check.should.equal(true); + }); + it('will verify pay-to-compressed publickey (v0) part 2', function() { + var flags; + var check; + var interpreter; + var output1 = bitcore.Transaction('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff01010000000000000016001457d5e8f4701ae218576e4fdbcf702e4716808f5f00000000'); + var input2 = bitcore.Transaction('01000000000101cdc27b7132dc20e463d20458aa9d5c38e664ff114ddab8277af4ed859f2b90e20000000000ffffffff0101000000000000000002483045022100db56d1a70244f478a345478be51891b38b9a46140402cddf85b3024ca1652b4b02202c00aaa41ac941ce426ae358aa8372b63aeba945372002c47dc3725d9dca8343012103585c9f7105e09a0abbc60dc72d9d0a456030d0f10f7c47c0616e71c325085cbd00000000'); + var scriptPubkey = output1.outputs[0].script; + var scriptSig = input2.inputs[0].script; + var witnesses = input2.inputs[0].getWitnesses(); + var satoshis = 1; + + interpreter = new Interpreter(); + flags = Interpreter.SCRIPT_VERIFY_P2SH; + check = interpreter.verify(scriptSig, scriptPubkey, input2, 0, flags, witnesses, satoshis); + check.should.equal(true); + + interpreter = new Interpreter(); + flags = Interpreter.SCRIPT_VERIFY_P2SH | Interpreter.SCRIPT_VERIFY_WITNESS; + check = interpreter.verify(scriptSig, scriptPubkey, input2, 0, flags, witnesses, satoshis); + check.should.equal(false); + }); + it('will verify p2sh witness pay-to-compressed pubkey (v0) part 1', function() { + var flags; + var check; + var interpreter; + var output1 = bitcore.Transaction('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff01010000000000000017a914ca8abcc57aff5ba3fb36f76fe8e260ce6a08e0bf8700000000'); + var input1 = bitcore.Transaction('01000000000101b85d4c861b00d31ac95ae0b2cad8635d8310fb7ca86b44fefcbe2b98c4e905bd000000001716001469f84dbc7f9ae8626aa2d4aee6c73ef726b53ac2ffffffff0101000000000000000002483045022100c0237a5743c684642b26347cf82df0f3b3e91c76aff171f7d065cea305f059a502205c168682630ea4e6bd42627c237207be3d43aeba5c1b8078f3043455bdb6a2270121036240793eedd7e6e53a7c236d069e4d8558f4c6e5950114d7e3d5e1579c93fdf100000000'); + var scriptPubkey = output1.outputs[0].script; + var scriptSig = input1.inputs[0].script; + var witnesses = input1.inputs[0].getWitnesses(); + var satoshis = 1; + + interpreter = new Interpreter(); + flags = Interpreter.SCRIPT_VERIFY_P2SH; + check = interpreter.verify(scriptSig, scriptPubkey, input1, 0, flags, witnesses, satoshis); + check.should.equal(true); + + interpreter = new Interpreter(); + flags = Interpreter.SCRIPT_VERIFY_P2SH | Interpreter.SCRIPT_VERIFY_WITNESS; + check = interpreter.verify(scriptSig, scriptPubkey, input1, 0, flags, witnesses, satoshis); + check.should.equal(true); + }); + it('will verify p2sh witness pay-to-compressed pubkey (v0) part 2', function() { + var flags; + var check; + var interpreter; + var output1 = bitcore.Transaction('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff01010000000000000017a9145675f64cbe03b43fb6d9d42debd207e4be3337db8700000000'); + var input2 = bitcore.Transaction('0100000000010104410fc0d228780b20ff790212aef558df008421a110d56d9c9a9b6e5eeb1a680000000017160014b9c556bc9c34cf70d4c253ff86a9eac64e355a25ffffffff0101000000000000000002483045022100dd41426f5eb82ef2b72a0b4e5112022c80045ae4919b2fdef7f438f7ed3c59ee022043494b6f9a9f28d7e5a5c221f92d5325d941722c0ffd00f8be335592015a44d2012103587155d2618b140244799f7a408a85836403f447d51778bdb832088c4a9dd1e300000000'); + var scriptPubkey = output1.outputs[0].script; + var scriptSig = input2.inputs[0].script; + var witnesses = input2.inputs[0].getWitnesses(); + var satoshis = 1; + + interpreter = new Interpreter(); + flags = Interpreter.SCRIPT_VERIFY_P2SH; + check = interpreter.verify(scriptSig, scriptPubkey, input2, 0, flags, witnesses, satoshis); + check.should.equal(true); + + interpreter = new Interpreter(); + flags = Interpreter.SCRIPT_VERIFY_P2SH | Interpreter.SCRIPT_VERIFY_WITNESS; + check = interpreter.verify(scriptSig, scriptPubkey, input2, 0, flags, witnesses, satoshis); + check.should.equal(false); + }); + it('will verify witness 2-of-2 multisig (part 1)', function() { + var flags; + var check; + var interpreter; + var output1 = bitcore.Transaction('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0101000000000000002200204cd0c4dc1a95d8909396d0c1648793fa673518849e1b25259c581ede30e61b7900000000'); + var input1 = bitcore.Transaction('010000000001010d81757bb9f141a2d002138e86e54e8cb92b72201b38480a50377913e918612f0000000000ffffffff010100000000000000000300483045022100aa92d26d830b7529d906f7e72c1015b96b067664b68abae2d960a501e76f07780220694f4850e0003cb7e0d08bd4c67ee5fcb604c42684eb805540db5723c4383f780147522102f30bb0258f12a3bbf4fe0b5ada99974d6dbdd06876cb2687a59fa2ea7c7268aa2103d74fd4c6f08e3a4d32dde8e1404d00b2a3d323f94f5c43b4edda962b1f4cb55852ae00000000'); + var scriptPubkey = output1.outputs[0].script; + var scriptSig = input1.inputs[0].script; + var witnesses = input1.inputs[0].getWitnesses(); + var satoshis = 1; + + interpreter = new Interpreter(); + flags = 0; + check = interpreter.verify(scriptSig, scriptPubkey, input1, 0, flags, witnesses, satoshis); + check.should.equal(true); + + interpreter = new Interpreter(); + flags = Interpreter.SCRIPT_VERIFY_P2SH | Interpreter.SCRIPT_VERIFY_WITNESS; + check = interpreter.verify(scriptSig, scriptPubkey, input1, 0, flags, witnesses, satoshis); + check.should.equal(false); + }); + it('will verify witness 2-of-2 multisig (part 2)', function() { + var flags; + var check; + var interpreter; + var output2 = bitcore.Transaction('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff01010000000000000022002067b786a598572a1a0fad2f8f48e90c3f2cc89ef110f029f35323b15ba6e9b2f900000000'); + var input2 = bitcore.Transaction('01000000000101812d39aa60f01c994c43bc160c87420b6b93bf8db2fe658df45f152250fae9100000000000ffffffff010100000000000000000300483045022100ae56c6d646656366601835e6bc2d151a9974cb1b7cbdeba27cc51ef8c59d2e3f022041e95e80d3e068eb278e31b07f984800869115111c647e2ca32718d26d8e8cd401475221032ac79a7160a0af81d59ffeb914537b1d126a3629271ac1393090c6c9a94bc81e2103eb8129ad88864e7702604ae5b36bad74dbb0f5abfd8ee9ee5def3869756b6c4152ae00000000'); + var scriptPubkey = output2.outputs[0].script; + var scriptSig = input2.inputs[0].script; + var witnesses = input2.inputs[0].getWitnesses(); + var satoshis = 1; + + interpreter = new Interpreter(); + flags = 0; + check = interpreter.verify(scriptSig, scriptPubkey, input2, 0, flags, witnesses, satoshis); + check.should.equal(true); + + interpreter = new Interpreter(); + flags = Interpreter.SCRIPT_VERIFY_P2SH | Interpreter.SCRIPT_VERIFY_WITNESS; + check = interpreter.verify(scriptSig, scriptPubkey, input2, 0, flags, witnesses, satoshis); + check.should.equal(false); + }); + it('will verify witness 2-of-2 multisig (part 3)', function() { + var flags; + var check; + var interpreter; + var output1 = bitcore.Transaction('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0101000000000000002200207780f1145ef7ba4e703388c155d94bc399e24345e11c4559e683d5070feeb27400000000'); + var input1 = bitcore.Transaction('01000000000101791890e3effa9d4061a984812a90675418d0eb141655c106cce9b4bbbf9a3be00000000000ffffffff010100000000000000000400483045022100db977a31834033466eb103131b1ef9c57d6cea17f9a7eb3f3bafde1d7c1ddff502205ad84c9ca9c4139dce6e8e7850cc09a49ad57197b266814e79a78527ab4a9f950147304402205bd26da7dab9e379019ffd5e76fa77e161090bf577ed875e8e969f06cd66ba0a0220082cf7315ff7dc7aa8f6cebf7e70af1ffa45e63581c08e6fbc4e964035e6326b0147522102f86e3dc39cf9cd6c0eeb5fe25e3abe34273b8e79cc888dd5512001c7dac31b9921032e16a3c764fb6485345d91b39fb6da52c7026b8819e1e7d2f838a0df1445851a52ae00000000'); + var scriptPubkey = output1.outputs[0].script; + var scriptSig = input1.inputs[0].script; + var witnesses = input1.inputs[0].getWitnesses(); + var satoshis = 1; + + interpreter = new Interpreter(); + flags = Interpreter.SCRIPT_VERIFY_P2SH | Interpreter.SCRIPT_VERIFY_WITNESS; + check = interpreter.verify(scriptSig, scriptPubkey, input1, 0, flags, witnesses, satoshis); + check.should.equal(true); + }); + it('will verify p2sh witness 2-of-2 multisig (part 1)', function() { + var flags; + var check; + var interpreter; + var output1 = bitcore.Transaction('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff01010000000000000017a914d0e24dc9fac5cfc616b364797de40f100086e9d58700000000'); + var input1 = bitcore.Transaction('010000000001015865ee582f91c2ac646114493c3c39a3b2b08607cd96ba573f4525a01d1f85da000000002322002055423059d7eb9252d1abd6e85a4710c0bb8fabcd48cf9ddd811377557a77fc0dffffffff010100000000000000000300473044022031f9630a8ed776d6cef9ecab58cc9ee384338f4304152d93ac19482ac1ccbc030220616f194c7228484af208433b734b59ec82e21530408ed7a61e896cfefb5c4d6b014752210361424173f5b273fc134ce02a5009b07422b3f4ee63edc82cfd5bba7f72e530732102014ba09ca8cc68720bdf565f55a28b7b845be8ef6a17188b0fddcd55c16d450652ae00000000'); + var scriptPubkey = output1.outputs[0].script; + var scriptSig = input1.inputs[0].script; + var witnesses = input1.inputs[0].getWitnesses(); + var satoshis = 1; + + interpreter = new Interpreter(); + flags = 0; + check = interpreter.verify(scriptSig, scriptPubkey, input1, 0, flags, witnesses, satoshis); + check.should.equal(true); + + interpreter = new Interpreter(); + flags = Interpreter.SCRIPT_VERIFY_P2SH | Interpreter.SCRIPT_VERIFY_WITNESS; + check = interpreter.verify(scriptSig, scriptPubkey, input1, 0, flags, witnesses, satoshis); + check.should.equal(false); + }); + it('will verify p2sh witness 2-of-2 multisig (part 2)', function() { + var flags; + var check; + var interpreter; + var output2 = bitcore.Transaction('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff01010000000000000017a914294b319a1c23951902e25e0147527c8eac3009c68700000000'); + var input2 = bitcore.Transaction('01000000000101d93fa44db148929eada630dd419142935c75a72d3678291327ab35d0983b37500000000023220020786e2abd1a684f8337c637f54f6ba3da75b5d75ef96cc7e7369cc69d8ca80417ffffffff010100000000000000000300483045022100b36be4297f2e1d115aba5a5fbb19f6882c61016ba9d6fa01ebb517d14109ec6602207de237433c7534d766ec36d9bddf839b961805e336e42fae574e209b1dc8e30701475221029569b67a4c695502aa31c8a7992b975aa591f2d7de61a4def63771213792288c2103ad3b7eeedf4cba17836ff9a29044a782889cd74ca8f426e83112fa199611676652ae00000000'); + var scriptPubkey = output2.outputs[0].script; + var scriptSig = input2.inputs[0].script; + var witnesses = input2.inputs[0].getWitnesses(); + var satoshis = 1; + + interpreter = new Interpreter(); + flags = 0; + check = interpreter.verify(scriptSig, scriptPubkey, input2, 0, flags, witnesses, satoshis); + check.should.equal(true); + + interpreter = new Interpreter(); + flags = Interpreter.SCRIPT_VERIFY_P2SH | Interpreter.SCRIPT_VERIFY_WITNESS; + check = interpreter.verify(scriptSig, scriptPubkey, input2, 0, flags, witnesses, satoshis); + check.should.equal(false); + }); + it('will verify p2sh witness 2-of-2 multisig (part 3)', function() { + var flags; + var check; + var interpreter; + var output1 = bitcore.Transaction('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff01010000000000000017a9143f588990832299c654d8032bc6c5d181427a321e8700000000'); + var input1 = bitcore.Transaction('01000000000101ef6f782539d100d563d736339c4a57485b562f9705b28680b08b3efe9dd815870000000023220020a51db581b721c64132415f985ac3086bcf7817f1bbf45be984718b41f4189b39ffffffff01010000000000000000040047304402203202c4c3b40c091a051707421def9adb0d101076672ab220db36a3f87bbecad402205f976ff87af9149e83c87c94ec3b308c1abe4b8c5b3f43c842ebffc22885fc530147304402203c0a50f199774f6393e42ee29d3540cf868441b47efccb11139a357ecd45c5b702205e8442ff34f6f836cd9ad96c158504469db178d63a309d813ba68b86c7293f66014752210334f22ecf25636ba18f8c89e90d38f05036094fe0be48187fb9842374a237b1062102993d85ece51cec8c4d841fce02faa6130f57c811078c5f2a48c204caf12853b552ae00000000'); + var scriptPubkey = output1.outputs[0].script; + var scriptSig = input1.inputs[0].script; + var witnesses = input1.inputs[0].getWitnesses(); + var satoshis = 1; + + interpreter = new Interpreter(); flags = Interpreter.SCRIPT_VERIFY_P2SH | Interpreter.SCRIPT_VERIFY_WITNESS; check = interpreter.verify(scriptSig, scriptPubkey, input1, 0, flags, witnesses, satoshis); check.should.equal(true); From 0007ffb8ed05644d7b39953e6118c39ce5333a70 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Mon, 3 Oct 2016 18:34:06 -0400 Subject: [PATCH 20/23] Add unit tests for isWitnessProgram --- lib/script/script.js | 4 ++-- test/script/script.js | 29 +++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/lib/script/script.js b/lib/script/script.js index ab7291766..fa5427f08 100644 --- a/lib/script/script.js +++ b/lib/script/script.js @@ -415,10 +415,10 @@ Script.prototype.isWitnessProgram = function(values) { values = {}; } var buf = this.toBuffer(); - if (buf.length < 4 || buf.length > 34) { + if (buf.length < 4 || buf.length > 42) { return false; } - if (buf[0] >= Opcode.OP_0 && (buf[0] < 1 && buf[0] > 16)) { + if (buf[0] !== Opcode.OP_0 && !(buf[0] >= Opcode.OP_1 && buf[0] <= Opcode.OP_16)) { return false; } diff --git a/test/script/script.js b/test/script/script.js index 6f5519650..aeabbdcb6 100644 --- a/test/script/script.js +++ b/test/script/script.js @@ -427,6 +427,35 @@ describe('Script', function() { }); }); + describe('#isWitnessProgram', function() { + it('will default values to empty object', function() { + Script('OP_0 20 0x799d283e7f92af1dd242bf4eea513c6efd117de2') + .isWitnessProgram().should.equal(true); + }); + it('will return false if script is data push longer than 40 bytes', function() { + Script('OP_0 42 0xd06863c385592423903682926825c495b6cf88fd7cd6159ffd72f778ca475d3046e7b87835d3b457cd') + .isWitnessProgram().should.equal(false); + }); + it('will return false if first byte op_code is greater than OP_16', function() { + Script('OP_NOP 20 0x799d283e7f92af1dd242bf4eea513c6efd117de2') + .isWitnessProgram().should.equal(false); + }); + it('will return true with datapush of 20', function() { + var values = {}; + Script('OP_0 20 0x799d283e7f92af1dd242bf4eea513c6efd117de2') + .isWitnessProgram(values).should.equal(true); + values.version.should.equal(0); + values.program.toString('hex').should.equal('799d283e7f92af1dd242bf4eea513c6efd117de2'); + }); + it('will return true with datapush of 32', function() { + var values = {}; + Script('OP_0 32 0xc756f6d660d4aaad55534cac599a0d9bf5c7e8f70363d22926291811a168c620') + .isWitnessProgram(values).should.equal(true); + values.version.should.equal(0); + values.program.toString('hex').should.equal('c756f6d660d4aaad55534cac599a0d9bf5c7e8f70363d22926291811a168c620'); + }); + }); + describe('#isPushOnly', function() { it('should know these scripts are or aren\'t push only', function() { Script('OP_NOP 1 0x01').isPushOnly().should.equal(false); From 09346a5dd26b98045cddb686220f8fcc60a79c8e Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Mon, 3 Oct 2016 18:34:41 -0400 Subject: [PATCH 21/23] Remove currently unused function witnessSignatureOperationsCount --- lib/script/script.js | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/lib/script/script.js b/lib/script/script.js index fa5427f08..dc7fc9a48 100644 --- a/lib/script/script.js +++ b/lib/script/script.js @@ -1144,19 +1144,4 @@ Script.prototype.getSignatureOperationsCount = function(accurate) { return n; }; -Script.prototype.witnessSignatureOperationsCount = function (version, program, witness, flags) { - if (version == 0) { - if (program.length == 20) { - return 1; - } - - if (program.length == 32 && witness.size > 0) { - var subscript = new Script(witness.back); - return subscript.getSignatureOperationsCount(true); - } - } - - return 0; -}; - module.exports = Script; From 2bc76b157034ce34148792564ef9c70115f8bcfe Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Tue, 4 Oct 2016 12:13:45 -0400 Subject: [PATCH 22/23] Unit test coverage for script interpreter verifyWitnessProgram --- lib/script/interpreter.js | 25 +++++---- test/script/interpreter.js | 106 +++++++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 11 deletions(-) diff --git a/lib/script/interpreter.js b/lib/script/interpreter.js index ffcec43fc..42b1c2622 100644 --- a/lib/script/interpreter.js +++ b/lib/script/interpreter.js @@ -38,7 +38,7 @@ Interpreter.prototype.verifyWitnessProgram = function(version, program, witness, if (version === 0) { if (program.length === 32) { if (witness.length === 0) { - this.errstr = 'v0 scripthash program empty'; + this.errstr = 'SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY'; return false; } @@ -46,14 +46,14 @@ Interpreter.prototype.verifyWitnessProgram = function(version, program, witness, scriptPubKey = new Script(scriptPubKeyBuffer); var hash = Hash.sha256(scriptPubKeyBuffer); if (hash.toString('hex') !== program.toString('hex')) { - this.errstr = 'witness program mismatch'; + this.errstr = 'SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH'; return false; } stack = witness.slice(0, -1); } else if (program.length === 20) { if (witness.length !== 2) { - this.errstr = 'witness program mismatch'; + this.errstr = 'SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH'; return false; } @@ -66,11 +66,11 @@ Interpreter.prototype.verifyWitnessProgram = function(version, program, witness, stack = witness; } else { - this.errstr = 'Witness program wrong length'; + this.errstr = 'SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH'; return false; } - } else if ((flags & this.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)) { - this.errstr = 'Upgradeable witness program discouraged'; + } else if ((flags & Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)) { + this.errstr = 'SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM'; return false; } else { return true; @@ -248,17 +248,17 @@ Interpreter.prototype.verify = function(scriptSig, scriptPubkey, tx, nin, flags, return false; } if ((flags & Interpreter.SCRIPT_VERIFY_WITNESS)) { - var witnessValues = {}; - if (redeemScript.isWitnessProgram(witnessValues)) { + var p2shWitnessValues = {}; + if (redeemScript.isWitnessProgram(p2shWitnessValues)) { hadWitness = true; var redeemScriptPush = new Script(); redeemScriptPush.add(redeemScript.toBuffer()); if (scriptSig.toHex() !== redeemScriptPush.toHex()) { - this.errstr = 'Malleated scriptSig'; + this.errstr = 'SCRIPT_ERR_WITNESS_MALLEATED_P2SH'; return false; } - if (!this.verifyWitnessProgram(witnessValues.version, witnessValues.program, witness, satoshis, flags)) { + if (!this.verifyWitnessProgram(p2shWitnessValues.version, p2shWitnessValues.program, witness, satoshis, flags)) { return false; } @@ -268,7 +268,7 @@ Interpreter.prototype.verify = function(scriptSig, scriptPubkey, tx, nin, flags, if ((flags & Interpreter.SCRIPT_VERIFY_WITNESS)) { if (!hadWitness && witness.length > 0) { - this.errstr = 'Witness unexpected'; + this.errstr = 'SCRIPT_ERR_WITNESS_UNEXPECTED'; return false; } } @@ -320,6 +320,9 @@ Interpreter.LOCKTIME_THRESHOLD_BN = new BN(Interpreter.LOCKTIME_THRESHOLD); // bitcoind commit: b5d1b1092998bc95313856d535c632ea5a8f9104 Interpreter.SCRIPT_VERIFY_NONE = 0; +// Making v1-v16 witness program non-standard +Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM = (1 << 12); + // Evaluate P2SH subscripts (softfork safe, BIP16). Interpreter.SCRIPT_VERIFY_P2SH = (1 << 0); diff --git a/test/script/interpreter.js b/test/script/interpreter.js index ea7a32877..70b95196c 100644 --- a/test/script/interpreter.js +++ b/test/script/interpreter.js @@ -1,6 +1,7 @@ 'use strict'; var should = require('chai').should(); +var sinon = require('sinon'); var bitcore = require('../..'); var Interpreter = bitcore.Script.Interpreter; var Transaction = bitcore.Transaction; @@ -97,6 +98,111 @@ describe('Interpreter', function() { }); + describe('#verifyWitnessProgram', function() { + it('will return true if witness program greater than 0', function() { + var si = Interpreter(); + var version = 1; + var program = new Buffer('bcbd1db07ce89d1f4050645c26c90ce78b67eff78460002a4d5c10410958e064', 'hex'); + var witness = [new Buffer('bda0eeeb166c8bfeaee88dedc8efa82d3bea35aac5be253902f59d52908bfe25', 'hex')]; + var satoshis = 1; + var flags = 0; + si.verifyWitnessProgram(version, program, witness, satoshis, flags).should.equal(true); + }); + it('will return false with error if witness length is 0', function() { + var si = Interpreter(); + var version = 0; + var program = new Buffer('bcbd1db07ce89d1f4050645c26c90ce78b67eff78460002a4d5c10410958e064', 'hex'); + var witness = []; + var satoshis = 1; + var flags = 0; + si.verifyWitnessProgram(version, program, witness, satoshis, flags).should.equal(false); + si.errstr.should.equal('SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY'); + }); + it('will return false if program hash mismatch (version 0, 32 byte program)', function() { + var si = Interpreter(); + var version = 0; + var program = new Buffer('0000000000000000000000000000000000000000000000000000000000000000', 'hex'); + var witness = [ + new Buffer('0000000000000000000000000000000000000000000000000000000000000000', 'hex'), + new Buffer('0000000000000000000000000000000000000000000000000000000000000000', 'hex') + ]; + var satoshis = 1; + var flags = 0; + si.verifyWitnessProgram(version, program, witness, satoshis, flags).should.equal(false); + si.errstr.should.equal('SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH'); + }); + it('will return false if witness stack doesn\'t have two items (version 0, 20 byte program)', function() { + var si = Interpreter(); + var version = 0; + var program = new Buffer('b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc6', 'hex'); + var witness = [ + new Buffer('0000000000000000000000000000000000000000000000000000000000000000', 'hex'), + new Buffer('0000000000000000000000000000000000000000000000000000000000000000', 'hex'), + new Buffer('0000000000000000000000000000000000000000000000000000000000000000', 'hex') + ]; + var satoshis = 1; + var flags = 0; + si.verifyWitnessProgram(version, program, witness, satoshis, flags).should.equal(false); + si.errstr.should.equal('SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH'); + }); + it('will return false if program wrong length for version 0', function() { + var si = Interpreter(); + var version = 0; + var program = new Buffer('b8bcb07f6344b42ab04250c86a6e8b75d3', 'hex'); + var witness = [ + new Buffer('0000000000000000000000000000000000000000000000000000000000000000', 'hex') + ]; + var satoshis = 1; + var flags = 0; + si.verifyWitnessProgram(version, program, witness, satoshis, flags).should.equal(false); + si.errstr.should.equal('SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH'); + }); + it('will return false with discourage upgradable witness program', function() { + var si = Interpreter(); + var version = 1; + var program = new Buffer('b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc6', 'hex'); + var witness = [ + new Buffer('0000000000000000000000000000000000000000000000000000000000000000', 'hex'), + new Buffer('0000000000000000000000000000000000000000000000000000000000000000', 'hex') + ]; + var satoshis = 1; + var flags = Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM; + si.verifyWitnessProgram(version, program, witness, satoshis, flags).should.equal(false); + si.errstr.should.equal('SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM'); + }); + it('will return false with error if stack doesn\'t have exactly one item', function() { + var si = Interpreter(); + si.evaluate = sinon.stub().returns(true); + var version = 0; + var program = new Buffer('b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc6', 'hex'); + var witness = [ + new Buffer('0000000000000000000000000000000000000000000000000000000000000000', 'hex'), + new Buffer('0000000000000000000000000000000000000000000000000000000000000000', 'hex') + ]; + var satoshis = 1; + var flags = 0; + si.verifyWitnessProgram(version, program, witness, satoshis, flags).should.equal(false); + si.errstr.should.equal('SCRIPT_ERR_EVAL_FALSE'); + }); + it('will return false if last item in stack casts to false', function() { + var si = Interpreter(); + si.evaluate = function() { + si.stack = [new Buffer('00', 'hex')]; + return true; + }; + var version = 0; + var program = new Buffer('b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc6', 'hex'); + var witness = [ + new Buffer('0000000000000000000000000000000000000000000000000000000000000000', 'hex'), + new Buffer('0000000000000000000000000000000000000000000000000000000000000000', 'hex') + ]; + var satoshis = 1; + var flags = 0; + si.verifyWitnessProgram(version, program, witness, satoshis, flags).should.equal(false); + si.errstr.should.equal('SCRIPT_ERR_EVAL_FALSE_IN_STACK'); + }); + }); + describe('#verify', function() { it('should verify these trivial scripts', function() { From 559520abc38f1d43500aaf67b8f71a44a3269e83 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Thu, 6 Oct 2016 16:31:40 -0400 Subject: [PATCH 23/23] Bitcoin core tests for witness uncompressed public keys --- test/transaction/transaction.js | 85 +++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/test/transaction/transaction.js b/test/transaction/transaction.js index 1d4fb52ae..c13df11ce 100644 --- a/test/transaction/transaction.js +++ b/test/transaction/transaction.js @@ -1533,6 +1533,91 @@ describe('Transaction', function() { check = interpreter.verify(scriptSig, scriptPubkey, input1, 0, flags, witnesses, satoshis); check.should.equal(true); }); + it('will verify witness pay-to-uncompressed-pubkey (v1) part 1', function() { + var flags; + var check; + var interpreter; + var output1 = bitcore.Transaction('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff01010000000000000016001449ca7f5980799857e4cc236a288b95dc7e647de200000000'); + var input1 = bitcore.Transaction('010000000001014cc98b43a012d8cb56cee7e2011e041c23a622a69a8b97d6f53144e5eb319d1c0000000000ffffffff010100000000000000000248304502210085fb71eecc4b65fd31102bc93f46ec564fce6d22f749ad2d9b4adf4d9477c52602204c4fb00a48bafb4f1c0d7a397d3e0ae12bb8ae394d8b5632e894eafccabf4b160141047dc77183e8fef00c7839a272c4dc2c9b25fb109c0eebe74b27fa98cfd6fa83c76c44a145827bf880162ff7ae48574b5d42595601eee5b8733f1507f028ba401000000000'); + var input2 = bitcore.Transaction('0100000000010170ccaf8888099cee3cb869e768f6f24a85838a936cfda787186b179392144cbc0000000000ffffffff010100000000000000000247304402206667f8681ecdc66ad160ff4916c6f3e2946a1eda9e031535475f834c11d5e07c022064360fce49477fa0898b3928eb4503ca71043c67df9229266316961a6bbcc2ef014104a8288183cc741b814a286414ee5fe81ab189ecae5bb1c42794b270c33ac9702ab279fd97a5ed87437659b45197bbd3a87a449fa5b244a6941303683aa68bd11e00000000'); + var scriptPubkey = output1.outputs[0].script; + var scriptSig = input1.inputs[0].script; + var witnesses = input1.inputs[0].getWitnesses(); + var satoshis = 1; + + interpreter = new Interpreter(); + flags = Interpreter.SCRIPT_VERIFY_P2SH; + check = interpreter.verify(scriptSig, scriptPubkey, input1, 0, flags, witnesses, satoshis); + check.should.equal(true); + + interpreter = new Interpreter(); + flags = Interpreter.SCRIPT_VERIFY_P2SH | Interpreter.SCRIPT_VERIFY_WITNESS; + check = interpreter.verify(scriptSig, scriptPubkey, input1, 0, flags, witnesses, satoshis); + check.should.equal(true); + }); + it('will verify witness pay-to-uncompressed-pubkey (v1) part 2', function() { + var flags; + var check; + var interpreter; + var output1 = bitcore.Transaction('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff01010000000000000016001449ca7f5980799857e4cc236a288b95dc7e647de200000000'); + var input2 = bitcore.Transaction('0100000000010170ccaf8888099cee3cb869e768f6f24a85838a936cfda787186b179392144cbc0000000000ffffffff010100000000000000000247304402206667f8681ecdc66ad160ff4916c6f3e2946a1eda9e031535475f834c11d5e07c022064360fce49477fa0898b3928eb4503ca71043c67df9229266316961a6bbcc2ef014104a8288183cc741b814a286414ee5fe81ab189ecae5bb1c42794b270c33ac9702ab279fd97a5ed87437659b45197bbd3a87a449fa5b244a6941303683aa68bd11e00000000'); + var scriptPubkey = output1.outputs[0].script; + var scriptSig = input2.inputs[0].script; + var witnesses = input2.inputs[0].getWitnesses(); + var satoshis = 1; + + interpreter = new Interpreter(); + flags = Interpreter.SCRIPT_VERIFY_P2SH; + check = interpreter.verify(scriptSig, scriptPubkey, input2, 0, flags, witnesses, satoshis); + check.should.equal(true); + + interpreter = new Interpreter(); + flags = Interpreter.SCRIPT_VERIFY_P2SH | Interpreter.SCRIPT_VERIFY_WITNESS;; + check = interpreter.verify(scriptSig, scriptPubkey, input2, 0, flags, witnesses, satoshis); + check.should.equal(false); + }); + it('will verify p2sh witness pay-to-uncompressed-pubkey (v1) part 1', function() { + var flags; + var check; + var interpreter; + var output1 = bitcore.Transaction('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff01010000000000000017a9147b615f35c476c8f3c555b4d52e54760b2873742f8700000000'); + var input1 = bitcore.Transaction('01000000000101160aa337bd325875674904f80d706b4d02cec9888eb2dbae788e18ed01f7712d0000000017160014eff6eebd0dcd3923ca3ab3ea57071fa82ea1faa5ffffffff010100000000000000000247304402205c87348896d3a9de62b1a646c29c4728bec62e384fa16167e302357883c04134022024a98e0fbfde9c24528fbe8f36e05a19a6f37dea16822b80259fcfc8ab2358fb0141048b4e234c057e32d2304697b4d2273679417355bb6bf2d946add731de9719d6801892b6154291ce2cf45c106a6d754c76f81e4316187aa54938af224d9eddb36400000000'); + var scriptPubkey = output1.outputs[0].script; + var scriptSig = input1.inputs[0].script; + var witnesses = input1.inputs[0].getWitnesses(); + var satoshis = 1; + + interpreter = new Interpreter(); + flags = Interpreter.SCRIPT_VERIFY_P2SH; + check = interpreter.verify(scriptSig, scriptPubkey, input1, 0, flags, witnesses, satoshis); + check.should.equal(true); + + interpreter = new Interpreter(); + flags = Interpreter.SCRIPT_VERIFY_P2SH | Interpreter.SCRIPT_VERIFY_WITNESS;; + check = interpreter.verify(scriptSig, scriptPubkey, input1, 0, flags, witnesses, satoshis); + check.should.equal(true); + }); + it('will verify p2sh witness pay-to-uncompressed-pubkey (v1) part 2', function() { + var flags; + var check; + var interpreter; + var output1 = bitcore.Transaction('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff01010000000000000017a9147b615f35c476c8f3c555b4d52e54760b2873742f8700000000'); + var input2 = bitcore.Transaction('01000000000101eefb67109c118e958d81f3f98638d48bc6c14eae97cedfce7c397eabb92b4e320000000017160014eff6eebd0dcd3923ca3ab3ea57071fa82ea1faa5ffffffff010100000000000000000247304402200ed4fa4bc8fbae2d1e88bbe8691b21233c23770e5eebf9767853de8579f5790a022015cb3f3dc88720199ee1ed5a9f4cf3186a29a0c361512f03b648c9998b3da7b4014104dfaee8168fe5d1ead2e0c8bb12e2d3ba500ade4f6c4983f3dbe5b70ffeaca1551d43c6c962b69fb8d2f4c02faaf1d4571aae7bbd209df5f3b8cd153e60e1627300000000'); + var scriptPubkey = output1.outputs[0].script; + var scriptSig = input2.inputs[0].script; + var witnesses = input2.inputs[0].getWitnesses(); + var satoshis = 1; + + interpreter = new Interpreter(); + flags = Interpreter.SCRIPT_VERIFY_P2SH; + check = interpreter.verify(scriptSig, scriptPubkey, input2, 0, flags, witnesses, satoshis); + check.should.equal(true); + + interpreter = new Interpreter(); + flags = Interpreter.SCRIPT_VERIFY_P2SH | Interpreter.SCRIPT_VERIFY_WITNESS;; + check = interpreter.verify(scriptSig, scriptPubkey, input2, 0, flags, witnesses, satoshis); + check.should.equal(false); + }); }); }); describe('signing', function() {