diff --git a/Transaction.js b/Transaction.js index c90ce4c..13ab35f 100644 --- a/Transaction.js +++ b/Transaction.js @@ -48,7 +48,7 @@ TransactionIn.prototype.isCoinBase = function isCoinBase() { if (!this.o) return false; //The new Buffer is for Firefox compatibility - return buffertools.compare(new Buffer(this.o), COINBASE_OP) === 0; + return buffertools.compare(new Buffer(this.o), COINBASE_OP) === 0; }; TransactionIn.prototype.serialize = function serialize() { @@ -287,20 +287,149 @@ var OP_CODESEPARATOR = 171; var SIGHASH_ALL = 1; var SIGHASH_NONE = 2; var SIGHASH_SINGLE = 3; -var SIGHASH_ANYONECANPAY = 80; +var SIGHASH_ANYONECANPAY = 0x80; Transaction.SIGHASH_ALL = SIGHASH_ALL; Transaction.SIGHASH_NONE = SIGHASH_NONE; Transaction.SIGHASH_SINGLE = SIGHASH_SINGLE; Transaction.SIGHASH_ANYONECANPAY = SIGHASH_ANYONECANPAY; +var oneBuffer = function() { + // bug present in bitcoind which must be also present in bitcore + // see https://bitcointalk.org/index.php?topic=260595 + var ret = new Buffer(1); + ret.writeUInt8(1, 0); + return ret; // return 1 bug + +}; + +var TransactionSignatureSerializer = function(txTo, scriptCode, nIn, nHashType) { + this.txTo = txTo; + this.scriptCode = scriptCode; + this.nIn = nIn; + console.log('nHashType '+nHashType); + this.anyoneCanPay = !!(nHashType & SIGHASH_ANYONECANPAY); + console.log('anyoneCanPay ='+this.anyoneCanPay); + var hashTypeMode = nHashType & 0x1f; + console.log('hashTypeMode ='+hashTypeMode); + this.hashSingle = hashTypeMode === SIGHASH_SINGLE; + this.hashNone = hashTypeMode === SIGHASH_NONE; + this.bytes = new Put(); +}; + +// serialize an output of txTo +TransactionSignatureSerializer.prototype.serializeOutput = function(nOutput) { + if (this.hashSingle && nOutput != this.nIn) { + // Do not lock-in the txout payee at other indices as txin + // ::Serialize(s, CTxOut(), nType, nVersion); + this.bytes.put(util.INT64_MAX); + this.bytes.varint(0); + } else { + //::Serialize(s, txTo.vout[nOutput], nType, nVersion); + var out = this.txTo.outs[nOutput]; + this.bytes.put(out.v); + this.bytes.varint(out.s.length); + this.bytes.put(out.s); + } + +}; + +// serialize the script +TransactionSignatureSerializer.prototype.serializeScriptCode = function() { + this.scriptCode.findAndDelete(OP_CODESEPARATOR); + this.bytes.varint(this.scriptCode.buffer.length); + this.bytes.put(this.scriptCode.buffer); +}; + +// serialize an input of txTo +TransactionSignatureSerializer.prototype.serializeInput = function(nInput) { + // In case of SIGHASH_ANYONECANPAY, only the input being signed is serialized + if (this.anyoneCanPay) nInput = this.nIn; + + // Serialize the prevout + this.bytes.put(this.txTo.ins[nInput].o); + + // Serialize the script + if (nInput !== this.nIn) { + // Blank out other inputs' signatures + this.bytes.varint(0); + } else { + this.serializeScriptCode(); + } + // Serialize the nSequence + if (nInput !== this.nIn && (this.hashSingle || this.hashNone)) { + // let the others update at will + this.bytes.word32le(0); + } else { + this.bytes.word32le(this.txTo.ins[nInput].q); + } + +}; + + +// serialize txTo for signature +TransactionSignatureSerializer.prototype.serialize = function() { + // serialize nVersion + this.bytes.word32le(this.txTo.version); + // serialize vin + var nInputs = this.anyoneCanPay ? 1 : this.txTo.ins.length; + console.log('nInputs '+nInputs); + this.bytes.varint(nInputs); + for (var nInput = 0; nInput < nInputs; nInput++) { + this.serializeInput(nInput); + } + // serialize vout + var nOutputs = this.hashNone ? 0 : (this.hashSingle ? this.nIn + 1 : this.txTo.outs.length); + this.bytes.varint(nOutputs); + for (var nOutput = 0; nOutput < nOutputs; nOutput++) { + this.serializeOutput(nOutput); + } + + // serialize nLockTime + this.bytes.word32le(this.txTo.lock_time); +}; + +TransactionSignatureSerializer.prototype.buffer = function() { + this.serialize(); + return this.bytes.buffer(); +}; + Transaction.prototype.hashForSignature = function hashForSignature(script, inIndex, hashType) { + if (+inIndex !== inIndex || inIndex < 0 || inIndex >= this.ins.length) { - throw new Error("Input index '" + inIndex + "' invalid or out of bounds " + - "(" + this.ins.length + " inputs)"); + return oneBuffer(); } + // Check for invalid use of SIGHASH_SINGLE + var hashTypeMode = hashType & 0x1f; + if (hashTypeMode === SIGHASH_SINGLE) { + if (inIndex >= this.outs.length) { + return oneBuffer(); + } + } + + // Wrapper to serialize only the necessary parts of the transaction being signed + var serializer = new TransactionSignatureSerializer(this, script, inIndex, hashType); + // Serialize + var buffer = serializer.buffer(); + // Append hashType + var hashBuf = new Put().word32le(hashType).buffer(); + buffer = Buffer.concat([buffer, hashBuf]); + var bth = buffertools.toHex(buffer); + //console.log('tx sig b ' + bth); + var expected = '907c2bc503ade11cc3b04eb2918b6f547b0630ab569273824748c87ea14b0696526c66ba740200000000fd1f9bdd4ef073c7afc4ae00da8a66f429c917a0081ad1e1dabce28d373eab81d8628de80200000000ad042b5f25efb33beec9f3364e8a9139e8439d9d7e26529c3c30b6c3fd89f8684cfd68ea0200000000599ac2fe02a526ed040000000008535300516352515164370e010000000003006300ab2ec2291fe51c6f'; + //console.log('expected '+expected); + for (var i=0; i= this.outs.length) { - // bug present in bitcoind which must be also present in bitcore - // see https://bitcointalk.org/index.php?topic=260595 - // Transaction.hashForSignature(): SIGHASH_SINGLE - // no corresponding txout found - out of bounds - var ret = new Buffer(1); - ret.writeUInt8(1, 0); - return ret; // return 1 bug + return oneBuffer(); } outsLen = inIndex + 1; } else { diff --git a/test/test.util.js b/test/test.util.js index 205cf22..b8c422d 100644 --- a/test/test.util.js +++ b/test/test.util.js @@ -55,6 +55,15 @@ describe('util', function() { }); }); + describe('#twoSha256', function() { + var data = new Buffer('907c2bc503ade11cc3b04eb2918b6f547b0630ab569273824748c87ea14b0696526c66ba740200000000fd1f9bdd4ef073c7afc4ae00da8a66f429c917a0081ad1e1dabce28d373eab81d8628de80200000000ad042b5f25efb33beec9f3364e8a9139e8439d9d7e26529c3c30b6c3fd89f8684cfd68ea0200000000599ac2fe02a526ed040000000008535300516352515164370e010000000003006300ab2ec2291fe51c6f', 'hex'); + it('should work for ' + data, function() { + var twoSha256 = buffertools.toHex(buffertools.reverse(coinUtil.twoSha256(data))); + var expected = '31af167a6cf3f9d5f6875caa4d31704ceb0eba078d132b78dab52c3b8997317e'; + twoSha256.should.equal(expected); + }); + }); + describe('#sha256ripe160', function() { var pk = '03d95e184cce34c3cfa58e9a277a09a7c5ed1b2a8134ea1e52887bc66fa3f47071' it('should work for ' + pk, function() { diff --git a/util/util.js b/util/util.js index dc7972b..eec49b2 100644 --- a/util/util.js +++ b/util/util.js @@ -454,6 +454,7 @@ var reverseBytes32 = exports.reverseBytes32 = function(data) { return put.buffer(); }; + var getVarIntSize = exports.getVarIntSize = function getVarIntSize(i) { if (i < 253) {