From 4edab2429a8382161716a3c5246a8d057a24f6aa Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Tue, 1 Apr 2014 00:07:45 -0300 Subject: [PATCH] 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(); +} + +//// +