diff --git a/gulpfile.js b/gulpfile.js index 0618136..ed80a78 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -32,19 +32,20 @@ 'use strict'; var gulp = require('gulp'); + +var bump = require('gulp-bump'); var coveralls = require('gulp-coveralls'); +var git = require('gulp-git'); +var gutil = require('gulp-util'); +var jsdoc2md = require('jsdoc-to-markdown'); var jshint = require('gulp-jshint'); +var mfs = require('more-fs'); var mocha = require('gulp-mocha'); +var rename = require('gulp-rename'); var runSequence = require('run-sequence'); var shell = require('gulp-shell'); var through = require('through2'); -var gutil = require('gulp-util'); -var jsdoc2md = require('jsdoc-to-markdown'); -var mfs = require('more-fs'); var uglify = require('gulp-uglify'); -var rename = require('gulp-rename'); -var bump = require('gulp-bump'); -var git = require('gulp-git'); var files = ['lib/**/*.js']; diff --git a/lib/script/script.js b/lib/script/script.js index 7785918..9eab646 100644 --- a/lib/script/script.js +++ b/lib/script/script.js @@ -715,6 +715,13 @@ Script.prototype.toAddress = function(network) { throw new Error('The script type needs to be PayToPublicKeyHash or PayToScriptHash'); }; +/** + * @return {Script} + */ +Script.prototype.toScriptHashOut = function() { + return Script.buildScriptHashOut(this); +}; + /** * Analagous to bitcoind's FindAndDelete. Find and delete equivalent chunks, * typically used with push data chunks. Note that this will find and delete diff --git a/lib/transaction/input/multisigscripthash.js b/lib/transaction/input/multisigscripthash.js index 1a36a14..e8a6286 100644 --- a/lib/transaction/input/multisigscripthash.js +++ b/lib/transaction/input/multisigscripthash.js @@ -9,12 +9,13 @@ 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'); /** * @constructor */ -function MultiSigScriptHashInput(input, pubkeys, threshold) { +function MultiSigScriptHashInput(input, pubkeys, threshold, signatures) { Input.apply(this, arguments); var self = this; this.publicKeys = _.sortBy(pubkeys, function(publicKey) { return publicKey.toString('hex'); }); @@ -27,10 +28,50 @@ function MultiSigScriptHashInput(input, pubkeys, threshold) { }); this.threshold = threshold; // Empty array of signatures - this.signatures = new Array(this.publicKeys.length); + this.signatures = signatures ? this._deserializeSignatures(signatures) : new Array(this.publicKeys.length); } inherits(MultiSigScriptHashInput, Input); +MultiSigScriptHashInput.prototype.toObject = function() { + var obj = Input.prototype.toObject.apply(this, arguments); + obj.threshold = this.threshold; + obj.publicKeys = _.map(this.publicKeys, function(publicKey) { return publicKey.toString(); }); + obj.signatures = this._serializeSignatures(); + return obj; +}; + +MultiSigScriptHashInput.prototype._deserializeSignatures = function(signatures) { + return _.map(signatures, function(signature) { + if (!signature) { + return undefined; + } + return { + publicKey: new PublicKey(signature.publicKey), + prevTxId: signature.txId, + outputIndex: signature.outputIndex, + inputIndex: signature.inputIndex, + signature: Signature.fromString(signature.signature), + sigtype: signature.sigtype + }; + }); +}; + +MultiSigScriptHashInput.prototype._serializeSignatures = function() { + return _.map(this.signatures, function(signature) { + if (!signature) { + return undefined; + } + return { + publicKey: signature.publicKey.toString(), + prevTxId: signature.txId, + outputIndex: signature.outputIndex, + inputIndex: signature.inputIndex, + signature: signature.signature.toString(), + sigtype: signature.sigtype + }; + }); +}; + MultiSigScriptHashInput.prototype.getSignatures = function(transaction, privateKey, index, sigtype) { $.checkState(this.output instanceof Output); sigtype = sigtype || Signature.SIGHASH_ALL; diff --git a/lib/transaction/transaction.js b/lib/transaction/transaction.js index e074b7b..accd86e 100644 --- a/lib/transaction/transaction.js +++ b/lib/transaction/transaction.js @@ -249,6 +249,18 @@ Transaction.prototype.toObject = function toObject() { Transaction.prototype.fromObject = function(transaction) { var self = this; _.each(transaction.inputs, function(input) { + if (input.output.script) { + input.output.script = new Script(input.output.script); + if (input.output.script.isPublicKeyHashOut()) { + self.addInput(new Input.PublicKeyHash(input)); + return; + } else if (input.output.script.isScriptHashOut() && input.publicKeys && input.threshold) { + self.addInput(new Input.MultiSigScriptHash( + input, input.publicKeys, input.threshold, input.signatures + )); + return; + } + } self.addInput(new Input(input)); }); _.each(transaction.outputs, function(output) { diff --git a/lib/transaction/unspentoutput.js b/lib/transaction/unspentoutput.js index f8aee61..f02dc79 100644 --- a/lib/transaction/unspentoutput.js +++ b/lib/transaction/unspentoutput.js @@ -98,7 +98,7 @@ UnspentOutput.prototype.toJSON = function() { */ UnspentOutput.prototype.toObject = function() { return { - address: this.address.toString(), + address: this.address ? this.address.toString() : undefined, txid: this.txId, vout: this.outputIndex, scriptPubKey: this.script.toBuffer().toString('hex'), diff --git a/test/transaction/transaction.js b/test/transaction/transaction.js index 110fbdc..0892a2f 100644 --- a/test/transaction/transaction.js +++ b/test/transaction/transaction.js @@ -222,6 +222,13 @@ describe('Transaction', function() { var deserialized = new Transaction(serialized); expect(deserialized._change.toString()).to.equal(changeAddress); }); + it('can avoid checked serialize', function() { + var transaction = new Transaction() + .from(simpleUtxoWith1BTC) + .to(fromAddress, 1); + expect(function() { return transaction.serialize(); }).to.throw(); + expect(function() { return transaction.serialize(true); }).to.not.throw(); + }); }); describe('checked serialize', function() { @@ -265,6 +272,30 @@ describe('Transaction', function() { expect(JSON.parse(transaction.toJSON()).change).to.equal(changeAddress.toString()); }); }); + + describe('serialization of inputs', function() { + it('can serialize and deserialize a P2PKH input', function() { + var transaction = new Transaction() + .from(simpleUtxoWith1BTC); + var deserialized = new Transaction(transaction.toObject()); + expect(deserialized.inputs[0] instanceof Transaction.Input.PublicKeyHash).to.equal(true); + }); + it('can serialize and deserialize a P2SH input', function() { + var private1 = '6ce7e97e317d2af16c33db0b9270ec047a91bff3eff8558afb5014afb2bb5976'; + var private2 = 'c9b26b0f771a0d2dad88a44de90f05f416b3b385ff1d989343005546a0032890'; + var public1 = new PrivateKey(private1).publicKey; + var public2 = new PrivateKey(private2).publicKey; + var transaction = new Transaction() + .from({ + txId: '0000', // Not relevant + outputIndex: 0, + script: Script.buildMultisigOut([public1, public2], 2).toScriptHashOut(), + satoshis: 10000 + }, [public1, public2], 2); + var deserialized = new Transaction(transaction.toObject()); + expect(deserialized.inputs[0] instanceof Transaction.Input.MultiSigScriptHash).to.equal(true); + }); + }); }); var tx_empty_hex = '01000000000000000000';