From 2af6ab76508e25ced857f2c91d1732ef47557bfb Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Mon, 31 Mar 2014 14:41:27 -0300 Subject: [PATCH] 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); + + + }); });