From 185ebe8ebb917665e2788d3ba1c0ea58f47dabdb Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Sat, 12 Apr 2014 18:41:34 -0300 Subject: [PATCH 1/6] add from-to Obj in Transaction Builder --- TransactionBuilder.js | 76 +++++++++++++++++++++++++++++---- test/test.TransactionBuilder.js | 72 ++++++++++++++++++++++++++++++- 2 files changed, 138 insertions(+), 10 deletions(-) diff --git a/TransactionBuilder.js b/TransactionBuilder.js index ebb3a6f5a..27c0884e8 100644 --- a/TransactionBuilder.js +++ b/TransactionBuilder.js @@ -506,12 +506,12 @@ TransactionBuilder.prototype._signPubKeyHash = function(walletKeyMap, input, txS }; // FOR TESTING -// var _dumpChunks = function (scriptSig, label) { -// console.log('## DUMP: ' + label + ' ##'); -// for(var i=0; i Date: Sat, 12 Apr 2014 20:58:22 -0300 Subject: [PATCH 2/6] remove txobj from this! #merge WIP --- TransactionBuilder.js | 114 ++++++++++++++++++++++++-------- test/test.TransactionBuilder.js | 18 ++--- 2 files changed, 96 insertions(+), 36 deletions(-) diff --git a/TransactionBuilder.js b/TransactionBuilder.js index 27c0884e8..28a13c32e 100644 --- a/TransactionBuilder.js +++ b/TransactionBuilder.js @@ -94,12 +94,7 @@ var FEE_PER_1000B_SAT = parseInt(0.0001 * util.COIN); function TransactionBuilder(opts) { opts = opts || {}; - this.txobj = {}; - this.txobj.version = 1; - this.txobj.lock_time = opts.lockTime || 0; - this.txobj.ins = []; - this.txobj.outs = []; - + this.lockTime = opts.lockTime || 0; this.spendUnconfirmed = opts.spendUnconfirmed || false; if (opts.fee || opts.feeSat) { @@ -265,12 +260,12 @@ TransactionBuilder.prototype._selectUnspent = function(neededAmountSat) { return this; }; -TransactionBuilder.prototype._setInputs = function() { +TransactionBuilder.prototype._setInputs = function(txobj) { var ins = this.selectedUtxos; var l = ins.length; var valueInSat = bignum(0); - this.txobj.ins=[]; + txobj.ins=[]; for (var i = 0; i < l; i++) { valueInSat = valueInSat.add(util.parseValue(ins[i].amount)); @@ -286,7 +281,7 @@ TransactionBuilder.prototype._setInputs = function() { voutBuf.writeUInt32LE(vout, 0); txin.o = Buffer.concat([hashReversed, voutBuf]); - this.txobj.ins.push(txin); + txobj.ins.push(txin); } this.valueInSat = valueInSat; return this; @@ -309,7 +304,7 @@ TransactionBuilder.prototype._setFee = function(feeSat) { return this; }; -TransactionBuilder.prototype._setRemainder = function(remainderIndex) { +TransactionBuilder.prototype._setRemainder = function(txobj, remainderIndex) { if ( typeof this.valueInSat === 'undefined' || typeof this.valueOutSat === 'undefined') @@ -317,12 +312,12 @@ TransactionBuilder.prototype._setRemainder = function(remainderIndex) { // add remainder (without modifying outs[]) var remainderSat = this.valueInSat.sub(this.valueOutSat).sub(this.feeSat); - var l =this.txobj.outs.length; + var l =txobj.outs.length; this.remainderSat = bignum(0); //remove old remainder? if (l > remainderIndex) { - this.txobj.outs.pop(); + txobj.outs.pop(); } if (remainderSat.cmp(0) > 0) { @@ -333,18 +328,17 @@ TransactionBuilder.prototype._setRemainder = function(remainderIndex) { v: value, s: script.getBuffer(), }; - this.txobj.outs.push(txout); + txobj.outs.push(txout); this.remainderSat = remainderSat; } return this; }; -TransactionBuilder.prototype._setFeeAndRemainder = function() { +TransactionBuilder.prototype._setFeeAndRemainder = function(txobj) { //starting size estimation - var size = 500, maxSizeK, remainderIndex = this.txobj.outs.length; - + var size = 500, maxSizeK, remainderIndex = txobj.outs.length; do { // based on https://en.bitcoin.it/wiki/Transaction_fees maxSizeK = parseInt(size / 1000) + 1; @@ -355,12 +349,12 @@ TransactionBuilder.prototype._setFeeAndRemainder = function() { var neededAmountSat = this.valueOutSat.add(feeSat); this._selectUnspent(neededAmountSat) - ._setInputs() + ._setInputs(txobj) ._setFee(feeSat) - ._setRemainder(remainderIndex); + ._setRemainder(txobj, remainderIndex); - size = new Transaction(this.txobj).getSize(); + size = new Transaction(txobj).getSize(); } while (size > (maxSizeK + 1) * 1000); return this; }; @@ -368,9 +362,13 @@ TransactionBuilder.prototype._setFeeAndRemainder = function() { TransactionBuilder.prototype.setOutputs = function(outs) { var valueOutSat = bignum(0); - this.txobj.outs = []; - var l =outs.length; + var txobj = {}; + txobj.version = 1; + txobj.lock_time = this.lockTime || 0; + txobj.ins = []; + txobj.outs = []; + var l =outs.length; for (var i = 0; i < l; i++) { var amountSat = outs[i].amountSat || util.parseValue(outs[i].amount); var value = util.bigIntToValue(amountSat); @@ -379,7 +377,7 @@ TransactionBuilder.prototype.setOutputs = function(outs) { v: value, s: script.getBuffer(), }; - this.txobj.outs.push(txout); + txobj.outs.push(txout); var sat = outs[i].amountSat || util.parseValue(outs[i].amount); valueOutSat = valueOutSat.add(sat); @@ -387,9 +385,9 @@ TransactionBuilder.prototype.setOutputs = function(outs) { this.valueOutSat = valueOutSat; - this._setFeeAndRemainder(); + this._setFeeAndRemainder(txobj); - this.tx = new Transaction(this.txobj); + this.tx = new Transaction(txobj); return this; }; @@ -736,7 +734,6 @@ TransactionBuilder.prototype.toObj = function() { spendUnconfirmed : this.spendUnconfirmed, inputMap : this.inputMap, - txobj : this.txobj, }; if (this.tx) { data.tx =this.tx.serialize().toString('hex'); @@ -762,18 +759,79 @@ TransactionBuilder.fromObj = function(data) { b.spendUnconfirmed = data.spendUnconfirmed; b.inputMap = data.inputMap; - b.txobj = data.txobj; if (data.tx) { + // Tx may have signatures, that are not on txobj var t = new Transaction(); t.parse(new Buffer(data.tx,'hex')); b.tx = t; } - else if (b.txobj) - b.tx = new Transaction(b.txobj); return b; }; +TransactionBuilder.merge = function(b) { + // Builder should have the same params + ['valueInSat', 'valueOutSat', 'feeSat', 'remainderSat', 'signhash', 'spendUnconfirmed'] + .forEach(function (k) { + if (this[k] !== b[k]) + throw new Error('mismatch at TransactionBuilder match: ' + k); + }); + + if (this.hashToScriptMap) { + var err = 0; + if(! b.hashToScriptMap) err=1; + Object.keys(this.hashToScriptMap).forEach(function(k) { + if (!b.hashToScriptMap[k]) err=1; + if (this.hashToScriptMap[k] !== b.hashToScriptMap[k]) err=1; + }); + if (err) + throw new Error('mismatch at TransactionBuilder hashToScriptMap'); + } + + + var err = 0, i=0;; + this.selectedUtxos.forEach(function(u) { + if (!err) { + var v=b.selectedUtxos[i++]; + if (!v) err=1; + // confirmations could differ + ['address', 'hash', 'scriptPubKey', 'vout', 'amount'].forEach(function(k) { + if (u[k] !== v[k]) + err=k; + }); + } + }); + if (err) + throw new Error('mismatch at TransactionBuilder selectedUtxos #' + i-1+ ' Key:' + err); + + + err = 0; i=0;; + this.inputMap.forEach(function(u) { + if (!err) { + var v=b.inputMap[i++]; + if (!v) err=1; + // confirmations could differ + ['address', 'scriptType', 'scriptPubKey', 'i'].forEach(function(k) { + if (u[k] !== v[k]) + err=k; + }); + } + }); + if (err) + throw new Error('mismatch at TransactionBuilder inputMap #' + i-1 + ' Key:' + err); + + + // Does this tX have any signature already? + if (this.signaturesAdded) { + } + if (this.tx) { + } + // to be really merged + // signaturesAdded, inputsSigned +}; + + + module.exports = require('soop')(TransactionBuilder); diff --git a/test/test.TransactionBuilder.js b/test/test.TransactionBuilder.js index 08374cfcf..ba3aa0de3 100644 --- a/test/test.TransactionBuilder.js +++ b/test/test.TransactionBuilder.js @@ -32,9 +32,9 @@ describe('TransactionBuilder', function() { it('should be able to create instance with params', function() { var t = new TransactionBuilder({spendUnconfirmed: true, lockTime: 10}); should.exist(t); - should.exist(t.txobj.version); + should.exist(t.lockTime); t.spendUnconfirmed.should.equal(true); - t.txobj.lock_time.should.equal(10); + t.lockTime.should.equal(10); }); @@ -84,22 +84,24 @@ describe('TransactionBuilder', function() { it('#_setInputs sets inputs', function() { + var txobj={}; var b = getBuilder() .setUnspent(testdata.dataUnspent) ._selectUnspent(0.1 * util.COIN) - ._setInputs(); + ._setInputs(txobj); - should.exist(b.txobj.ins[0].s); - should.exist(b.txobj.ins[0].q); - should.exist(b.txobj.ins[0].o); + should.exist(txobj.ins[0].s); + should.exist(txobj.ins[0].q); + should.exist(txobj.ins[0].o); }); it('#_setInputMap set inputMap', function() { + var txobj={}; var b = getBuilder() .setUnspent(testdata.dataUnspent) ._selectUnspent(0.1 * util.COIN) - ._setInputs() - ._setInputMap(); + ._setInputs(txobj) + ._setInputMap(txobj); should.exist(b.inputMap); b.inputMap.length.should.equal(2); From 57a5ca5fa7ebf7db1e4d79f195c864f9425d9309 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Sun, 13 Apr 2014 02:01:29 -0300 Subject: [PATCH 3/6] remove txobj from this! #merge WIP --- TransactionBuilder.js | 102 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 96 insertions(+), 6 deletions(-) diff --git a/TransactionBuilder.js b/TransactionBuilder.js index 28a13c32e..14e84e511 100644 --- a/TransactionBuilder.js +++ b/TransactionBuilder.js @@ -769,7 +769,8 @@ TransactionBuilder.fromObj = function(data) { return b; }; -TransactionBuilder.merge = function(b) { + +TransactionBuilder._checkMergeability = function(b) { // Builder should have the same params ['valueInSat', 'valueOutSat', 'feeSat', 'remainderSat', 'signhash', 'spendUnconfirmed'] .forEach(function (k) { @@ -820,14 +821,103 @@ TransactionBuilder.merge = function(b) { if (err) throw new Error('mismatch at TransactionBuilder inputMap #' + i-1 + ' Key:' + err); +}; + + +// this assumes that the same signature can not be v0 / v1 (which shouldnt be!) +TransactionBuilder.prototype._mergeInputSig = function(s0buf, s1buf) { + if (buffertools.compare(s0buf,s1buf) === 0) { + console.log('BUFFERS .s MATCH'); //TODO + return s0buf; + } + // Is multisig? + var s0 = new Script(s0buf); + var s1 = new Script(s1buf); + var l0 = s0.chunks.length; + var l1 = s1.chunks.length; + var s0map = {}; + + if (l0 && l1 && l0 !== l1) + throw new Error('TX sig types mismatch in merge'); + + if (l0) { + // Look for differences. + for (var i=0; i Date: Sun, 13 Apr 2014 02:21:44 -0300 Subject: [PATCH 4/6] merge working! --- TransactionBuilder.js | 89 ++++++++++++++++++--------------- test/test.TransactionBuilder.js | 35 +++++++++++++ 2 files changed, 83 insertions(+), 41 deletions(-) diff --git a/TransactionBuilder.js b/TransactionBuilder.js index 14e84e511..0c75239a9 100644 --- a/TransactionBuilder.js +++ b/TransactionBuilder.js @@ -770,20 +770,25 @@ TransactionBuilder.fromObj = function(data) { }; -TransactionBuilder._checkMergeability = function(b) { +TransactionBuilder.prototype._checkMergeability = function(b) { + var self=this; + // Builder should have the same params ['valueInSat', 'valueOutSat', 'feeSat', 'remainderSat', 'signhash', 'spendUnconfirmed'] .forEach(function (k) { - if (this[k] !== b[k]) - throw new Error('mismatch at TransactionBuilder match: ' + k); + + if (self[k].toString() !== b[k].toString()) { + throw new Error('mismatch at TransactionBuilder match: ' + + k + ': ' + self[k] + ' vs. ' + b[k]); + } }); - if (this.hashToScriptMap) { + if (self.hashToScriptMap) { var err = 0; if(! b.hashToScriptMap) err=1; - Object.keys(this.hashToScriptMap).forEach(function(k) { + Object.keys(self.hashToScriptMap).forEach(function(k) { if (!b.hashToScriptMap[k]) err=1; - if (this.hashToScriptMap[k] !== b.hashToScriptMap[k]) err=1; + if (self.hashToScriptMap[k] !== b.hashToScriptMap[k]) err=1; }); if (err) throw new Error('mismatch at TransactionBuilder hashToScriptMap'); @@ -791,7 +796,7 @@ TransactionBuilder._checkMergeability = function(b) { var err = 0, i=0;; - this.selectedUtxos.forEach(function(u) { + self.selectedUtxos.forEach(function(u) { if (!err) { var v=b.selectedUtxos[i++]; if (!v) err=1; @@ -807,13 +812,13 @@ TransactionBuilder._checkMergeability = function(b) { err = 0; i=0;; - this.inputMap.forEach(function(u) { + self.inputMap.forEach(function(u) { if (!err) { var v=b.inputMap[i++]; if (!v) err=1; // confirmations could differ ['address', 'scriptType', 'scriptPubKey', 'i'].forEach(function(k) { - if (u[k] !== v[k]) + if (u[k].toString() !== v[k].toString()) err=k; }); } @@ -840,45 +845,44 @@ TransactionBuilder.prototype._mergeInputSig = function(s0buf, s1buf) { if (l0 && l1 && l0 !== l1) throw new Error('TX sig types mismatch in merge'); - if (l0) { - // Look for differences. - for (var i=0; i Date: Sun, 13 Apr 2014 09:49:26 -0300 Subject: [PATCH 5/6] add merge options --- TransactionBuilder.js | 31 ++++++++------ test/test.TransactionBuilder.js | 75 +++++++++++++++++++++++++++++++-- 2 files changed, 89 insertions(+), 17 deletions(-) diff --git a/TransactionBuilder.js b/TransactionBuilder.js index 0c75239a9..9e741ab86 100644 --- a/TransactionBuilder.js +++ b/TransactionBuilder.js @@ -830,9 +830,9 @@ TransactionBuilder.prototype._checkMergeability = function(b) { // this assumes that the same signature can not be v0 / v1 (which shouldnt be!) -TransactionBuilder.prototype._mergeInputSig = function(s0buf, s1buf) { +TransactionBuilder.prototype._mergeInputSig = function(s0buf, s1buf, ignoreConflictingSignatures) { if (buffertools.compare(s0buf,s1buf) === 0) { - console.log('BUFFERS .s MATCH'); //TODO + //console.log('BUFFERS .s MATCH'); //TODO return s0buf; } // Is multisig? @@ -874,19 +874,24 @@ TransactionBuilder.prototype._mergeInputSig = function(s0buf, s1buf) { } } - if (emptySlots.length this fails: no way to check signatures, since PRIV Keys are not stored + b = getBuilder3([{ + address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', + amount: 16 + }]) + .sign(testdata.dataUnspentSign.keyStrings); + // merge simple + b2 = getBuilder3([{ + address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', + amount: 16 + }]) + .sign(testdata.dataUnspentSign.keyStrings); + (function() {b2.merge(b);}).should.throw(); + b2.merge(b, true); + }); + + it('#merge p2sh/steps', function() { var b = getP2shBuilder(1); var k1 = testdata.dataUnspentSign.keyStringsP2sh.slice(0,1); var k2 = testdata.dataUnspentSign.keyStringsP2sh.slice(1,2); @@ -791,8 +861,5 @@ describe('TransactionBuilder', function() { b2.signaturesAdded.should.equal(3); tx = b2.build(); tx.isComplete().should.equal(true); - - - }); }); From bec26d80d6c5caa96205f305f43f8a30a2b563bf Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Mon, 14 Apr 2014 11:58:24 -0300 Subject: [PATCH 6/6] fix TB test --- test/test.TransactionBuilder.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/test/test.TransactionBuilder.js b/test/test.TransactionBuilder.js index 362e3c050..d4671ec55 100644 --- a/test/test.TransactionBuilder.js +++ b/test/test.TransactionBuilder.js @@ -6,17 +6,11 @@ var bitcore = bitcore || require('../bitcore'); var should = chai.should(); -var Transaction = bitcore.Transaction; var TransactionBuilder = bitcore.TransactionBuilder; -var In; -var Out; -var Script = bitcore.Script; var WalletKey = bitcore.WalletKey; var util = bitcore.util; var networks = bitcore.networks; -var buffertools = require('buffertools'); var testdata = testdata || require('./testdata'); -var nutil = require('util'); describe('TransactionBuilder', function() { it('should initialze the main object', function() {