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..7c4f1f6 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 + * * */ @@ -89,7 +93,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; @@ -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,49 @@ 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('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; @@ -144,18 +191,28 @@ 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; }; @@ -200,7 +257,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(); @@ -268,9 +326,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(), @@ -316,7 +374,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(), @@ -360,14 +418,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 +437,260 @@ 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 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; +}; + +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; TransactionBuilder.prototype.sign = function(keys) { this._checkTx(); - var tx = this.tx, ins = tx.ins, - l = ins.length; + l = ins.length, + walletKeyMap = TransactionBuilder._mapKeys(keys); + - var walletKeyMap = TransactionBuilder._mapKeys(keys); for (var i = 0; i < l; i++) { - var im = this.inputMap[i]; - if (typeof im === 'undefined') continue; - var wk = walletKeyMap[im.address]; - if (!wk) continue; + var input = this.inputMap[i]; - var scriptBuf = new Buffer(im.scriptPubKey, 'hex'); + var txSigHash = this.tx.hashForSignature( + input.scriptPubKey, i, this.signhash); -//TODO: support p2sh - var s = new Script(scriptBuf); - TransactionBuilder._checkSupportedScriptType(s); - - var txSigHash = this.tx.hashForSignature(s, i, this.signhash); - 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(); - tx.ins[i].s = scriptSig.getBuffer(); - this.inputsSigned++; + var ret = fnToSign[input.scriptType].call(this, walletKeyMap, input, txSigHash); + if (ret && ret.script) { + tx.ins[i].s = ret.script; //esto no aqui TODO + if (ret.isFullySigned) this.inputsSigned++; + } } return this; }; +// [addr -> script] +TransactionBuilder.prototype.setHashToScriptMap = function(hashToScriptMap) { + this.hashToScriptMap= hashToScriptMap; + + return this; +}; + + TransactionBuilder.prototype.isFullySigned = function() { return this.inputsSigned === this.tx.ins.length; }; diff --git a/examples/CreateAndSignTx-Multisig.js b/examples/CreateAndSignTx-Multisig.js new file mode 100644 index 0000000..a88e73b --- /dev/null +++ b/examples/CreateAndSignTx-Multisig.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/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..ff930c1 --- /dev/null +++ b/examples/CreateAndSignTx-PayToScriptHash.js @@ -0,0 +1,120 @@ +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: "ba20653648a896ae95005b8f52847935a7313da06cd7295bb2cfc8b5c1b36c71", + vout: 1, + ts: 1396290442, + scriptPubKey: "76a914e867aad8bd361f57c50adc37a0c018692b5b0c9a88ac", + amount: 0.5298, + confirmations: 7 + } + ]; + + var privs = [ + "cMpKwGr5oxEacN95WFKNEq6tTcvi11regFwS3muHvGYVxMPJX8JA", + "cVf32m9MR4vxcPwKNJuPepUe8XrHD2z63eCk76d6njRGyCkXpkSM", + "cQ2sVRFX4jQYMLhWyzz6jTQ2xju51P36968ecXnPhRLKLH677eKR", + "cSw7x9ERcmeWCU3yVBT6Nz7b9JiZ5yjUB7JMhBUv9UM7rSaDpwX9", + "cRQBM8qM4ZXJGP1De4D5RtJm7Q6FNWQSMx7YExxzgn2ehjM3haxW", + ]; + + 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('p2sh address: ' + p2shAddress); //TODO + console.log('1) SEND TO P2SH TX: ', txHex); + 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'); + + /* + * + * 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(); +} + +//// + 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/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..100cbc3 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, }; @@ -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,137 @@ 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 = { + remainderOut: {address: '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 = { + remainderOut: {address: '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 = { + 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 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 = { + 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 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); + }); + + + 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); + }); }); 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', ];