From 2af6ab76508e25ced857f2c91d1732ef47557bfb Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Mon, 31 Mar 2014 14:41:27 -0300 Subject: [PATCH 1/5] TX_MULTISIG support --- Transaction.js | 23 ++- TransactionBuilder.js | 239 +++++++++++++++++++++++++++----- examples/CreateScript.js | 2 +- test/data/unspent.json | 4 +- test/data/unspentSign.json | 37 +++++ test/test.TransactionBuilder.js | 104 +++++++++++++- 6 files changed, 364 insertions(+), 45 deletions(-) diff --git a/Transaction.js b/Transaction.js index 8d5b642..1f7634c 100644 --- a/Transaction.js +++ b/Transaction.js @@ -553,15 +553,28 @@ Transaction.prototype.getSize = function getHash() { }; Transaction.prototype.isComplete = function() { - var l = this.ins.length; - var ret = true; + var l = this.ins.length; + for (var i = 0; i < l; i++) { - if (buffertools.compare(this.ins[i].s, util.EMPTY_BUFFER) === 0) { - ret = false; - break; + var script = new Script(this.ins[i].s); + // Multisig? + if (!Buffer.isBuffer(script.chunks[0]) && script.chunks[0] ===0) { + for (var i = 1; i < script.chunks.length; i++) { + if (buffertools.compare(script.chunks[i], util.EMPTY_BUFFER) === 0){ + ret = false; + break; + } + } + } + else { + if (buffertools.compare(this.ins[i].s, util.EMPTY_BUFFER) === 0) { + ret = false; + break; + } } }; + return ret; }; diff --git a/TransactionBuilder.js b/TransactionBuilder.js index 083decb..d1b9ed6 100644 --- a/TransactionBuilder.js +++ b/TransactionBuilder.js @@ -89,7 +89,7 @@ var Transaction = imports.Transaction || require('./Transaction'); var FEE_PER_1000B_SAT = parseInt(0.0001 * util.COIN); function TransactionBuilder(opts) { - var opts = opts || {}; + opts = opts || {}; this.txobj = {}; this.txobj.version = 1; this.txobj.lock_time = opts.lockTime || 0; @@ -144,18 +144,29 @@ TransactionBuilder.prototype._setInputMap = function() { var l = this.selectedUtxos.length; for (var i = 0; i < l; i++) { - var s = this.selectedUtxos[i]; + var utxo = this.selectedUtxos[i]; + + var scriptBuf = new Buffer(utxo.scriptPubKey, 'hex'); + var scriptPubKey = new Script(scriptBuf); + var scriptType = scriptPubKey.classify(); + + if (scriptType === Script.TX_UNKNOWN) + throw new Error('unkown output type at:' + i + + ' Type:' + scriptPubKey.getRawOutType()); inputMap.push({ - address: s.address, - scriptPubKey: s.scriptPubKey + address: utxo.address, //TODO que pasa en multisig normal? + scriptPubKeyHex: utxo.scriptPubKey, + scriptPubKey: scriptPubKey, + scriptType: scriptType, + i: i, }); } this.inputMap = inputMap; return this; }; -TransactionBuilder.prototype.getSelectedUnspent = function(neededAmountSat) { +TransactionBuilder.prototype.getSelectedUnspent = function() { return this.selectedUtxos; }; @@ -360,14 +371,6 @@ TransactionBuilder._mapKeys = function(keys) { return walletKeyMap; }; -TransactionBuilder._checkSupportedScriptType = function (s) { - if (s.classify() !== Script.TX_PUBKEYHASH) { - throw new Error('scriptSig type:' + s.getRawOutType() + - ' not supported yet'); - } -}; - - TransactionBuilder._signHashAndVerify = function(wk, txSigHash) { var triesLeft = 10, sigRaw; @@ -387,44 +390,208 @@ TransactionBuilder.prototype._checkTx = function() { throw new Error('tx is not defined'); }; +TransactionBuilder.prototype._signPubKey = function(walletKeyMap, input, txSigHash) { + if (this.tx.ins[input.i].s.length > 0) return {}; + + var wk = walletKeyMap[input.address]; + if (!wk) return; + + var sigRaw = TransactionBuilder._signHashAndVerify(wk, txSigHash); + var sigType = new Buffer(1); + sigType[0] = this.signhash; + var sig = Buffer.concat([sigRaw, sigType]); + + var scriptSig = new Script(); + scriptSig.chunks.push(sig); + scriptSig.updateBuffer(); + return {isFullySigned: true, script: scriptSig.getBuffer()}; +}; + +TransactionBuilder.prototype._signPubKeyHash = function(walletKeyMap, input, txSigHash) { + + if (this.tx.ins[input.i].s.length > 0) return {}; + + var wk = walletKeyMap[input.address]; + if (!wk) return; + + var sigRaw = TransactionBuilder._signHashAndVerify(wk, txSigHash); + var sigType = new Buffer(1); + sigType[0] = this.signhash; + var sig = Buffer.concat([sigRaw, sigType]); + + var scriptSig = new Script(); + scriptSig.chunks.push(sig); + scriptSig.chunks.push(wk.privKey.public); + scriptSig.updateBuffer(); + return {isFullySigned: true, script: scriptSig.getBuffer()}; +}; + +// FOR TESTING +/* + var _dumpChunks = function (scriptSig, label) { + console.log('## DUMP: ' + label + ' ##'); + for(var i=0; i script] +TransactionBuilder.prototype.setHashToScriptMap = function(hashToScriptMap) { + this.hashToScriptMap= hashToScriptMap; +}; + + TransactionBuilder.prototype.isFullySigned = function() { return this.inputsSigned === this.tx.ins.length; }; diff --git a/examples/CreateScript.js b/examples/CreateScript.js index f2ac9ae..3587b33 100644 --- a/examples/CreateScript.js +++ b/examples/CreateScript.js @@ -9,7 +9,7 @@ var run = function() { var buffertools = bitcore.buffertools; var Address = bitcore.Address; var util = bitcore.util; - var opts = {network: networks.livenet}; + var opts = {network: networks.testnet}; var p = console.log; diff --git a/test/data/unspent.json b/test/data/unspent.json index 9c818a8..15072b3 100644 --- a/test/data/unspent.json +++ b/test/data/unspent.json @@ -10,7 +10,7 @@ { "address": "mqSjTad2TKbPcKQ3Jq4kgCkKatyN44UMgZ", "txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc2", - "scriptPubKey": "76a9146ce4e1163eb18939b1440c42844d5f0261c0338288ad", + "scriptPubKey": "76a9146ce4e1163eb18939b1440c42844d5f0261c0338288ac", "vout": 0, "confirmations": 1, "amount": 0.1 @@ -18,7 +18,7 @@ { "address": "mqSjTad2TKbPcKQ3Jq4kgCkKatyN44UMgZ", "txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc3", - "scriptPubKey": "76a9146ce4e1163eb18939b1440c42844d5f0261c0338288ae", + "scriptPubKey": "76a9146ce4e1163eb18939b1440c42844d5f0261c0338288ac", "vout": 3, "confirmations": 0, "amount": 1 diff --git a/test/data/unspentSign.json b/test/data/unspentSign.json index 5826f4c..1121108 100644 --- a/test/data/unspentSign.json +++ b/test/data/unspentSign.json @@ -29,6 +29,43 @@ "cSq7yo4fvsbMyWVN945VUGUWMaSazZPWqBVJZyoGsHmNq6W4HVBV", "cPa87VgwZfowGZYaEenoQeJgRfKW6PhZ1R65EHTkN1K19cSvc92G", "cPQ9DSbBRLva9av5nqeF5AGrh3dsdW8p2E5jS4P8bDWZAoQTeeKB" + ], + "unspentPubKey": [ + { + "address": "mqqnn93xN81eZTLqj7Wk2cacBBTR8agFZ5", + "scriptPubKey": "2102aa869ff719f23d9959dca340cbf3b72770294c64005e53e0429948aa6e9701d1ac", + "txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1", + "vout": 1, + "amount": 1, + "confirmations":7 + } + ], + "keyStringsPubKey": [ + "cTSvhK2b3XxJezmDjVN5x1KTCtui4NaLhvb78nvprpVAiqHgQvMm" + ], + "unspentMulti": [ + { + "address": [ + "n4JAZc4cJimQbky5wxZUEDeAFZtGaZrjWK", + "msge5muNmBSRDn5nsaRcHCU6dg2zimA8wQ", + "mvz9MjocpyXdgXqRcZYazsdE8iThdvjdhk", + "miQGZ2gybQe7UvUQDBYsgcctUteij5pTpm", + "mu9kmhGrzREKsWaXUEUrsRLLMG4UMPy1LF" + ], + "scriptPubKey": "532103bf025eb410407aec5a67c975ce222e363bb88c69bb1acce45d20d85602df2ec52103d76dd6d99127f4b733e772f0c0a09c573ac7e4d69b8bf50272292da2e093de2c2103dd9acd8dd1816c825d6b0739339c171ae2cb10efb53699680537865b07086e9b2102371cabbaf466c3a536034b4bda64ad515807bffd87488f44f93c2373d4d189c9210264cd444358f8d57f8637a7309f9736806f4883aebc4fe7da4bad1e4b37f2d12c55ae", + "txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1", + "vout": 1, + "amount": 1, + "confirmations":7 + } + ], + "keyStringsMulti": [ + "cP6JBHuQf7yqeqtdKRd22ibF3VehDv7G6BdzxSNABgrv3jFJUGoN", + "cQfRwF7XLSM5xGUpF8PZvob2MZyULvZPA2j5cat2RKDJrja7FtCZ", + "cUkYub4jtFVYymHh38yMMW36nJB4pXG5Pzd5QjResq79kAndkJcg", + "cMyBgowsyrJRufoKWob73rMQB1PBqDdwFt8z4TJ6APN2HkmX1Ttm", + "cN9yZCom6hAZpHtCp8ovE1zFa7RqDf3Cr4W6AwH2tp59Jjh9JcXu" ] + } diff --git a/test/test.TransactionBuilder.js b/test/test.TransactionBuilder.js index dd5a472..6bc6bd7 100644 --- a/test/test.TransactionBuilder.js +++ b/test/test.TransactionBuilder.js @@ -220,7 +220,6 @@ describe('TransactionBuilder', function() { amount: 0.08 }]; -//console.log('[test.TransactionBuilder.js.216:outs:]',outs, outs.length); //TODO return new TransactionBuilder(opts) .setUnspent(testdata.dataUnspentSign.unspent) .setOutputs(outs); @@ -395,4 +394,107 @@ describe('TransactionBuilder', function() { util.valueToBigInt(tx.outs[N].v).cmp(970000).should.equal(0); tx.isComplete().should.equal(false); }); + + it('should sign a p2pubkey tx', function() { + var opts = { + remainderAddress: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd', + }; + var outs = outs || [{ + address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', + amount: 0.08 + }]; + + var b = new TransactionBuilder(opts) + .setUnspent(testdata.dataUnspentSign.unspentPubKey) + .setOutputs(outs) + .sign(testdata.dataUnspentSign.keyStringsPubKey); + + b.isFullySigned().should.equal(true); + var tx = b.build(); + tx.isComplete().should.equal(true); + tx.ins.length.should.equal(1); + tx.outs.length.should.equal(2); + }); + + + it('should sign a multisig tx', function() { + var opts = { + remainderAddress: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd', + }; + var outs = outs || [{ + address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', + amount: 0.08 + }]; + var b = new TransactionBuilder(opts) + .setUnspent(testdata.dataUnspentSign.unspentMulti) + .setOutputs(outs); + + b.sign(testdata.dataUnspentSign.keyStringsMulti); + b.isFullySigned().should.equal(true); + var tx = b.build(); + tx.ins.length.should.equal(1); + tx.outs.length.should.equal(2); + tx.isComplete().should.equal(true); + }); + + + it('should sign a multisig tx in steps (3-5)', function() { + var opts = { + remainderAddress: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd', + }; + var outs = outs || [{ + address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', + amount: 0.08 + }]; + var b = new TransactionBuilder(opts) + .setUnspent(testdata.dataUnspentSign.unspentMulti) + .setOutputs(outs); + + var k1 = testdata.dataUnspentSign.keyStringsMulti.slice(0,1); + var k2 = testdata.dataUnspentSign.keyStringsMulti.slice(1,2); + var k3 = testdata.dataUnspentSign.keyStringsMulti.slice(2,3); + + b.sign(k1); + b.isFullySigned().should.equal(false); + b.sign(k2); + b.isFullySigned().should.equal(false); + b.sign(k3); + b.isFullySigned().should.equal(true); + + var tx = b.build(); + tx.ins.length.should.equal(1); + tx.outs.length.should.equal(2); + tx.isComplete().should.equal(true); + }); + + + it('should count multisig signs (3-5)', function() { + var opts = { + remainderAddress: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd', + }; + var outs = outs || [{ + address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', + amount: 0.08 + }]; + var b = new TransactionBuilder(opts) + .setUnspent(testdata.dataUnspentSign.unspentMulti) + .setOutputs(outs); + + var k1 = testdata.dataUnspentSign.keyStringsMulti.slice(0,1); + var k2 = testdata.dataUnspentSign.keyStringsMulti.slice(1,2); + var k3 = testdata.dataUnspentSign.keyStringsMulti.slice(2,3); + + b.countInputMultiSig(0).should.equal(0); + b.sign(k1); + b.isFullySigned().should.equal(false); + b.countInputMultiSig(0).should.equal(1); + b.sign(k2); + b.isFullySigned().should.equal(false); + b.countInputMultiSig(0).should.equal(2); + b.sign(k3); + b.isFullySigned().should.equal(true); + b.countInputMultiSig(0).should.equal(3); + + + }); }); From 8acf093339655d1c854d146db87cf70de1a08417 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Mon, 31 Mar 2014 15:16:30 -0300 Subject: [PATCH 2/5] multisign test for signing twice with same sig --- TransactionBuilder.js | 50 +++++++++++++++++++++++++++------ examples/CreateKey.js | 4 +-- test/test.TransactionBuilder.js | 47 ++++++++++++++++++++++++++----- 3 files changed, 84 insertions(+), 17 deletions(-) diff --git a/TransactionBuilder.js b/TransactionBuilder.js index d1b9ed6..e4504d5 100644 --- a/TransactionBuilder.js +++ b/TransactionBuilder.js @@ -58,7 +58,7 @@ * * @opts * { - * remainderAddress: null, + * remainderOut: null, * fee: 0.001, * lockTime: null, * spendUnconfirmed: false, @@ -67,8 +67,12 @@ * Amounts are in BTC. instead of fee and amount; feeSat and amountSat can be given, * repectively, to provide amounts in satoshis. * - * If no remainderAddress is given, and there are remainder coins, the - * first IN address will be used to return the coins. (TODO: is this is reasonable?) + * If no remainderOut is given, and there are remainder coins, the + * first IN out will be used to return the coins. remainderOut has the form: + * remainderOut = { address: 1xxxxx} +* or + * remainderOut = { pubkeys: ['hex1','hex2',...} for multisig + * * */ @@ -101,7 +105,7 @@ function TransactionBuilder(opts) { if (opts.fee || opts.feeSat) { this.givenFeeSat = opts.fee ? opts.fee * util.COIN : opts.feeSat; } - this.remainderAddress = opts.remainderAddress; + this.remainderOut = opts.remainderOut; this.signhash = opts.signhash || Transaction.SIGHASH_ALL; this.tx = {}; @@ -134,6 +138,31 @@ TransactionBuilder._scriptForAddress = function(addressString) { return script; }; + +TransactionBuilder._scriptForPubkeys = function(out) { + + var l = out.pubkeys.length; + var pubKeyBuf=[]; + + for (var i=0; i 1) + ret = this._scriptForPubkeys(out); + else + throw new Error('unknow out type'); + + return ret; +}; + TransactionBuilder.prototype.setUnspent = function(utxos) { this.utxos = utxos; return this; @@ -279,9 +308,9 @@ TransactionBuilder.prototype._setRemainder = function(remainderIndex) { } if (remainderSat.cmp(0) > 0) { - var remainderAddress = this.remainderAddress || this.selectedUtxos[0].address; + var remainderOut = this.remainderOut || this.selectedUtxos[0]; var value = util.bigIntToValue(remainderSat); - var script = TransactionBuilder._scriptForAddress(remainderAddress); + var script = TransactionBuilder._scriptForOut(remainderOut); var txout = { v: value, s: script.getBuffer(), @@ -327,7 +356,7 @@ TransactionBuilder.prototype.setOutputs = function(outs) { for (var i = 0; i < l; i++) { var amountSat = outs[i].amountSat || util.parseValue(outs[i].amount); var value = util.bigIntToValue(amountSat); - var script = TransactionBuilder._scriptForAddress(outs[i].address); + var script = TransactionBuilder._scriptForOut(outs[i]); var txout = { v: value, s: script.getBuffer(), @@ -552,15 +581,20 @@ TransactionBuilder.prototype._signMultiSig = function(walletKeyMap, input, txSig }; }; +TransactionBuilder.prototype._signScriptHash = function(walletKeyMap, input, txSigHash) { + if (!this.hashToScriptMap) + throw new Error('hashToScriptMap not set'); + throw new Error('TX_SCRIPTHASH not supported yet'); +}; + var fnToSign = {}; fnToSign[Script.TX_PUBKEYHASH] = TransactionBuilder.prototype._signPubKeyHash; fnToSign[Script.TX_PUBKEY] = TransactionBuilder.prototype._signPubKey; fnToSign[Script.TX_MULTISIG] = TransactionBuilder.prototype._signMultiSig; fnToSign[Script.TX_SCRIPTHASH] = TransactionBuilder.prototype._signScriptHash; -//if (!this.hashToScriptMap) throw new Error('hashToScriptMap not set'); TransactionBuilder.prototype.sign = function(keys) { this._checkTx(); diff --git a/examples/CreateKey.js b/examples/CreateKey.js index 8b162cf..3264d3e 100644 --- a/examples/CreateKey.js +++ b/examples/CreateKey.js @@ -3,12 +3,12 @@ var run = function() { - // Replace '../bitcore' with 'bitcore' if you use this code elsewhere. + // replace '../bitcore' with 'bitcore' if you use this code elsewhere. var bitcore = require('../bitcore'); var networks = require('../networks'); var WalletKey = bitcore.WalletKey; - var opts = {network: networks.livenet}; + var opts = {network: networks.testnet}; function print(wk) { diff --git a/test/test.TransactionBuilder.js b/test/test.TransactionBuilder.js index 6bc6bd7..d848a3c 100644 --- a/test/test.TransactionBuilder.js +++ b/test/test.TransactionBuilder.js @@ -104,7 +104,7 @@ describe('TransactionBuilder', function() { var getBuilder2 = function (fee) { var opts = { - remainderAddress: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd', + remainderOut: {address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'}, spendUnconfirmed: true, }; @@ -134,7 +134,7 @@ describe('TransactionBuilder', function() { var utxos = testdata.dataUnspent; var opts = { - remainderAddress: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd', + remainderOut: {address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'}, spendUnconfirmed: true, }; var outs = [{ @@ -211,7 +211,7 @@ describe('TransactionBuilder', function() { var getBuilder3 = function (outs) { var opts = { - remainderAddress: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd', + remainderOut: {address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'}, spendUnconfirmed: true, }; @@ -397,7 +397,7 @@ describe('TransactionBuilder', function() { it('should sign a p2pubkey tx', function() { var opts = { - remainderAddress: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd', + remainderOut: {address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'}, }; var outs = outs || [{ address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', @@ -419,7 +419,7 @@ describe('TransactionBuilder', function() { it('should sign a multisig tx', function() { var opts = { - remainderAddress: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd', + remainderOut: {address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'}, }; var outs = outs || [{ address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', @@ -440,7 +440,7 @@ describe('TransactionBuilder', function() { it('should sign a multisig tx in steps (3-5)', function() { var opts = { - remainderAddress: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd', + remainderOut: {address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'}, }; var outs = outs || [{ address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', @@ -470,7 +470,7 @@ describe('TransactionBuilder', function() { it('should count multisig signs (3-5)', function() { var opts = { - remainderAddress: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd', + remainderOut: {address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'}, }; var outs = outs || [{ address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', @@ -494,7 +494,40 @@ describe('TransactionBuilder', function() { b.sign(k3); b.isFullySigned().should.equal(true); b.countInputMultiSig(0).should.equal(3); + }); + it('should avoid siging with the same key twice multisig signs (3-5)', function() { + var opts = { + remainderOut: {address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'}, + }; + var outs = outs || [{ + address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', + amount: 0.08 + }]; + var b = new TransactionBuilder(opts) + .setUnspent(testdata.dataUnspentSign.unspentMulti) + .setOutputs(outs); + + var k1 = testdata.dataUnspentSign.keyStringsMulti.slice(0,1); + var k23 = testdata.dataUnspentSign.keyStringsMulti.slice(1,3); + + b.countInputMultiSig(0).should.equal(0); + b.sign(k1); + b.isFullySigned().should.equal(false); + b.countInputMultiSig(0).should.equal(1); + b.sign(k1); + b.isFullySigned().should.equal(false); + b.countInputMultiSig(0).should.equal(1); + b.sign(k1); + b.isFullySigned().should.equal(false); + b.countInputMultiSig(0).should.equal(1); + + + + + b.sign(k23); + b.isFullySigned().should.equal(true); + b.countInputMultiSig(0).should.equal(3); }); }); From 4fe8dffe4a56c3fa4fbfd976af32a7c5b9e34ce7 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Mon, 31 Mar 2014 16:25:43 -0300 Subject: [PATCH 3/5] fix error msgs --- TransactionBuilder.js | 5 +- examples/CreateMultisig.js | 104 ++++++++++++++++++++++++++++++++ test/test.TransactionBuilder.js | 3 - 3 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 examples/CreateMultisig.js diff --git a/TransactionBuilder.js b/TransactionBuilder.js index e4504d5..748d0ab 100644 --- a/TransactionBuilder.js +++ b/TransactionBuilder.js @@ -158,7 +158,7 @@ TransactionBuilder._scriptForOut = function(out) { else if (out.pubkeys || out.nreq || out.nreq > 1) ret = this._scriptForPubkeys(out); else - throw new Error('unknow out type'); + throw new Error('unknown out type'); return ret; }; @@ -240,7 +240,8 @@ TransactionBuilder.prototype._selectUnspent = function(neededAmountSat) { } while (!fulfill && minConfirmationSteps.length); if (!fulfill) - throw new Error('no enough unspent to fulfill totalNeededAmount'); + throw new Error('no enough unspent to fulfill totalNeededAmount [SAT]:' + + neededAmountSat); this.selectedUtxos = sel; this._setInputMap(); diff --git a/examples/CreateMultisig.js b/examples/CreateMultisig.js new file mode 100644 index 0000000..a88e73b --- /dev/null +++ b/examples/CreateMultisig.js @@ -0,0 +1,104 @@ + +var run = function() { + bitcore = typeof (bitcore) === 'undefined' ? require('../bitcore') : bitcore; + var networks = require('../networks'); + var WalletKey = bitcore.WalletKey; + var Script = bitcore.Script; + var Builder = bitcore.TransactionBuilder; + var opts = {network: networks.testnet}; + + console.log('## Network: ' + opts.network.name); + + var input = {}; + input.addr = "n2hoFVbPrYQf7RJwiRy1tkbuPPqyhAEfbp"; + input.priv = "cS62Ej4SobZnpFQYN1PEEBr2KWf5sgRYYnELtumcG6WVCfxno39V"; + + // Complete with the corresponding UTXO you want to use + var utxos = [ + { + address: input.addr, + txid: "a2a1b0bfbbe769253787d83c097adf61e6d77088e295249e9c3f1ca8a035c639", + vout: 0, + ts: 1396288753, + scriptPubKey: "76a914e867aad8bd361f57c50adc37a0c018692b5b0c9a88ac", + amount: 0.63, + confirmations: 2 + } + ]; + + var privs = [ + "cP6JBHuQf7yqeqtdKRd22ibF3VehDv7G6BdzxSNABgrv3jFJUGoN", + "cQfRwF7XLSM5xGUpF8PZvob2MZyULvZPA2j5cat2RKDJrja7FtCZ", + "cUkYub4jtFVYymHh38yMMW36nJB4pXG5Pzd5QjResq79kAndkJcg", + "cMyBgowsyrJRufoKWob73rMQB1PBqDdwFt8z4TJ6APN2HkmX1Ttm", + "cN9yZCom6hAZpHtCp8ovE1zFa7RqDf3Cr4W6AwH2tp59Jjh9JcXu", + ]; + + var pubkeys = [] + privs.forEach(function(p) { + var wk = new WalletKey(opts); + wk.fromObj({priv: p}); + pubkeys.push(bitcore.buffertools.toHex(wk.privKey.public)); + }); + + + var outs = [{nreq:3, pubkeys:pubkeys, amount:0.05}]; + var tx = new Builder(opts) + .setUnspent(utxos) + .setOutputs(outs) + .sign([input.priv]) + .build(); + var txHex = tx.serialize().toString('hex'); + console.log('1) SEND TO MULSISIG TX: ', txHex); + console.log('[this example originally generated TXID: ff5c8b4912f6d056f0cf8431ec27032a73df22c167726267dd4cc0d7817a1e7d on testnet]\n\n\thttp://test.bitcore.io/tx/ff5c8b4912f6d056f0cf8431ec27032a73df22c167726267dd4cc0d7817a1e7d\n\n'); + + + //save scriptPubKey + var scriptPubKey = tx.outs[0].s.toString('hex'); + + /* + * + * REDDEEM TX + */ + var utxos2 = [ + { + address: input.addr, + txid: "ff5c8b4912f6d056f0cf8431ec27032a73df22c167726267dd4cc0d7817a1e7d", + vout: 0, + ts: 1396288753, + scriptPubKey: scriptPubKey, + amount: 0.05, + confirmations: 2 + } + ]; + + outs = [{address:input.addr, amount:0.04}]; + var b = new Builder(opts) + .setUnspent(utxos2) + .setOutputs(outs) + .sign(privs); + + + tx= b.build(); + + + var txHex = tx.serialize().toString('hex'); + console.log('2) REDEEM SCRIPT: ', txHex); +console.log('=> Is signed status:', b.isFullySigned(), b.countInputMultiSig(0) ); + + console.log('[this example originally generated TXID: 2813c5a670d2c9d0527718f9d0ea896c78c3c8fc57b409e67308744fc7a7a98e on testnet]\n\n\thttp://test.bitcore.io/tx/2813c5a670d2c9d0527718f9d0ea896c78c3c8fc57b409e67308744fc7a7a98e'); + +}; + +// This is just for browser & mocha compatibility +if (typeof module !== 'undefined') { + module.exports.run = run; + if (require.main === module) { + run(); + } +} else { + run(); +} + +//// + diff --git a/test/test.TransactionBuilder.js b/test/test.TransactionBuilder.js index d848a3c..100cbc3 100644 --- a/test/test.TransactionBuilder.js +++ b/test/test.TransactionBuilder.js @@ -523,9 +523,6 @@ describe('TransactionBuilder', function() { b.isFullySigned().should.equal(false); b.countInputMultiSig(0).should.equal(1); - - - b.sign(k23); b.isFullySigned().should.equal(true); b.countInputMultiSig(0).should.equal(3); From 4edab2429a8382161716a3c5246a8d057a24f6aa Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Tue, 1 Apr 2014 00:07:45 -0300 Subject: [PATCH 4/5] PayToScriptHash support (WIP) --- TransactionBuilder.js | 84 +++++++++++-- ...ultisig.js => CreateAndSignTx-Multisig.js} | 0 ....js => CreateAndSignTx-PayToPubkeyHash.js} | 0 examples/CreateAndSignTx-PayToScriptHash.js | 115 ++++++++++++++++++ 4 files changed, 189 insertions(+), 10 deletions(-) rename examples/{CreateMultisig.js => CreateAndSignTx-Multisig.js} (100%) rename examples/{CreateAndSignTx.js => CreateAndSignTx-PayToPubkeyHash.js} (100%) create mode 100644 examples/CreateAndSignTx-PayToScriptHash.js diff --git a/TransactionBuilder.js b/TransactionBuilder.js index 748d0ab..7c4f1f6 100644 --- a/TransactionBuilder.js +++ b/TransactionBuilder.js @@ -152,17 +152,35 @@ TransactionBuilder._scriptForPubkeys = function(out) { }; TransactionBuilder._scriptForOut = function(out) { - var ret; - if (out.address) - ret = this._scriptForAddress(out.address) + var ret; + if (out.address) + ret = this._scriptForAddress(out.address); else if (out.pubkeys || out.nreq || out.nreq > 1) ret = this._scriptForPubkeys(out); - else + else throw new Error('unknown out type'); return ret; }; + +TransactionBuilder.infoForP2sh = function(opts, networkName) { + var script = this._scriptForOut(opts); + var hash = util.sha256ripe160(script.getBuffer()); + + var version = networkName === 'testnet' ? + networks.testnet.addressScript : networks.livenet.addressScript; + + var addr = new Address(version, hash); + var addrStr = addr.as('base58'); + return { + script: script, + scriptBufHex: script.getBuffer().toString('hex'), + hash: hash, + address: addrStr, + }; +}; + TransactionBuilder.prototype.setUnspent = function(utxos) { this.utxos = utxos; return this; @@ -173,8 +191,7 @@ TransactionBuilder.prototype._setInputMap = function() { var l = this.selectedUtxos.length; for (var i = 0; i < l; i++) { - var utxo = this.selectedUtxos[i]; - + var utxo = this.selectedUtxos[i]; var scriptBuf = new Buffer(utxo.scriptPubKey, 'hex'); var scriptPubKey = new Script(scriptBuf); var scriptType = scriptPubKey.classify(); @@ -582,16 +599,61 @@ TransactionBuilder.prototype._signMultiSig = function(walletKeyMap, input, txSig }; }; +var fnToSign = {}; + + TransactionBuilder.prototype._signScriptHash = function(walletKeyMap, input, txSigHash) { + var originalScriptBuf = this.tx.ins[input.i].s; + + if (!this.hashToScriptMap) throw new Error('hashToScriptMap not set'); + var scriptHex = this.hashToScriptMap[input.address]; + if (!scriptHex) return; - throw new Error('TX_SCRIPTHASH not supported yet'); + var script = new Script(new Buffer(scriptHex,'hex')); + var scriptType = script.classify(); + var scriptPubKeyHex = script.getBuffer().toString('hex'); + + if (!fnToSign[scriptType]) + throw new Error('dont know how to sign p2sh script type'+ script.getRawOutType()); + + var newInput = { + address: 'TODO', // if p2pkubkeyhash -> get the address + i: input.i, + scriptPubKey: script, + scriptPubKeyHex: scriptPubKeyHex , + scriptType: scriptType, + }; + + var txSigHash2 = this.tx.hashForSignature( script, input.i, this.signhash); + var ret = fnToSign[scriptType].call(this, walletKeyMap, newInput, txSigHash2); + + var rc =1; //TODO : si alguno firmó... + if (ret.script) { + +console.log('[TransactionBuilder.js.634] IN'); //TODO + var scriptSig = new Script(originalScriptBuf); + var len = scriptSig.chunks.length; + var scriptBufNotAlreadyAppended = scriptSig.chunks[len-1] !== undefined && (typeof scriptSig.chunks[len-1] == "number" || scriptSig.chunks[len-1].toString('hex') != scriptBuf.toString('hex')); + if (rc > 0 && scriptBufNotAlreadyAppended) { + scriptSig.chunks.push(scriptBuf); + scriptSig.updateBuffer(); + ret.script = scriptSig.getBuffer(); + } + + if (scriptType == Script.TX_MULTISIG && scriptSig.finishedMultiSig()) + { + scriptSig.removePlaceHolders(); + scriptSig.prependOp0(); + ret.script = scriptSig.getBuffer(); + } + } + + return ret; }; - -var fnToSign = {}; fnToSign[Script.TX_PUBKEYHASH] = TransactionBuilder.prototype._signPubKeyHash; fnToSign[Script.TX_PUBKEY] = TransactionBuilder.prototype._signPubKey; fnToSign[Script.TX_MULTISIG] = TransactionBuilder.prototype._signMultiSig; @@ -605,13 +667,13 @@ TransactionBuilder.prototype.sign = function(keys) { walletKeyMap = TransactionBuilder._mapKeys(keys); + for (var i = 0; i < l; i++) { var input = this.inputMap[i]; var txSigHash = this.tx.hashForSignature( input.scriptPubKey, i, this.signhash); - var ret = fnToSign[input.scriptType].call(this, walletKeyMap, input, txSigHash); if (ret && ret.script) { tx.ins[i].s = ret.script; //esto no aqui TODO @@ -624,6 +686,8 @@ TransactionBuilder.prototype.sign = function(keys) { // [addr -> script] TransactionBuilder.prototype.setHashToScriptMap = function(hashToScriptMap) { this.hashToScriptMap= hashToScriptMap; + + return this; }; diff --git a/examples/CreateMultisig.js b/examples/CreateAndSignTx-Multisig.js similarity index 100% rename from examples/CreateMultisig.js rename to examples/CreateAndSignTx-Multisig.js diff --git a/examples/CreateAndSignTx.js b/examples/CreateAndSignTx-PayToPubkeyHash.js similarity index 100% rename from examples/CreateAndSignTx.js rename to examples/CreateAndSignTx-PayToPubkeyHash.js diff --git a/examples/CreateAndSignTx-PayToScriptHash.js b/examples/CreateAndSignTx-PayToScriptHash.js new file mode 100644 index 0000000..d302411 --- /dev/null +++ b/examples/CreateAndSignTx-PayToScriptHash.js @@ -0,0 +1,115 @@ +var run = function() { + bitcore = typeof (bitcore) === 'undefined' ? require('../bitcore') : bitcore; + var networks = require('../networks'); + var WalletKey = bitcore.WalletKey; + var Script = bitcore.Script; + var Builder = bitcore.TransactionBuilder; + var opts = {network: networks.testnet}; + + console.log('## Network: ' + opts.network.name); + + var input = {}; + input.addr = "n2hoFVbPrYQf7RJwiRy1tkbuPPqyhAEfbp"; + input.priv = "cS62Ej4SobZnpFQYN1PEEBr2KWf5sgRYYnELtumcG6WVCfxno39V"; + + // Complete with the corresponding UTXO you want to use + var utxos = [ + { + address: "n2hoFVbPrYQf7RJwiRy1tkbuPPqyhAEfbp", + txid: "ff5c8b4912f6d056f0cf8431ec27032a73df22c167726267dd4cc0d7817a1e7d", + vout: 1, + ts: 1396290442, + scriptPubKey: "76a914e867aad8bd361f57c50adc37a0c018692b5b0c9a88ac", + amount: 0.5799, + confirmations: 7 + } + ]; + + var privs = [ + "cP6JBHuQf7yqeqtdKRd22ibF3VehDv7G6BdzxSNABgrv3jFJUGoN", + "cQfRwF7XLSM5xGUpF8PZvob2MZyULvZPA2j5cat2RKDJrja7FtCZ", + "cUkYub4jtFVYymHh38yMMW36nJB4pXG5Pzd5QjResq79kAndkJcg", + "cMyBgowsyrJRufoKWob73rMQB1PBqDdwFt8z4TJ6APN2HkmX1Ttm", + "cN9yZCom6hAZpHtCp8ovE1zFa7RqDf3Cr4W6AwH2tp59Jjh9JcXu", + ]; + + var pubkeys = [] + privs.forEach(function(p) { + var wk = new WalletKey(opts); + wk.fromObj({priv: p}); + pubkeys.push(bitcore.buffertools.toHex(wk.privKey.public)); + }); + + // multisig p2sh + var opts = {nreq:3, pubkeys:pubkeys, amount:0.05}; + + // p2scriphash p2sh + //var opts = [{address: an_address, amount:0.05}]; + + var info = Builder.infoForP2sh(opts, 'testnet'); + var p2shScript = info.scriptBufHex; + var p2shAddress = info.address; + var outs = [{address:p2shAddress, amount:0.05}]; + var tx = new Builder(opts) + .setUnspent(utxos) + .setOutputs(outs) + .sign([input.priv]) + .build(); + var txHex = tx.serialize().toString('hex'); + console.log('1) SEND TO P2SH TX: ', txHex); + console.log('[this example originally generated TXID: ba20653648a896ae95005b8f52847935a7313da06cd7295bb2cfc8b5c1b36c71 on testnet]\n\n\thttp://test.bitcore.io/tx/ba20653648a896ae95005b8f52847935a7313da06cd7295bb2cfc8b5c1b36c71\n\n'); + + //save scriptPubKey + var scriptPubKey = tx.outs[0].s.toString('hex'); + + /* + * + * REDDEEM TX + */ + var utxos2 = [ + { + address: p2shAddress, + txid: "ba20653648a896ae95005b8f52847935a7313da06cd7295bb2cfc8b5c1b36c71", + vout: 0, + ts: 1396288753, + scriptPubKey: scriptPubKey, + amount: 0.05, + confirmations: 2 + } + ]; + + outs = [{address:input.addr, amount:0.04}]; + + var hashMap = {}; + hashMap[p2shAddress]=p2shScript; + + var b = new Builder(opts) + .setUnspent(utxos2) + .setHashToScriptMap(hashMap) + .setOutputs(outs) + .sign(privs); + + + tx= b.build(); + + + var txHex = tx.serialize().toString('hex'); + console.log('2) REDEEM SCRIPT: ', txHex); +console.log('=> Is signed status:', b.isFullySigned(), b.countInputMultiSig(0) ); + + console.log('[this example originally generated TXID: 2813c5a670d2c9d0527718f9d0ea896c78c3c8fc57b409e67308744fc7a7a98e on testnet]\n\n\thttp://test.bitcore.io/tx/2813c5a670d2c9d0527718f9d0ea896c78c3c8fc57b409e67308744fc7a7a98e'); + +}; + +// This is just for browser & mocha compatibility +if (typeof module !== 'undefined') { + module.exports.run = run; + if (require.main === module) { + run(); + } +} else { + run(); +} + +//// + From d8f49e87aeb419d1c10e11ddc479fa4c9b736952 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Tue, 1 Apr 2014 09:58:17 -0300 Subject: [PATCH 5/5] different keys p2script example, add new examples in tets --- examples/CreateAndSignTx-PayToScriptHash.js | 21 +++++++++++++-------- test/test.examples.js | 6 +++++- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/examples/CreateAndSignTx-PayToScriptHash.js b/examples/CreateAndSignTx-PayToScriptHash.js index d302411..ff930c1 100644 --- a/examples/CreateAndSignTx-PayToScriptHash.js +++ b/examples/CreateAndSignTx-PayToScriptHash.js @@ -16,21 +16,21 @@ var run = function() { var utxos = [ { address: "n2hoFVbPrYQf7RJwiRy1tkbuPPqyhAEfbp", - txid: "ff5c8b4912f6d056f0cf8431ec27032a73df22c167726267dd4cc0d7817a1e7d", + txid: "ba20653648a896ae95005b8f52847935a7313da06cd7295bb2cfc8b5c1b36c71", vout: 1, ts: 1396290442, scriptPubKey: "76a914e867aad8bd361f57c50adc37a0c018692b5b0c9a88ac", - amount: 0.5799, + amount: 0.5298, confirmations: 7 } ]; var privs = [ - "cP6JBHuQf7yqeqtdKRd22ibF3VehDv7G6BdzxSNABgrv3jFJUGoN", - "cQfRwF7XLSM5xGUpF8PZvob2MZyULvZPA2j5cat2RKDJrja7FtCZ", - "cUkYub4jtFVYymHh38yMMW36nJB4pXG5Pzd5QjResq79kAndkJcg", - "cMyBgowsyrJRufoKWob73rMQB1PBqDdwFt8z4TJ6APN2HkmX1Ttm", - "cN9yZCom6hAZpHtCp8ovE1zFa7RqDf3Cr4W6AwH2tp59Jjh9JcXu", + "cMpKwGr5oxEacN95WFKNEq6tTcvi11regFwS3muHvGYVxMPJX8JA", + "cVf32m9MR4vxcPwKNJuPepUe8XrHD2z63eCk76d6njRGyCkXpkSM", + "cQ2sVRFX4jQYMLhWyzz6jTQ2xju51P36968ecXnPhRLKLH677eKR", + "cSw7x9ERcmeWCU3yVBT6Nz7b9JiZ5yjUB7JMhBUv9UM7rSaDpwX9", + "cRQBM8qM4ZXJGP1De4D5RtJm7Q6FNWQSMx7YExxzgn2ehjM3haxW", ]; var pubkeys = [] @@ -49,6 +49,8 @@ var run = function() { var info = Builder.infoForP2sh(opts, 'testnet'); var p2shScript = info.scriptBufHex; var p2shAddress = info.address; + + var outs = [{address:p2shAddress, amount:0.05}]; var tx = new Builder(opts) .setUnspent(utxos) @@ -56,8 +58,11 @@ var run = function() { .sign([input.priv]) .build(); var txHex = tx.serialize().toString('hex'); + + + console.log('p2sh address: ' + p2shAddress); //TODO console.log('1) SEND TO P2SH TX: ', txHex); - console.log('[this example originally generated TXID: ba20653648a896ae95005b8f52847935a7313da06cd7295bb2cfc8b5c1b36c71 on testnet]\n\n\thttp://test.bitcore.io/tx/ba20653648a896ae95005b8f52847935a7313da06cd7295bb2cfc8b5c1b36c71\n\n'); + console.log('[this example originally generated TXID: 8675a1f7ab0c2eeec2ff2def539446d1942efffd468319107429b894e60ecac3 on testnet]\n\n\thttp://test.bitcore.io/tx/8675a1f7ab0c2eeec2ff2def539446d1942efffd468319107429b894e60ecac3\n\n'); //save scriptPubKey var scriptPubKey = tx.outs[0].s.toString('hex'); diff --git a/test/test.examples.js b/test/test.examples.js index 4115bc7..2017691 100644 --- a/test/test.examples.js +++ b/test/test.examples.js @@ -10,7 +10,11 @@ var examples = [ 'PeerManager', 'Rpc', 'SendTx', - 'CreateAndSignTx', + 'CreateScript', + 'CreateKey', + 'CreateAndSignTx-Multisig', + 'CreateAndSignTx-PayToPubkeyHash', + 'CreateAndSignTx-PayToScriptHash', 'Script', ];