diff --git a/lib/address.js b/lib/address.js index 2fd487e9d..bb552a605 100644 --- a/lib/address.js +++ b/lib/address.js @@ -259,11 +259,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); }; /** @@ -336,7 +341,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/script/interpreter.js b/lib/script/interpreter.js index 31e132f71..42b1c2622 100644 --- a/lib/script/interpreter.js +++ b/lib/script/interpreter.js @@ -31,6 +31,80 @@ 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 = 'SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY'; + return false; + } + + var scriptPubKeyBuffer = witness[witness.length - 1]; + scriptPubKey = new Script(scriptPubKeyBuffer); + var hash = Hash.sha256(scriptPubKeyBuffer); + if (hash.toString('hex') !== program.toString('hex')) { + 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 = 'SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH'; + return false; + } + + 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 = 'SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH'; + return false; + } + } else if ((flags & Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)) { + this.errstr = 'SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM'; + return false; + } else { + return true; + } + + this.initialize(); + + 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 +115,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 +132,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 +189,22 @@ Interpreter.prototype.verify = function(scriptSig, scriptPubkey, tx, nin, flags) return false; } + var hadWitness = false; + + if ((flags & Interpreter.SCRIPT_VERIFY_WITNESS)) { + var witnessValues = {}; + if (scriptPubkey.isWitnessProgram(witnessValues)) { + hadWitness = true; + if (scriptSig.toBuffer().length !== 0) { + return false; + } + + if (!this.verifyWitnessProgram(witnessValues.version, witnessValues.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 +246,31 @@ 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 & Interpreter.SCRIPT_VERIFY_WITNESS)) { + var p2shWitnessValues = {}; + if (redeemScript.isWitnessProgram(p2shWitnessValues)) { + hadWitness = true; + var redeemScriptPush = new Script(); + redeemScriptPush.add(redeemScript.toBuffer()); + if (scriptSig.toHex() !== redeemScriptPush.toHex()) { + this.errstr = 'SCRIPT_ERR_WITNESS_MALLEATED_P2SH'; + return false; + } + + if (!this.verifyWitnessProgram(p2shWitnessValues.version, p2shWitnessValues.program, witness, satoshis, flags)) { + return false; + } + + stack = [stack[0]]; + } + } + + if ((flags & Interpreter.SCRIPT_VERIFY_WITNESS)) { + if (!hadWitness && witness.length > 0) { + this.errstr = 'SCRIPT_ERR_WITNESS_UNEXPECTED'; + return false; + } } } @@ -158,6 +283,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 +300,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; @@ -191,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); @@ -231,6 +363,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 +1245,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, this.satoshis); } catch (e) { //invalid sig or pubkey fSuccess = false; @@ -1200,7 +1334,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, this.satoshis); } catch (e) { //invalid sig or pubkey fOk = false; diff --git a/lib/script/script.js b/lib/script/script.js index e48d7163f..0ab7f630f 100644 --- a/lib/script/script.js +++ b/lib/script/script.js @@ -404,6 +404,49 @@ 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); +}; + +/** + * @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(values) { + if (!values) { + values = {}; + } + var buf = this.toBuffer(); + if (buf.length < 4 || buf.length > 42) { + return false; + } + if (buf[0] !== Opcode.OP_0 && !(buf[0] >= Opcode.OP_1 && buf[0] <= Opcode.OP_16)) { + return false; + } + + if (buf.length === buf[1] + 2) { + values.version = buf[0]; + values.program = buf.slice(2, buf.length); + return true; + } + + return false; +}; + /** * @returns {boolean} if this is a p2sh input script * Note that these are frequently indistinguishable from pubkeyhashin @@ -689,6 +732,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++) { @@ -733,6 +785,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/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/input/input.js b/lib/transaction/input/input.js index 9d86e6656..1c27a2e08 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; @@ -153,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'); }; @@ -169,6 +177,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..0007a0097 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,36 @@ var $ = require('../../util/preconditions'); var Script = require('../../script'); var Signature = require('../../crypto/signature'); var Sighash = require('../sighash'); -var PublicKey = require('../../publickey'); +var SighashWitness = require('../sighashwitness'); +var BufferWriter = require('../../encoding/bufferwriter'); 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)'); + 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'); + } + this.publicKeyIndex = {}; _.each(this.publicKeys, function(publicKey, index) { self.publicKeyIndex[publicKey.toString()] = index; @@ -62,6 +77,31 @@ MultiSigScriptHashInput.prototype._serializeSignatures = function() { }); }; +MultiSigScriptHashInput.prototype.getScriptCode = function() { + var writer = new BufferWriter(); + if (!this.redeemScript.hasCodeseparators()) { + var redeemScriptBuffer = this.redeemScript.toBuffer(); + writer.writeVarintNum(redeemScriptBuffer.length); + writer.write(redeemScriptBuffer); + } else { + throw new Error('@TODO'); + } + return writer.toBuffer(); +}; + +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; @@ -70,12 +110,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 })); } @@ -94,12 +142,25 @@ MultiSigScriptHashInput.prototype.addSignature = function(transaction, signature }; MultiSigScriptHashInput.prototype._updateScript = function() { - this.setScript(Script.buildP2SHMultisigIn( - this.publicKeys, - this.threshold, - this._createSignatures(), - { cachedMultisig: this.redeemScript } - )); + if (this.nestedWitness) { + 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; }; @@ -142,15 +203,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 new file mode 100644 index 000000000..7fe04a986 --- /dev/null +++ b/lib/transaction/sighashwitness.js @@ -0,0 +1,149 @@ +'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, 0); + 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, 0); + 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, scriptCode, satoshisBuffer) { + var hashbuf = sighash(transaction, sighashType, inputIndex, scriptCode, satoshisBuffer); + var sig = ECDSA.sign(hashbuf, privateKey).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, scriptCode, satoshisBuffer) { + $.checkArgument(!_.isUndefined(transaction)); + $.checkArgument(!_.isUndefined(signature) && !_.isUndefined(signature.nhashtype)); + var hashbuf = sighash(transaction, signature.nhashtype, inputIndex, scriptCode, satoshisBuffer); + return ECDSA.verify(hashbuf, signature, publicKey); +} + +/** + * @namespace Signing + */ +module.exports = { + sighash: sighash, + sign: sign, + verify: verify +}; diff --git a/lib/transaction/transaction.js b/lib/transaction/transaction.js index 947af4336..0ffb4a963 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'); @@ -57,7 +58,6 @@ function Transaction(serialized) { this._newTransaction(); } } - var CURRENT_VERSION = 1; var DEFAULT_NLOCKTIME = 0; var MAX_BLOCK_SIZE = 1000000; @@ -105,6 +105,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); @@ -126,7 +136,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)); }; /** @@ -273,21 +291,51 @@ Transaction.prototype.inspect = function() { return ''; }; -Transaction.prototype.toBuffer = function() { +Transaction.prototype.toBuffer = function(noWitness) { var writer = new BufferWriter(); - return this.toBufferWriter(writer).toBuffer(); + return this.toBufferWriter(writer, noWitness).toBuffer(); }; -Transaction.prototype.toBufferWriter = function(writer) { +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); + + 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 (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; }; @@ -299,22 +347,46 @@ 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 + var hasWitnesses = false; + if (sizeTxIns === 0 && reader.buf[reader.pos] !== 0) { + reader.pos += 1; + hasWitnesses = 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 (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.toObject = Transaction.prototype.toJSON = function toObject() { var inputs = []; this.inputs.forEach(function(input) { @@ -532,8 +604,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) { @@ -549,7 +622,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); } @@ -577,7 +650,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; @@ -597,7 +670,7 @@ Transaction.prototype._fromMultisigUtxo = function(utxo, pubkeys, threshold) { prevTxId: utxo.txId, outputIndex: utxo.outputIndex, script: Script.empty() - }, pubkeys, threshold)); + }, pubkeys, threshold, false, nestedWitness)); }; /** @@ -1121,7 +1194,36 @@ 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, satoshis) { + + if (_.isUndefined(sigversion)) { + sigversion = 0; + } + + if (sigversion === 1) { + var subscriptBuffer = subscript.toBuffer(); + var scriptCodeWriter = new BufferWriter(); + scriptCodeWriter.writeVarintNum(subscriptBuffer.length); + scriptCodeWriter.write(subscriptBuffer); + + 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/address.js b/test/address.js index f4632c713..feb5cab8e 100644 --- a/test/address.js +++ b/test/address.js @@ -551,6 +551,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/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/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() { diff --git a/test/script/script.js b/test/script/script.js index 1e8172fab..e1ff03f2c 100644 --- a/test/script/script.js +++ b/test/script/script.js @@ -442,6 +442,60 @@ 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('#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); @@ -697,6 +751,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/input/multisigscripthash.js b/test/transaction/input/multisigscripthash.js index 4856913fd..b3bcd8324 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('695221025c95ec627038e85b5688a9b3d84d28c5ebe66e8c8d697d498e20fe96e3b1ab1d2102cdddfc974d41a62f1f80081deee70592feb7d6e6cf6739d6592edbe7946720e72103c95924e02c240b5545089c69c6432447412b58be43fd671918bd184a5009834353ae'); + }); + 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/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'); + }); + +}); diff --git a/test/transaction/transaction.js b/test/transaction/transaction.js index 82a6eb27f..b553d36fe 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; @@ -1235,6 +1236,430 @@ 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('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, inputDummy, outputCount, witness, + witnessItems, locktime]); + var tx = bitcore.Transaction().fromBuffer(txBuffer); + tx.hasWitnesses().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'); + }); + 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 signedamount = 1; + var input = new Transaction.Input.MultiSigScriptHash({ + output: new Output({ + script: destScript, + satoshis: signedamount + }), + 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 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); + + 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) part 1', function() { + var check; + var flags; + var 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; + + 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); + }); + 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() { + 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 + ], 1, 'testnet', true); + var utxo = { + address: address.toString(), + 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], 1, true) + .to([{address: 'n3LsXgyStG2CkS2CnWZtDqxTfCnXB8PvD9', satoshis: 50000}]) + .fee(150000) + .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'); + }); + }); + }); + });