diff --git a/Script.js b/Script.js index 95f522c..d104d98 100644 --- a/Script.js +++ b/Script.js @@ -490,7 +490,25 @@ Script.prototype.toHumanReadable = function() { } } return s; +}; +Script.prototype.countMissingSignatures = function() { + var ret = 0; + if (!Buffer.isBuffer(this.chunks[0]) && this.chunks[0] ===0) { + // Multisig, skip first 0x0 + for (var i = 1; i < this.chunks.length; i++) { + if (this.chunks[i]===0 + || buffertools.compare(this.chunks[i], util.EMPTY_BUFFER) === 0){ + ret++; + } + } + } + else { + if (buffertools.compare(this.getBuffer(), util.EMPTY_BUFFER) === 0) { + ret = 1; + } + } + return ret; }; Script.stringToBuffer = function(s) { diff --git a/Transaction.js b/Transaction.js index 1f7634c..c90ce4c 100644 --- a/Transaction.js +++ b/Transaction.js @@ -552,28 +552,28 @@ Transaction.prototype.getSize = function getHash() { return this.size; }; + +Transaction.prototype.countInputMissingSignatures = function(index) { + var ret = 0; + var script = new Script(this.ins[index].s); + return script.countMissingSignatures(); +}; + + +Transaction.prototype.isInputComplete = function(index) { + return this.countInputMissingSignatures(index)===0; +}; + Transaction.prototype.isComplete = function() { var ret = true; var l = this.ins.length; for (var i = 0; i < l; i++) { - 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; - } - } + if (!this.isInputComplete(i)){ + 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 7c4f1f6..7ef7ac8 100644 --- a/TransactionBuilder.js +++ b/TransactionBuilder.js @@ -115,12 +115,12 @@ function TransactionBuilder(opts) { } /* - * _scriptForAddress + * scriptForAddress * * Returns a scriptPubKey for the given address type */ -TransactionBuilder._scriptForAddress = function(addressString) { +TransactionBuilder.scriptForAddress = function(addressString) { var livenet = networks.livenet; var testnet = networks.testnet; @@ -154,7 +154,7 @@ TransactionBuilder._scriptForPubkeys = function(out) { TransactionBuilder._scriptForOut = function(out) { var ret; if (out.address) - ret = this._scriptForAddress(out.address); + ret = this.scriptForAddress(out.address); else if (out.pubkeys || out.nreq || out.nreq > 1) ret = this._scriptForPubkeys(out); else @@ -201,8 +201,7 @@ TransactionBuilder.prototype._setInputMap = function() { ' Type:' + scriptPubKey.getRawOutType()); inputMap.push({ - address: utxo.address, //TODO que pasa en multisig normal? - scriptPubKeyHex: utxo.scriptPubKey, + address: utxo.address, scriptPubKey: scriptPubKey, scriptType: scriptType, i: i, @@ -394,7 +393,6 @@ TransactionBuilder.prototype.setOutputs = function(outs) { }; TransactionBuilder._mapKeys = function(keys) { - //prepare keys var walletKeyMap = {}; var l = keys.length; @@ -437,10 +435,43 @@ TransactionBuilder.prototype._checkTx = function() { throw new Error('tx is not defined'); }; + +TransactionBuilder.prototype._multiFindKey = function(walletKeyMap,pubKeyHash) { + var wk; + [ networks.livenet, networks.testnet].forEach(function(n) { + [ n.addressPubkey, n.addressScript].forEach(function(v) { + var a = new Address(v,pubKeyHash); + if (!wk && walletKeyMap[a]) { + wk = walletKeyMap[a]; + } + }); + }); + + return wk; +}; + +TransactionBuilder.prototype._findWalletKey = function(walletKeyMap, input) { + var wk; + + if (input.address) { + wk = walletKeyMap[input.address]; + } + else if (input.pubKeyHash) { + wk = this._multiFindKey(walletKeyMap, input.pubKeyHash); + } + else if (input.pubKeyBuf) { + var pubKeyHash = util.sha256ripe160(input.pubKeyBuf); + wk = this._multiFindKey(walletKeyMap, pubKeyHash); + } else { + throw new Error('no infomation at input to find keys'); + } + return wk; +}; + TransactionBuilder.prototype._signPubKey = function(walletKeyMap, input, txSigHash) { if (this.tx.ins[input.i].s.length > 0) return {}; - var wk = walletKeyMap[input.address]; + var wk = this._findWalletKey(walletKeyMap, input); if (!wk) return; var sigRaw = TransactionBuilder._signHashAndVerify(wk, txSigHash); @@ -451,14 +482,14 @@ TransactionBuilder.prototype._signPubKey = function(walletKeyMap, input, txSigHa var scriptSig = new Script(); scriptSig.chunks.push(sig); scriptSig.updateBuffer(); - return {isFullySigned: true, script: scriptSig.getBuffer()}; + return {isFullySigned: true, signaturesAdded: 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]; + var wk = this._findWalletKey(walletKeyMap, input); if (!wk) return; var sigRaw = TransactionBuilder._signHashAndVerify(wk, txSigHash); @@ -470,18 +501,16 @@ TransactionBuilder.prototype._signPubKeyHash = function(walletKeyMap, input, txS scriptSig.chunks.push(sig); scriptSig.chunks.push(wk.privKey.public); scriptSig.updateBuffer(); - return {isFullySigned: true, script: scriptSig.getBuffer()}; + return {isFullySigned: true, signaturesAdded: 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 newInput = this._getInputForP2sh(script, input.i); + var newTxSigHash = this.tx.hashForSignature( script, newInput.i, this.signhash); + var ret = fnToSign[scriptType].call(this, walletKeyMap, newInput, newTxSigHash); - 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(); - } + if (ret && ret.script && ret.signaturesAdded) { + ret.script = this._addScript(ret.script, scriptBuf); } - return ret; }; @@ -676,17 +692,16 @@ TransactionBuilder.prototype.sign = function(keys) { var ret = fnToSign[input.scriptType].call(this, walletKeyMap, input, txSigHash); if (ret && ret.script) { - tx.ins[i].s = ret.script; //esto no aqui TODO + tx.ins[i].s = ret.script; if (ret.isFullySigned) this.inputsSigned++; } } return this; }; -// [addr -> script] +// [ { address:scriptHex }] TransactionBuilder.prototype.setHashToScriptMap = function(hashToScriptMap) { this.hashToScriptMap= hashToScriptMap; - return this; }; diff --git a/examples/CreateAndSignTx-Multisig.js b/examples/CreateAndSignTx-Multisig.js index a88e73b..d519963 100644 --- a/examples/CreateAndSignTx-Multisig.js +++ b/examples/CreateAndSignTx-Multisig.js @@ -17,11 +17,11 @@ var run = function() { var utxos = [ { address: input.addr, - txid: "a2a1b0bfbbe769253787d83c097adf61e6d77088e295249e9c3f1ca8a035c639", - vout: 0, + txid: "39c71ebda371f75f4b854a720eaf9898b237facf3c2b101b58cd4383a44a6adc", + vout: 1, ts: 1396288753, scriptPubKey: "76a914e867aad8bd361f57c50adc37a0c018692b5b0c9a88ac", - amount: 0.63, + amount: 0.4296, confirmations: 2 } ]; @@ -50,7 +50,7 @@ var run = function() { .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'); + console.log('[this example originally generated TXID: e4bc22d8c519d3cf848d710619f8480be56176a4a6548dfbe865ab3886b578b5 on testnet]\n\n\thttp://test.bitcore.io/tx/e4bc22d8c519d3cf848d710619f8480be56176a4a6548dfbe865ab3886b578b5\n\n'); //save scriptPubKey @@ -63,7 +63,7 @@ var run = function() { var utxos2 = [ { address: input.addr, - txid: "ff5c8b4912f6d056f0cf8431ec27032a73df22c167726267dd4cc0d7817a1e7d", + txid: "e4bc22d8c519d3cf848d710619f8480be56176a4a6548dfbe865ab3886b578b5", vout: 0, ts: 1396288753, scriptPubKey: scriptPubKey, @@ -84,9 +84,9 @@ var run = function() { var txHex = tx.serialize().toString('hex'); console.log('2) REDEEM SCRIPT: ', txHex); -console.log('=> Is signed status:', b.isFullySigned(), b.countInputMultiSig(0) ); +console.log('=> Is signed status:', b.isFullySigned(), tx.countInputMissingSignatures(0) ); - console.log('[this example originally generated TXID: 2813c5a670d2c9d0527718f9d0ea896c78c3c8fc57b409e67308744fc7a7a98e on testnet]\n\n\thttp://test.bitcore.io/tx/2813c5a670d2c9d0527718f9d0ea896c78c3c8fc57b409e67308744fc7a7a98e'); + console.log('[this example originally generated TXID: 1eb388977b2de99562eb0fbcc661a100eaffed99c53bfcfebe5a087002039b83 on testnet]\n\n\thttp://test.bitcore.io/tx/1eb388977b2de99562eb0fbcc661a100eaffed99c53bfcfebe5a087002039b83'); }; diff --git a/examples/CreateAndSignTx-PayToScriptHash.js b/examples/CreateAndSignTx-PayToScriptHash.js index ff930c1..8b035ee 100644 --- a/examples/CreateAndSignTx-PayToScriptHash.js +++ b/examples/CreateAndSignTx-PayToScriptHash.js @@ -16,11 +16,11 @@ var run = function() { var utxos = [ { address: "n2hoFVbPrYQf7RJwiRy1tkbuPPqyhAEfbp", - txid: "ba20653648a896ae95005b8f52847935a7313da06cd7295bb2cfc8b5c1b36c71", + txid: "e4bc22d8c519d3cf848d710619f8480be56176a4a6548dfbe865ab3886b578b5", vout: 1, ts: 1396290442, scriptPubKey: "76a914e867aad8bd361f57c50adc37a0c018692b5b0c9a88ac", - amount: 0.5298, + amount: 0.3795, confirmations: 7 } ]; @@ -57,12 +57,13 @@ var run = function() { .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'); + console.log('## p2sh address: ' + p2shAddress); //TODO + console.log('\n1) SEND TO P2SH TX: ', txHex); + console.log('[this example originally generated TXID: c2e50d1c8c581d8c4408378b751633f7eb86687fc5f0502be7b467173f275ae7 on testnet]\n\n\thttp://test.bitcore.io/tx/c2e50d1c8c581d8c4408378b751633f7eb86687fc5f0502be7b467173f275ae7\n\n'); //save scriptPubKey var scriptPubKey = tx.outs[0].s.toString('hex'); @@ -74,12 +75,12 @@ var run = function() { var utxos2 = [ { address: p2shAddress, - txid: "ba20653648a896ae95005b8f52847935a7313da06cd7295bb2cfc8b5c1b36c71", + txid: "c2e50d1c8c581d8c4408378b751633f7eb86687fc5f0502be7b467173f275ae7", vout: 0, - ts: 1396288753, - scriptPubKey: scriptPubKey, + ts: 1396375187, + scriptPubKey: scriptPubKey, amount: 0.05, - confirmations: 2 + confirmations: 1 } ]; @@ -94,18 +95,42 @@ var run = function() { .setOutputs(outs) .sign(privs); - tx= b.build(); +console.log('Builder:'); +console.log('\tSignatures:' + tx.countInputMissingSignatures(0) ); +console.log('\t#isFullySigned:' + b.isFullySigned() ); + +console.log('TX:'); +console.log('\t #isComplete:' + tx.isComplete() ); + 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: 8284aa3b6f9c71c35ecb1d61d05ae78c8ca1f36940eaa615b50584dfc3d95cb7 on testnet]\n\n\thttp://test.bitcore.io/tx/8284aa3b6f9c71c35ecb1d61d05ae78c8ca1f36940eaa615b50584dfc3d95cb7\n\n'); - console.log('[this example originally generated TXID: 2813c5a670d2c9d0527718f9d0ea896c78c3c8fc57b409e67308744fc7a7a98e on testnet]\n\n\thttp://test.bitcore.io/tx/2813c5a670d2c9d0527718f9d0ea896c78c3c8fc57b409e67308744fc7a7a98e'); +/* + // To send TX with RPC: + var RpcClient = bitcore.RpcClient; + var config = { + protocol: 'http', + user: 'user', + pass: 'pass', + host: '127.0.0.1', + port: '18332', + }; + var rpc = new RpcClient(config); + rpc.sendRawTransaction(txHex, function(err, ret) { + console.log('err', err); //TODO + console.log('ret', ret); //TODO + process.exit(-1); + }); +}; +*/ }; + // This is just for browser & mocha compatibility if (typeof module !== 'undefined') { module.exports.run = run; @@ -115,6 +140,3 @@ if (typeof module !== 'undefined') { } else { run(); } - -//// - diff --git a/examples/CreateKey.js b/examples/CreateKey.js index 3264d3e..7f2f8a1 100644 --- a/examples/CreateKey.js +++ b/examples/CreateKey.js @@ -32,7 +32,7 @@ var run = function() { //Generate from private Key WIF. Compressed status taken from WIF. var wk2 = new WalletKey(opts); - wk2.fromObj({priv:'cS62Ej4SobZnpFQYN1PEEBr2KWf5sgRYYnELtumcG6WVCfxno39V'}); + wk2.fromObj({priv:'cMpKwGr5oxEacN95WFKNEq6tTcvi11regFwS3muHvGYVxMPJX8JA'}); print(wk2); diff --git a/test/data/unspentSign.json b/test/data/unspentSign.json index 1121108..19f9aac 100644 --- a/test/data/unspentSign.json +++ b/test/data/unspentSign.json @@ -65,7 +65,23 @@ "cUkYub4jtFVYymHh38yMMW36nJB4pXG5Pzd5QjResq79kAndkJcg", "cMyBgowsyrJRufoKWob73rMQB1PBqDdwFt8z4TJ6APN2HkmX1Ttm", "cN9yZCom6hAZpHtCp8ovE1zFa7RqDf3Cr4W6AwH2tp59Jjh9JcXu" + ], + "unspentP2sh": [ + { + "address": "2Mwswt6Eih28xH8611fexpqKqJCLJMomveK", + "scriptPubKey": "a91432d272ce8a9b482b363408a0b1dd28123d59c63387", + "txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1", + "vout": 1, + "amount": 1, + "confirmations":7 + } + ], + "keyStringsP2sh": [ + "cMpKwGr5oxEacN95WFKNEq6tTcvi11regFwS3muHvGYVxMPJX8JA", + "cVf32m9MR4vxcPwKNJuPepUe8XrHD2z63eCk76d6njRGyCkXpkSM", + "cQ2sVRFX4jQYMLhWyzz6jTQ2xju51P36968ecXnPhRLKLH677eKR", + "cSw7x9ERcmeWCU3yVBT6Nz7b9JiZ5yjUB7JMhBUv9UM7rSaDpwX9", + "cRQBM8qM4ZXJGP1De4D5RtJm7Q6FNWQSMx7YExxzgn2ehjM3haxW" ] - } diff --git a/test/test.TransactionBuilder.js b/test/test.TransactionBuilder.js index 100cbc3..dc32198 100644 --- a/test/test.TransactionBuilder.js +++ b/test/test.TransactionBuilder.js @@ -11,7 +11,9 @@ 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'); @@ -484,16 +486,26 @@ describe('TransactionBuilder', function() { var k2 = testdata.dataUnspentSign.keyStringsMulti.slice(1,2); var k3 = testdata.dataUnspentSign.keyStringsMulti.slice(2,3); - b.countInputMultiSig(0).should.equal(0); + var tx = b.build(); + + b.isFullySigned().should.equal(false); + + // This is cumbersome. Before sign, missing is 1. Need to be changed in the future + tx.countInputMissingSignatures(0).should.equal(1); + b.sign(['cSq7yo4fvsbMyWVN945VUGUWMaSazZPWqBVJZyoGsHmNq6W4HVBV']); + tx.countInputMissingSignatures(0).should.equal(1); + b.sign(k1); + tx.countInputMissingSignatures(0).should.equal(2); b.isFullySigned().should.equal(false); - b.countInputMultiSig(0).should.equal(1); + b.sign(k2); + tx.countInputMissingSignatures(0).should.equal(1); b.isFullySigned().should.equal(false); - b.countInputMultiSig(0).should.equal(2); + b.sign(k3); + tx.countInputMissingSignatures(0).should.equal(0); b.isFullySigned().should.equal(true); - b.countInputMultiSig(0).should.equal(3); }); @@ -511,20 +523,162 @@ describe('TransactionBuilder', function() { var k1 = testdata.dataUnspentSign.keyStringsMulti.slice(0,1); var k23 = testdata.dataUnspentSign.keyStringsMulti.slice(1,3); + var tx = b.build(); - b.countInputMultiSig(0).should.equal(0); + tx.countInputMissingSignatures(0).should.equal(1); b.sign(k1); b.isFullySigned().should.equal(false); - b.countInputMultiSig(0).should.equal(1); + tx.countInputMissingSignatures(0).should.equal(2); b.sign(k1); b.isFullySigned().should.equal(false); - b.countInputMultiSig(0).should.equal(1); + tx.countInputMissingSignatures(0).should.equal(2); b.sign(k1); b.isFullySigned().should.equal(false); - b.countInputMultiSig(0).should.equal(1); - + tx.countInputMissingSignatures(0).should.equal(2); b.sign(k23); b.isFullySigned().should.equal(true); - b.countInputMultiSig(0).should.equal(3); + tx.countInputMissingSignatures(0).should.equal(0); }); + + + var getInfoForP2sh = function () { + var privs = testdata.dataUnspentSign.keyStringsP2sh; + var pubkeys = []; + privs.forEach(function(p) { + var wk = new WalletKey({network: networks.testnet}); + wk.fromObj({priv: p}); + pubkeys.push(bitcore.buffertools.toHex(wk.privKey.public)); + }); + + return { + privkeys: privs, + pubkeys: pubkeys, + }; + }; + + var getP2shBuilder = function(setMap) { + var network = 'testnet'; + var opts = { + remainderOut: {address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'}, + }; + var data = getInfoForP2sh(); + // multisig p2sh + var p2shOpts = {nreq:3, pubkeys:data.pubkeys, amount:0.05}; + var info = TransactionBuilder.infoForP2sh(p2shOpts, network); + + var outs = outs || [{ + address: info.address, + amount: 0.08 + }]; + var b = new TransactionBuilder(opts) + .setUnspent(testdata.dataUnspentSign.unspentP2sh) + .setOutputs(outs); + + if (setMap) { + var hashMap = {}; + hashMap[info.address]=info.scriptBufHex; + b.setHashToScriptMap(hashMap); + } + return b; + }; + + it('should fail to sign a p2sh/multisign tx if none script map was given', function() { + var b = getP2shBuilder(); + (function() {b.sign(testdata.dataUnspentSign.keyStringsP2sh);}).should.throw(); + }); + + it('should sign a p2sh/multisign tx', function() { + var b = getP2shBuilder(1); + b.sign(testdata.dataUnspentSign.keyStringsP2sh); + 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 in steps a p2sh/multisign tx', function() { + var b = getP2shBuilder(1); + + var k1 = testdata.dataUnspentSign.keyStringsP2sh.slice(0,1); + var k2 = testdata.dataUnspentSign.keyStringsP2sh.slice(1,2); + var k5 = testdata.dataUnspentSign.keyStringsP2sh.slice(4,5); + b.isFullySigned().should.equal(false); + + b.sign(k1); + b.isFullySigned().should.equal(false); + + var tx = b.build(); + tx.ins.length.should.equal(1); + tx.outs.length.should.equal(2); + tx.isComplete().should.equal(false); + + // Sign with the same + b.sign(k1); + b.isFullySigned().should.equal(false); + tx.isComplete().should.equal(false); + + // Sign with k5 + b.sign(k5); +/// + b.isFullySigned().should.equal(false); + tx.isComplete().should.equal(false); + + // Sign with same + b.sign(k5); + b.isFullySigned().should.equal(false); + tx.isComplete().should.equal(false); + + + // Sign k2 + b.sign(k2); + b.isFullySigned().should.equal(true); + tx.isComplete().should.equal(true); + }); + + it('should sign in steps a p2sh/p2pubkeyhash tx', function() { + var priv = 'cMpKwGr5oxEacN95WFKNEq6tTcvi11regFwS3muHvGYVxMPJX8JA'; + var network = 'testnet'; + var opts = { + remainderOut: {address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'}, + }; + // p2hash/ p2sh + var p2shOpts = {address:'mgwqzy6pF5BSc72vxHBFSnnhNEBcV4TJzV', amount:0.05}; + var info = TransactionBuilder.infoForP2sh(p2shOpts, network); + + //addr: 2NAwCQ1jPYPrSsyBQvfP6AJ6d6SSxnHsZ4e + //hash: de09d4a9c7e53e08043efc74d14490dbcf03b0ba + // + var outs = outs || [{ + address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', + amount: 0.08 + }]; + //info.scriptBufHex, + + var s = TransactionBuilder.scriptForAddress(info.address) + .getBuffer().toString('hex'); + + var b = new TransactionBuilder(opts) + .setUnspent([{ + "address": info.address, + "scriptPubKey": s, + "txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1", + "vout": 1, + "amount": 1, + "confirmations":7 + }]) + .setOutputs(outs); + + var hashMap = {}; + hashMap[info.address]=info.scriptBufHex; + b.setHashToScriptMap(hashMap); + b.sign([priv]); + 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); + }); + });