From dc6ff322902d1cc6643deecfd9654daaedecf6fa Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Tue, 6 Jan 2015 09:26:14 -0300 Subject: [PATCH 1/4] Make serialization roundtrip recover info about inputs --- lib/transaction/input/multisigscripthash.js | 45 ++++++++++++++++++++- lib/transaction/transaction.js | 12 ++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/lib/transaction/input/multisigscripthash.js b/lib/transaction/input/multisigscripthash.js index 1a36a14..d56e1ad 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 signature; + } + 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 signature; + } + 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 38568fe..f7c73fb 100644 --- a/lib/transaction/transaction.js +++ b/lib/transaction/transaction.js @@ -234,6 +234,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) { From ff82ccec7148eee9b6b8bf451bad91f9c5478810 Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Thu, 8 Jan 2015 16:18:20 -0300 Subject: [PATCH 2/4] Return undefined instead of a null signature --- lib/transaction/input/multisigscripthash.js | 4 ++-- lib/transaction/unspentoutput.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/transaction/input/multisigscripthash.js b/lib/transaction/input/multisigscripthash.js index d56e1ad..e8a6286 100644 --- a/lib/transaction/input/multisigscripthash.js +++ b/lib/transaction/input/multisigscripthash.js @@ -43,7 +43,7 @@ MultiSigScriptHashInput.prototype.toObject = function() { MultiSigScriptHashInput.prototype._deserializeSignatures = function(signatures) { return _.map(signatures, function(signature) { if (!signature) { - return signature; + return undefined; } return { publicKey: new PublicKey(signature.publicKey), @@ -59,7 +59,7 @@ MultiSigScriptHashInput.prototype._deserializeSignatures = function(signatures) MultiSigScriptHashInput.prototype._serializeSignatures = function() { return _.map(this.signatures, function(signature) { if (!signature) { - return signature; + return undefined; } return { publicKey: signature.publicKey.toString(), 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'), From d99d1c9cc240acb3a6c5be1750225d90ee1b2e2b Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Thu, 8 Jan 2015 17:41:15 -0300 Subject: [PATCH 3/4] Add tests for serialization roundtrip of inputs --- .jshintrc | 2 +- test/transaction/transaction.js | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/.jshintrc b/.jshintrc index 1d70b69..40fd8c6 100644 --- a/.jshintrc +++ b/.jshintrc @@ -28,7 +28,7 @@ "maxcomplexity": 6, // Cyclomatic complexity (http://en.wikipedia.org/wiki/Cyclomatic_complexity) "maxdepth": 4, // Maximum depth of nested control structures "maxlen": 120, // Maximum number of cols in a line - "multistr": true // Allow use of multiline EOL escaping + "multistr": true, // Allow use of multiline EOL escaping "predef": [ // Extra globals. "after", diff --git a/test/transaction/transaction.js b/test/transaction/transaction.js index 9a4ad0e..00b4f57 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() { @@ -255,6 +262,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: private1, + outputIndex: 0, + script: Script.buildScriptHashOut(Script.buildMultisigOut([public1, public2], 2)), + 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'; From 26e688ae0637ee1b41cabf6866d5df412ed6cb3e Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Fri, 9 Jan 2015 16:09:36 -0300 Subject: [PATCH 4/4] Add .toScriptHashOut() to scripts - Also sort gulpfile dependencies --- gulpfile.js | 13 +++++++------ lib/script/script.js | 7 +++++++ test/transaction/transaction.js | 4 ++-- 3 files changed, 16 insertions(+), 8 deletions(-) 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/test/transaction/transaction.js b/test/transaction/transaction.js index 00b4f57..e5f2f4d 100644 --- a/test/transaction/transaction.js +++ b/test/transaction/transaction.js @@ -277,9 +277,9 @@ describe('Transaction', function() { var public2 = new PrivateKey(private2).publicKey; var transaction = new Transaction() .from({ - txId: private1, + txId: '0000', // Not relevant outputIndex: 0, - script: Script.buildScriptHashOut(Script.buildMultisigOut([public1, public2], 2)), + script: Script.buildMultisigOut([public1, public2], 2).toScriptHashOut(), satoshis: 10000 }, [public1, public2], 2); var deserialized = new Transaction(transaction.toObject());