diff --git a/lib/script/script.js b/lib/script/script.js index 6daeb24e5d9..f7b4165b9bf 100644 --- a/lib/script/script.js +++ b/lib/script/script.js @@ -327,6 +327,11 @@ Script.prototype.isPublicKeyHashIn = function() { return false; }; +Script.prototype.getPublicKey = function() { + $.checkState(this.isPublicKeyOut(), 'Can\'t retreive PublicKey from a non-PK output'); + return this.chunks[0].buf; +}; + Script.prototype.getPublicKeyHash = function() { $.checkState(this.isPublicKeyHashOut(), 'Can\'t retrieve PublicKeyHash from a non-PKH output'); return this.chunks[2].buf; @@ -361,12 +366,17 @@ Script.prototype.isPublicKeyOut = function() { * @returns {boolean} if this is a pay to public key input script */ Script.prototype.isPublicKeyIn = function() { - return this.chunks.length === 1 && - BufferUtil.isBuffer(this.chunks[0].buf) && - this.chunks[0].buf.length === 0x47; + if (this.chunks.length === 1) { + var signatureBuf = this.chunks[0].buf; + if (signatureBuf && + signatureBuf.length && + signatureBuf[0] === 0x30) { + return true; + } + } + return false; }; - /** * @returns {boolean} if this is a p2sh output script */ @@ -775,6 +785,26 @@ Script.buildScriptHashOut = function(script) { return s; }; +/** + * Builds a scriptSig (a script for an input) that signs a public key output script. + * + * @param {Signature|Buffer} signature - a Signature object, or the signature in DER cannonical encoding + * @param {number=} sigtype - the type of the signature (defaults to SIGHASH_ALL) + */ +Script.buildPublicKeyIn = function(signature, sigtype) { + $.checkArgument(signature instanceof Signature || BufferUtil.isBuffer(signature)); + $.checkArgument(_.isUndefined(sigtype) || _.isNumber(sigtype)); + if (signature instanceof Signature) { + signature = signature.toBuffer(); + } + var script = new Script(); + script.add(BufferUtil.concat([ + signature, + BufferUtil.integerAsSingleByteBuffer(sigtype || Signature.SIGHASH_ALL) + ])); + return script; +}; + /** * Builds a scriptSig (a script for an input) that signs a public key hash * output script. diff --git a/lib/transaction/input/index.js b/lib/transaction/input/index.js index 9103d3250ee..1744ea7b7c6 100644 --- a/lib/transaction/input/index.js +++ b/lib/transaction/input/index.js @@ -1,4 +1,5 @@ module.exports = require('./input'); +module.exports.PublicKey = require('./publickey'); module.exports.PublicKeyHash = require('./publickeyhash'); module.exports.MultiSigScriptHash = require('./multisigscripthash.js'); diff --git a/lib/transaction/input/publickey.js b/lib/transaction/input/publickey.js new file mode 100644 index 00000000000..3cb2fe58146 --- /dev/null +++ b/lib/transaction/input/publickey.js @@ -0,0 +1,89 @@ +'use strict'; + +var inherits = require('inherits'); + +var $ = require('../../util/preconditions'); +var BufferUtil = require('../../util/buffer'); + +var Input = require('./input'); +var Output = require('../output'); +var Sighash = require('../sighash'); +var Script = require('../../script'); +var Signature = require('../../crypto/signature'); +var TransactionSignature = require('../signature'); + +/** + * Represents a special kind of input of PayToPublicKey kind. + * @constructor + */ +function PublicKeyInput() { + Input.apply(this, arguments); +} +inherits(PublicKeyInput, Input); + +/** + * @param {Transaction} transaction - the transaction to be signed + * @param {PrivateKey} privateKey - the private key with which to sign the transaction + * @param {number} index - the index of the input in the transaction input vector + * @param {number=} sigtype - the type of signature, defaults to Signature.SIGHASH_ALL + * @return {Array} of objects that can be + */ +PublicKeyInput.prototype.getSignatures = function(transaction, privateKey, index, sigtype) { + $.checkState(this.output instanceof Output); + sigtype = sigtype || Signature.SIGHASH_ALL; + var publicKey = privateKey.toPublicKey(); + if (publicKey.toString() === this.output.script.getPublicKey().toString('hex')) { + return [new TransactionSignature({ + publicKey: publicKey, + prevTxId: this.prevTxId, + outputIndex: this.outputIndex, + inputIndex: index, + signature: Sighash.sign(transaction, privateKey, sigtype, index, this.output.script), + sigtype: sigtype + })]; + } + return []; +}; + +/** + * Add the provided signature + * + * @param {Object} signature + * @param {PublicKey} signature.publicKey + * @param {Signature} signature.signature + * @param {number=} signature.sigtype + * @return {PublicKeyInput} this, for chaining + */ +PublicKeyInput.prototype.addSignature = function(transaction, signature) { + $.checkState(this.isValidSignature(transaction, signature), 'Signature is invalid'); + this.setScript(Script.buildPublicKeyIn( + signature.signature.toDER(), + signature.sigtype + )); + return this; +}; + +/** + * Clear the input's signature + * @return {PublicKeyHashInput} this, for chaining + */ +PublicKeyInput.prototype.clearSignatures = function() { + this.setScript(Script.empty()); + return this; +}; + +/** + * Query whether the input is signed + * @return {boolean} + */ +PublicKeyInput.prototype.isFullySigned = function() { + return this.script.isPublicKeyIn(); +}; + +PublicKeyInput.SCRIPT_MAX_SIZE = 73; // sigsize (1 + 72) + +PublicKeyInput.prototype._estimateSize = function() { + return PublicKeyInput.SCRIPT_MAX_SIZE; +}; + +module.exports = PublicKeyInput; diff --git a/lib/transaction/transaction.js b/lib/transaction/transaction.js index b3773e12631..f1aefe9f727 100644 --- a/lib/transaction/transaction.js +++ b/lib/transaction/transaction.js @@ -17,6 +17,7 @@ var Address = require('../address'); var UnspentOutput = require('./unspentoutput'); var Input = require('./input'); var PublicKeyHashInput = Input.PublicKeyHash; +var PublicKeyInput = Input.PublicKey; var MultiSigScriptHashInput = Input.MultiSigScriptHash; var Output = require('./output'); var Script = require('../script'); @@ -538,6 +539,8 @@ Transaction.prototype._fromNonP2SH = function(utxo) { utxo = new UnspentOutput(utxo); if (utxo.script.isPublicKeyHashOut()) { clazz = PublicKeyHashInput; + } else if (utxo.script.isPublicKeyOut()) { + clazz = PublicKeyInput; } else { clazz = Input; } diff --git a/test/script/script.js b/test/script/script.js index bbbf94532cc..b436bbf1572 100644 --- a/test/script/script.js +++ b/test/script/script.js @@ -249,6 +249,16 @@ describe('Script', function() { }); }); + describe('#isPublicKeyIn', function() { + it('correctly identify scriptSig as a public key in', function() { + // from txid: 5c85ed63469aa9971b5d01063dbb8bcdafd412b2f51a3d24abf2e310c028bbf8 + // and input index: 5 + var scriptBuffer = new Buffer('483045022050eb59c79435c051f45003d9f82865c8e4df5699d7722e77113ef8cadbd92109022100d4ab233e070070eb8e0e62e3d2d2eb9474a5bf135c9eda32755acb0875a6c20601', 'hex'); + var script = bitcore.Script.fromBuffer(scriptBuffer); + script.isPublicKeyIn().should.equal(true); + }); + }); + describe('#isPublicKeyHashIn', function() { it('should identify this known pubkeyhashin (uncompressed pubkey version)', function() { diff --git a/test/transaction/input/publickey.js b/test/transaction/input/publickey.js new file mode 100644 index 00000000000..5d6b366c150 --- /dev/null +++ b/test/transaction/input/publickey.js @@ -0,0 +1,71 @@ +'use strict'; + +var should = require('chai').should(); +var bitcore = require('../../..'); +var Transaction = bitcore.Transaction; +var PrivateKey = bitcore.PrivateKey; + +describe('PublicKeyInput', function() { + + var utxo = { + txid: '7f3b688cb224ed83e12d9454145c26ac913687086a0a62f2ae0bc10934a4030f', + vout: 0, + address: 'n4McBrSkw42eYGX5YMACGpkGUJKL3jVSbo', + scriptPubKey: '2103c9594cb2ebfebcb0cfd29eacd40ba012606a197beef76f0269ed8c101e56ceddac', + amount: 50, + confirmations: 104, + spendable: true + }; + var privateKey = PrivateKey.fromWIF('cQ7tSSQDEwaxg9usnnP1Aztqvm9nCQVfNWz9kU2rdocDjknF2vd6'); + var address = privateKey.toAddress(); + utxo.address.should.equal(address.toString()); + + var destKey = new PrivateKey(); + + it('will correctly sign a publickey out transaction', function() { + var tx = new Transaction(); + tx.from(utxo); + tx.to(destKey.toAddress(), 10000); + tx.sign(privateKey); + tx.inputs[0].script.toBuffer().length.should.be.above(0); + }); + + it('count can count missing signatures', function() { + var tx = new Transaction(); + tx.from(utxo); + tx.to(destKey.toAddress(), 10000); + var input = tx.inputs[0]; + input.isFullySigned().should.equal(false); + tx.sign(privateKey); + input.isFullySigned().should.equal(true); + }); + + it('it\'s size can be estimated', function() { + var tx = new Transaction(); + tx.from(utxo); + tx.to(destKey.toAddress(), 10000); + var input = tx.inputs[0]; + input._estimateSize().should.equal(73); + }); + + it('it\'s signature can be removed', function() { + var tx = new Transaction(); + tx.from(utxo); + tx.to(destKey.toAddress(), 10000); + var input = tx.inputs[0]; + tx.sign(privateKey); + input.isFullySigned().should.equal(true); + input.clearSignatures(); + input.isFullySigned().should.equal(false); + }); + + it('returns an empty array if private key mismatches', function() { + var tx = new Transaction(); + tx.from(utxo); + tx.to(destKey.toAddress(), 10000); + var input = tx.inputs[0]; + var signatures = input.getSignatures(tx, new PrivateKey(), 0); + signatures.length.should.equal(0); + }); + +}); diff --git a/test/transaction/input/publickeyhash.js b/test/transaction/input/publickeyhash.js index f8f19aed4a0..d0ae6233113 100644 --- a/test/transaction/input/publickeyhash.js +++ b/test/transaction/input/publickeyhash.js @@ -58,8 +58,7 @@ describe('PublicKeyHashInput', function() { .from(output) .to(address, 1000000); var input = transaction.inputs[0]; - - input.getSignatures(transaction, new PrivateKey(), 0); - input.isFullySigned().should.equal(false); + var signatures = input.getSignatures(transaction, new PrivateKey(), 0); + signatures.length.should.equal(0); }); });