From a2709fad7f57b000333371954016045e12fc4bed Mon Sep 17 00:00:00 2001 From: Gavin Andresen Date: Thu, 31 May 2012 16:01:16 -0400 Subject: [PATCH] Implement raw transaction RPC calls Implement listunspent / getrawtransaction / createrawtransaction / signrawtransaction, to support creation and signing-on-multiple-device multisignature transactions. --- bitcoin-qt.pro | 1 + src/bitcoinrpc.cpp | 66 ++---- src/makefile.linux-mingw | 1 + src/makefile.mingw | 1 + src/makefile.osx | 1 + src/makefile.unix | 1 + src/rpcrawtransaction.cpp | 470 ++++++++++++++++++++++++++++++++++++++ src/script.cpp | 139 ++++++++++- src/script.h | 4 + src/test/script_tests.cpp | 113 +++++++++ src/wallet.cpp | 7 +- src/wallet.h | 2 +- 12 files changed, 750 insertions(+), 56 deletions(-) create mode 100644 src/rpcrawtransaction.cpp diff --git a/bitcoin-qt.pro b/bitcoin-qt.pro index 6bfeeff0..22f624d5 100644 --- a/bitcoin-qt.pro +++ b/bitcoin-qt.pro @@ -207,6 +207,7 @@ SOURCES += src/qt/bitcoin.cpp src/qt/bitcoingui.cpp \ src/bitcoinrpc.cpp \ src/rpcdump.cpp \ src/rpcnet.cpp \ + src/rpcrawtransaction.cpp \ src/qt/overviewpage.cpp \ src/qt/csvmodelwriter.cpp \ src/crypter.cpp \ diff --git a/src/bitcoinrpc.cpp b/src/bitcoinrpc.cpp index c9413a5b..adcf359c 100644 --- a/src/bitcoinrpc.cpp +++ b/src/bitcoinrpc.cpp @@ -46,10 +46,16 @@ static std::string strRPCUserColonPass; static int64 nWalletUnlockTime; static CCriticalSection cs_nWalletUnlockTime; -extern Value getconnectioncount(const Array& params, bool fHelp); +extern Value getconnectioncount(const Array& params, bool fHelp); // in rpcnet.cpp extern Value getpeerinfo(const Array& params, bool fHelp); -extern Value dumpprivkey(const Array& params, bool fHelp); +extern Value dumpprivkey(const Array& params, bool fHelp); // in rpcdump.cpp extern Value importprivkey(const Array& params, bool fHelp); +extern Value getrawtransaction(const Array& params, bool fHelp); // in rcprawtransaction.cpp +extern Value listunspent(const Array& params, bool fHelp); +extern Value createrawtransaction(const Array& params, bool fHelp); +extern Value decoderawtransaction(const Array& params, bool fHelp); +extern Value signrawtransaction(const Array& params, bool fHelp); +extern Value sendrawtransaction(const Array& params, bool fHelp); const Object emptyobj; @@ -159,7 +165,7 @@ HexBits(unsigned int nBits) return HexStr(BEGIN(uBits.cBits), END(uBits.cBits)); } -static std::string +std::string HelpRequiringPassphrase() { return pwalletMain->IsCrypted() @@ -167,7 +173,7 @@ HelpRequiringPassphrase() : ""; } -static inline void +void EnsureWalletIsUnlocked() { if (pwalletMain->IsLocked()) @@ -2048,44 +2054,6 @@ Value getblock(const Array& params, bool fHelp) return blockToJSON(block, pblockindex); } -Value sendrawtx(const Array& params, bool fHelp) -{ - if (fHelp || params.size() < 1 || params.size() > 1) - throw runtime_error( - "sendrawtx \n" - "Submits raw transaction (serialized, hex-encoded) to local node and network."); - - // parse hex string from parameter - vector txData(ParseHex(params[0].get_str())); - CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION); - CTransaction tx; - - // deserialize binary data stream - try { - ssData >> tx; - } - catch (std::exception &e) { - throw JSONRPCError(-22, "TX decode failed"); - } - - // push to local node - CTxDB txdb("r"); - if (!tx.AcceptToMemoryPool(txdb)) - throw JSONRPCError(-22, "TX rejected"); - - SyncWithWallets(tx, NULL, true); - - // relay to network - CInv inv(MSG_TX, tx.GetHash()); - RelayInventory(inv); - - return tx.GetHash().GetHex(); -} - - - - - @@ -2147,7 +2115,12 @@ static const CRPCCommand vRPCCommands[] = { "listsinceblock", &listsinceblock, false }, { "dumpprivkey", &dumpprivkey, false }, { "importprivkey", &importprivkey, false }, - { "sendrawtx", &sendrawtx, false }, + { "listunspent", &listunspent, false }, + { "getrawtransaction", &getrawtransaction, false }, + { "createrawtransaction", &createrawtransaction, false }, + { "decoderawtransaction", &decoderawtransaction, false }, + { "signrawtransaction", &signrawtransaction, false }, + { "sendrawtransaction", &sendrawtransaction, false }, }; CRPCTable::CRPCTable() @@ -3021,6 +2994,13 @@ Array RPCConvertValues(const std::string &strMethod, const std::vector 2) ConvertTo(params[2]); if (strMethod == "addmultisigaddress" && n > 0) ConvertTo(params[0]); if (strMethod == "addmultisigaddress" && n > 1) ConvertTo(params[1]); + if (strMethod == "listunspent" && n > 0) ConvertTo(params[0]); + if (strMethod == "listunspent" && n > 1) ConvertTo(params[1]); + if (strMethod == "getrawtransaction" && n > 1) ConvertTo(params[1]); + if (strMethod == "createrawtransaction" && n > 0) ConvertTo(params[0]); + if (strMethod == "createrawtransaction" && n > 1) ConvertTo(params[1]); + if (strMethod == "signrawtransaction" && n > 1) ConvertTo(params[1]); + if (strMethod == "signrawtransaction" && n > 2) ConvertTo(params[2]); return params; } diff --git a/src/makefile.linux-mingw b/src/makefile.linux-mingw index 5afb5c78..e7cbb470 100644 --- a/src/makefile.linux-mingw +++ b/src/makefile.linux-mingw @@ -61,6 +61,7 @@ OBJS= \ obj/bitcoinrpc.o \ obj/rpcdump.o \ obj/rpcnet.o \ + obj/rpcrawtransaction.o \ obj/script.o \ obj/sync.o \ obj/util.o \ diff --git a/src/makefile.mingw b/src/makefile.mingw index 907a15a3..e60b3201 100644 --- a/src/makefile.mingw +++ b/src/makefile.mingw @@ -58,6 +58,7 @@ OBJS= \ obj/bitcoinrpc.o \ obj/rpcdump.o \ obj/rpcnet.o \ + obj/rpcrawtransaction.o \ obj/script.o \ obj/sync.o \ obj/util.o \ diff --git a/src/makefile.osx b/src/makefile.osx index 44e648d6..f9f28267 100644 --- a/src/makefile.osx +++ b/src/makefile.osx @@ -85,6 +85,7 @@ OBJS= \ obj/bitcoinrpc.o \ obj/rpcdump.o \ obj/rpcnet.o \ + obj/rpcrawtransaction.o \ obj/script.o \ obj/sync.o \ obj/util.o \ diff --git a/src/makefile.unix b/src/makefile.unix index 6d9bf276..2784335a 100644 --- a/src/makefile.unix +++ b/src/makefile.unix @@ -105,6 +105,7 @@ OBJS= \ obj/bitcoinrpc.o \ obj/rpcdump.o \ obj/rpcnet.o \ + obj/rpcrawtransaction.o \ obj/script.o \ obj/sync.o \ obj/util.o \ diff --git a/src/rpcrawtransaction.cpp b/src/rpcrawtransaction.cpp new file mode 100644 index 00000000..0e8c8068 --- /dev/null +++ b/src/rpcrawtransaction.cpp @@ -0,0 +1,470 @@ +// Copyright (c) 2010 Satoshi Nakamoto +// Copyright (c) 2009-2012 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include "base58.h" +#include "bitcoinrpc.h" +#include "db.h" +#include "init.h" +#include "main.h" +#include "wallet.h" + +using namespace std; +using namespace boost; +using namespace boost::assign; +using namespace json_spirit; + +// These are all in bitcoinrpc.cpp: +extern Object JSONRPCError(int code, const string& message); +extern int64 AmountFromValue(const Value& value); +extern Value ValueFromAmount(int64 amount); +extern std::string HelpRequiringPassphrase(); +extern void EnsureWalletIsUnlocked(); + +void +ScriptPubKeyToJSON(const CScript& scriptPubKey, Object& out) +{ + txnouttype type; + vector addresses; + int nRequired; + + out.push_back(Pair("asm", scriptPubKey.ToString())); + out.push_back(Pair("hex", HexStr(scriptPubKey.begin(), scriptPubKey.end()))); + + if (!ExtractDestinations(scriptPubKey, type, addresses, nRequired)) + { + out.push_back(Pair("type", GetTxnOutputType(TX_NONSTANDARD))); + return; + } + + out.push_back(Pair("reqSigs", nRequired)); + out.push_back(Pair("type", GetTxnOutputType(type))); + + Array a; + BOOST_FOREACH(const CTxDestination& addr, addresses) + a.push_back(CBitcoinAddress(addr).ToString()); + out.push_back(Pair("addresses", a)); +} + +void +TxToJSON(const CTransaction& tx, const uint256 hashBlock, Object& entry) +{ + entry.push_back(Pair("txid", tx.GetHash().GetHex())); + entry.push_back(Pair("version", tx.nVersion)); + entry.push_back(Pair("locktime", (boost::int64_t)tx.nLockTime)); + Array vin; + BOOST_FOREACH(const CTxIn& txin, tx.vin) + { + Object in; + if (tx.IsCoinBase()) + in.push_back(Pair("coinbase", HexStr(txin.scriptSig.begin(), txin.scriptSig.end()))); + else + { + in.push_back(Pair("txid", txin.prevout.hash.GetHex())); + in.push_back(Pair("vout", (boost::int64_t)txin.prevout.n)); + Object o; + o.push_back(Pair("asm", txin.scriptSig.ToString())); + o.push_back(Pair("hex", HexStr(txin.scriptSig.begin(), txin.scriptSig.end()))); + in.push_back(Pair("scriptSig", o)); + } + in.push_back(Pair("sequence", (boost::int64_t)txin.nSequence)); + vin.push_back(in); + } + entry.push_back(Pair("vin", vin)); + Array vout; + for (int i = 0; i < tx.vout.size(); i++) + { + const CTxOut& txout = tx.vout[i]; + Object out; + out.push_back(Pair("value", ValueFromAmount(txout.nValue))); + out.push_back(Pair("n", i)); + Object o; + ScriptPubKeyToJSON(txout.scriptPubKey, o); + out.push_back(Pair("scriptPubKey", o)); + vout.push_back(out); + } + entry.push_back(Pair("vout", vout)); + + if (hashBlock != 0) + { + entry.push_back(Pair("blockhash", hashBlock.GetHex())); + map::iterator mi = mapBlockIndex.find(hashBlock); + if (mi != mapBlockIndex.end() && (*mi).second) + { + CBlockIndex* pindex = (*mi).second; + if (pindex->IsInMainChain()) + { + entry.push_back(Pair("confirmations", 1 + nBestHeight - pindex->nHeight)); + entry.push_back(Pair("time", (boost::int64_t)pindex->nTime)); + } + else + entry.push_back(Pair("confirmations", 0)); + } + } +} + +Value getrawtransaction(const Array& params, bool fHelp) +{ + if (fHelp || params.size() < 1 || params.size() > 2) + throw runtime_error( + "getrawtransaction [verbose=0]\n" + "If verbose=0, returns a string that is\n" + "serialized, hex-encoded data for .\n" + "If verbose is non-zero, returns an Object\n" + "with information about ."); + + uint256 hash; + hash.SetHex(params[0].get_str()); + + bool fVerbose = false; + if (params.size() > 1) + fVerbose = (params[1].get_int() != 0); + + CTransaction tx; + uint256 hashBlock = 0; + if (!GetTransaction(hash, tx, hashBlock)) + throw JSONRPCError(-5, "No information available about transaction"); + + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << tx; + string strHex = HexStr(ssTx.begin(), ssTx.end()); + + if (!fVerbose) + return strHex; + + Object result; + result.push_back(Pair("hex", strHex)); + TxToJSON(tx, hashBlock, result); + return result; +} + +Value listunspent(const Array& params, bool fHelp) +{ + if (fHelp || params.size() > 2) + throw runtime_error( + "listunspent [minconf=1] [maxconf=999999]\n" + "Returns array of unspent transaction outputs\n" + "with between minconf and maxconf (inclusive) confirmations.\n" + "Results are an array of Objects, each of which has:\n" + "{txid, vout, scriptPubKey, amount, confirmations}"); + + RPCTypeCheck(params, list_of(int_type)(int_type)); + + int nMinDepth = 1; + if (params.size() > 0) + nMinDepth = params[0].get_int(); + + int nMaxDepth = 999999; + if (params.size() > 1) + nMaxDepth = params[1].get_int(); + + Array results; + vector vecOutputs; + pwalletMain->AvailableCoins(vecOutputs, false); + BOOST_FOREACH(const COutput& out, vecOutputs) + { + if (out.nDepth < nMinDepth || out.nDepth > nMaxDepth) + continue; + + int64 nValue = out.tx->vout[out.i].nValue; + const CScript& pk = out.tx->vout[out.i].scriptPubKey; + Object entry; + entry.push_back(Pair("txid", out.tx->GetHash().GetHex())); + entry.push_back(Pair("vout", out.i)); + entry.push_back(Pair("scriptPubKey", HexStr(pk.begin(), pk.end()))); + entry.push_back(Pair("amount",ValueFromAmount(nValue))); + entry.push_back(Pair("confirmations",out.nDepth)); + results.push_back(entry); + } + + return results; +} + +Value createrawtransaction(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 2) + throw runtime_error( + "createrawtransaction [{\"txid\":txid,\"vout\":n},...] {address:amount,...}\n" + "Create a transaction spending given inputs\n" + "(array of objects containing transaction id and output number),\n" + "sending to given address(es).\n" + "Returns hex-encoded raw transaction.\n" + "Note that the transaction's inputs are not signed, and\n" + "it is not stored in the wallet or transmitted to the network."); + + RPCTypeCheck(params, list_of(array_type)(obj_type)); + + Array inputs = params[0].get_array(); + Object sendTo = params[1].get_obj(); + + CTransaction rawTx; + + BOOST_FOREACH(Value& input, inputs) + { + const Object& o = input.get_obj(); + + const Value& txid_v = find_value(o, "txid"); + if (txid_v.type() != str_type) + throw JSONRPCError(-8, "Invalid parameter, missing txid key"); + string txid = txid_v.get_str(); + if (!IsHex(txid)) + throw JSONRPCError(-8, "Invalid parameter, expected hex txid"); + + const Value& vout_v = find_value(o, "vout"); + if (vout_v.type() != int_type) + throw JSONRPCError(-8, "Invalid parameter, missing vout key"); + int nOutput = vout_v.get_int(); + if (nOutput < 0) + throw JSONRPCError(-8, "Invalid parameter, vout must be positive"); + + CTxIn in(COutPoint(uint256(txid), nOutput)); + rawTx.vin.push_back(in); + } + + set setAddress; + BOOST_FOREACH(const Pair& s, sendTo) + { + CBitcoinAddress address(s.name_); + if (!address.IsValid()) + throw JSONRPCError(-5, string("Invalid Bitcoin address:")+s.name_); + + if (setAddress.count(address)) + throw JSONRPCError(-8, string("Invalid parameter, duplicated address: ")+s.name_); + setAddress.insert(address); + + CScript scriptPubKey; + scriptPubKey.SetDestination(address.Get()); + int64 nAmount = AmountFromValue(s.value_); + + CTxOut out(nAmount, scriptPubKey); + rawTx.vout.push_back(out); + } + + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << rawTx; + return HexStr(ss.begin(), ss.end()); +} + +Value decoderawtransaction(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw runtime_error( + "decoderawtransaction \n" + "Return a JSON object representing the serialized, hex-encoded transaction."); + + RPCTypeCheck(params, list_of(str_type)); + + vector txData(ParseHex(params[0].get_str())); + CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION); + CTransaction tx; + try { + ssData >> tx; + } + catch (std::exception &e) { + throw JSONRPCError(-22, "TX decode failed"); + } + + Object result; + TxToJSON(tx, 0, result); + + return result; +} + +Value signrawtransaction(const Array& params, bool fHelp) +{ + if (fHelp || params.size() < 1 || params.size() > 3) + throw runtime_error( + "signrawtransaction [{\"txid\":txid,\"vout\":n,\"scriptPubKey\":hex},...] [,...]\n" + "Sign inputs for raw transaction (serialized, hex-encoded).\n" + "Second optional argument is an array of previous transaction outputs that\n" + "this transaction depends on but may not yet be in the blockchain.\n" + "Third optional argument is an array of base58-encoded private\n" + "keys that, if given, will be the only keys used to sign the transaction.\n" + "Returns json object with keys:\n" + " hex : raw transaction with signature(s) (hex-encoded string)\n" + " complete : 1 if transaction has a complete set of signature (0 if not)" + + HelpRequiringPassphrase()); + + if (params.size() < 3) + EnsureWalletIsUnlocked(); + + RPCTypeCheck(params, list_of(str_type)(array_type)(array_type)); + + vector txData(ParseHex(params[0].get_str())); + CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION); + vector txVariants; + while (!ssData.empty()) + { + try { + CTransaction tx; + ssData >> tx; + txVariants.push_back(tx); + } + catch (std::exception &e) { + throw JSONRPCError(-22, "TX decode failed"); + } + } + + if (txVariants.empty()) + throw JSONRPCError(-22, "Missing transaction"); + + // mergedTx will end up with all the signatures; it + // starts as a clone of the rawtx: + CTransaction mergedTx(txVariants[0]); + bool fComplete = true; + + // Fetch previous transactions (inputs): + map mapPrevOut; + { + MapPrevTx mapPrevTx; + CTxDB txdb("r"); + map unused; + bool fInvalid; + mergedTx.FetchInputs(txdb, unused, false, false, mapPrevTx, fInvalid); + + // Copy results into mapPrevOut: + BOOST_FOREACH(const CTxIn& txin, mergedTx.vin) + { + const uint256& prevHash = txin.prevout.hash; + if (mapPrevTx.count(prevHash)) + mapPrevOut[txin.prevout] = mapPrevTx[prevHash].second.vout[txin.prevout.n].scriptPubKey; + } + } + + // Add previous txouts given in the RPC call: + if (params.size() > 1) + { + Array prevTxs = params[1].get_array(); + BOOST_FOREACH(Value& p, prevTxs) + { + if (p.type() != obj_type) + throw JSONRPCError(-22, "expected object with {\"txid'\",\"vout\",\"scriptPubKey\"}"); + + Object prevOut = p.get_obj(); + + RPCTypeCheck(prevOut, map_list_of("txid", str_type)("vout", int_type)("scriptPubKey", str_type)); + + string txidHex = find_value(prevOut, "txid").get_str(); + if (!IsHex(txidHex)) + throw JSONRPCError(-22, "txid must be hexadecimal"); + uint256 txid; + txid.SetHex(txidHex); + + int nOut = find_value(prevOut, "vout").get_int(); + if (nOut < 0) + throw JSONRPCError(-22, "vout must be positive"); + + string pkHex = find_value(prevOut, "scriptPubKey").get_str(); + if (!IsHex(pkHex)) + throw JSONRPCError(-22, "scriptPubKey must be hexadecimal"); + vector pkData(ParseHex(pkHex)); + CScript scriptPubKey(pkData.begin(), pkData.end()); + + COutPoint outpoint(txid, nOut); + if (mapPrevOut.count(outpoint)) + { + // Complain if scriptPubKey doesn't match + if (mapPrevOut[outpoint] != scriptPubKey) + { + string err("Previous output scriptPubKey mismatch:\n"); + err = err + mapPrevOut[outpoint].ToString() + "\nvs:\n"+ + scriptPubKey.ToString(); + throw JSONRPCError(-22, err); + } + } + else + mapPrevOut[outpoint] = scriptPubKey; + } + } + + bool fGivenKeys = false; + CBasicKeyStore tempKeystore; + if (params.size() > 2) + { + fGivenKeys = true; + Array keys = params[2].get_array(); + BOOST_FOREACH(Value k, keys) + { + CBitcoinSecret vchSecret; + bool fGood = vchSecret.SetString(k.get_str()); + if (!fGood) + throw JSONRPCError(-5,"Invalid private key"); + CKey key; + bool fCompressed; + CSecret secret = vchSecret.GetSecret(fCompressed); + key.SetSecret(secret, fCompressed); + tempKeystore.AddKey(key); + } + } + const CKeyStore& keystore = (fGivenKeys ? tempKeystore : *pwalletMain); + + // Sign what we can: + for (int i = 0; i < mergedTx.vin.size(); i++) + { + CTxIn& txin = mergedTx.vin[i]; + if (mapPrevOut.count(txin.prevout) == 0) + { + fComplete = false; + continue; + } + const CScript& prevPubKey = mapPrevOut[txin.prevout]; + + txin.scriptSig.clear(); + SignSignature(keystore, prevPubKey, mergedTx, i); + + // ... and merge in other signatures: + BOOST_FOREACH(const CTransaction& txv, txVariants) + { + txin.scriptSig = CombineSignatures(prevPubKey, mergedTx, i, txin.scriptSig, txv.vin[i].scriptSig); + } + if (!VerifyScript(txin.scriptSig, prevPubKey, mergedTx, i, true, 0)) + fComplete = false; + } + + Object result; + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << mergedTx; + result.push_back(Pair("hex", HexStr(ssTx.begin(), ssTx.end()))); + result.push_back(Pair("complete", fComplete)); + + return result; +} + +Value sendrawtransaction(const Array& params, bool fHelp) +{ + if (fHelp || params.size() < 1 || params.size() > 1) + throw runtime_error( + "sendrawtransaction \n" + "Submits raw transaction (serialized, hex-encoded) to local node and network."); + + RPCTypeCheck(params, list_of(str_type)); + + // parse hex string from parameter + vector txData(ParseHex(params[0].get_str())); + CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION); + CTransaction tx; + + // deserialize binary data stream + try { + ssData >> tx; + } + catch (std::exception &e) { + throw JSONRPCError(-22, "TX decode failed"); + } + + // push to local node + CTxDB txdb("r"); + if (!tx.AcceptToMemoryPool(txdb)) + throw JSONRPCError(-22, "TX rejected"); + + SyncWithWallets(tx, NULL, true); + + // relay to network + CInv inv(MSG_TX, tx.GetHash()); + RelayInventory(inv); + + return tx.GetHash().GetHex(); +} diff --git a/src/script.cpp b/src/script.cpp index 1451747f..103fc0e4 100644 --- a/src/script.cpp +++ b/src/script.cpp @@ -1331,15 +1331,12 @@ bool SignN(const vector& multisigdata, const CKeyStore& keystore, uint2 { int nSigned = 0; int nRequired = multisigdata.front()[0]; - for (vector::const_iterator it = multisigdata.begin()+1; it != multisigdata.begin()+multisigdata.size()-1; it++) + for (int i = 1; i < multisigdata.size()-1 && nSigned < nRequired; i++) { - const valtype& pubkey = *it; + const valtype& pubkey = multisigdata[i]; CKeyID keyID = CPubKey(pubkey).GetID(); if (Sign1(keyID, keystore, hash, nHashType, scriptSigRet)) - { ++nSigned; - if (nSigned == nRequired) break; - } } return nSigned==nRequired; } @@ -1612,12 +1609,13 @@ bool SignSignature(const CKeyStore &keystore, const CScript& fromPubKey, CTransa // Recompute txn hash using subscript in place of scriptPubKey: uint256 hash2 = SignatureHash(subscript, txTo, nIn, nHashType); + txnouttype subType; - if (!Solver(keystore, subscript, hash2, nHashType, txin.scriptSig, subType)) - return false; - if (subType == TX_SCRIPTHASH) - return false; - txin.scriptSig << static_cast(subscript); // Append serialized subscript + bool fSolved = + Solver(keystore, subscript, hash2, nHashType, txin.scriptSig, subType) && subType != TX_SCRIPTHASH; + // Append serialized subscript whether or not it is completely signed: + txin.scriptSig << static_cast(subscript); + if (!fSolved) return false; } // Test solution @@ -1648,6 +1646,127 @@ bool VerifySignature(const CTransaction& txFrom, const CTransaction& txTo, unsig return VerifyScript(txin.scriptSig, txout.scriptPubKey, txTo, nIn, fValidatePayToScriptHash, nHashType); } +static CScript PushAll(const vector& values) +{ + CScript result; + BOOST_FOREACH(const valtype& v, values) + result << v; + return result; +} + +static CScript CombineMultisig(CScript scriptPubKey, const CTransaction& txTo, unsigned int nIn, + const vector& vSolutions, + vector& sigs1, vector& sigs2) +{ + // Combine all the signatures we've got: + set allsigs; + BOOST_FOREACH(const valtype& v, sigs1) + { + if (!v.empty()) + allsigs.insert(v); + } + BOOST_FOREACH(const valtype& v, sigs2) + { + if (!v.empty()) + allsigs.insert(v); + } + + // Build a map of pubkey -> signature by matching sigs to pubkeys: + int nSigsRequired = vSolutions.front()[0]; + int nPubKeys = vSolutions.size()-2; + map sigs; + BOOST_FOREACH(const valtype& sig, allsigs) + { + for (int i = 0; i < nPubKeys; i++) + { + const valtype& pubkey = vSolutions[i+1]; + if (sigs.count(pubkey)) + continue; // Already got a sig for this pubkey + + if (CheckSig(sig, pubkey, scriptPubKey, txTo, nIn, 0)) + { + sigs[pubkey] = sig; + break; + } + } + } + // Now build a merged CScript: + unsigned int nSigsHave = 0; + CScript result; result << OP_0; // pop-one-too-many workaround + for (int i = 0; i < nPubKeys && nSigsHave < nSigsRequired; i++) + { + if (sigs.count(vSolutions[i+1])) + { + result << sigs[vSolutions[i+1]]; + ++nSigsHave; + } + } + // Fill any missing with OP_0: + for (int i = nSigsHave; i < nSigsRequired; i++) + result << OP_0; + + return result; +} + +static CScript CombineSignatures(CScript scriptPubKey, const CTransaction& txTo, unsigned int nIn, + const txnouttype txType, const vector& vSolutions, + vector& sigs1, vector& sigs2) +{ + switch (txType) + { + case TX_NONSTANDARD: + // Don't know anything about this, assume bigger one is correct: + if (sigs1.size() >= sigs2.size()) + return PushAll(sigs1); + return PushAll(sigs2); + case TX_PUBKEY: + case TX_PUBKEYHASH: + // Signatures are bigger than placeholders or empty scripts: + if (sigs1.empty() || sigs1[0].empty()) + return PushAll(sigs2); + return PushAll(sigs1); + case TX_SCRIPTHASH: + if (sigs1.empty() || sigs1.back().empty()) + return PushAll(sigs2); + else if (sigs2.empty() || sigs2.back().empty()) + return PushAll(sigs1); + else + { + // Recurse to combine: + valtype spk = sigs1.back(); + CScript pubKey2(spk.begin(), spk.end()); + + txnouttype txType2; + vector > vSolutions2; + Solver(pubKey2, txType2, vSolutions2); + sigs1.pop_back(); + sigs2.pop_back(); + CScript result = CombineSignatures(pubKey2, txTo, nIn, txType2, vSolutions2, sigs1, sigs2); + result << spk; + return result; + } + case TX_MULTISIG: + return CombineMultisig(scriptPubKey, txTo, nIn, vSolutions, sigs1, sigs2); + } + + return CScript(); +} + +CScript CombineSignatures(CScript scriptPubKey, const CTransaction& txTo, unsigned int nIn, + const CScript& scriptSig1, const CScript& scriptSig2) +{ + txnouttype txType; + vector > vSolutions; + Solver(scriptPubKey, txType, vSolutions); + + vector stack1; + EvalScript(stack1, scriptSig1, CTransaction(), 0, 0); + vector stack2; + EvalScript(stack2, scriptSig2, CTransaction(), 0, 0); + + return CombineSignatures(scriptPubKey, txTo, nIn, txType, vSolutions, stack1, stack2); +} + unsigned int CScript::GetSigOpCount(bool fAccurate) const { unsigned int n = 0; diff --git a/src/script.h b/src/script.h index 38f34693..f4db112d 100644 --- a/src/script.h +++ b/src/script.h @@ -597,4 +597,8 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const C bool fValidatePayToScriptHash, int nHashType); bool VerifySignature(const CTransaction& txFrom, const CTransaction& txTo, unsigned int nIn, bool fValidatePayToScriptHash, int nHashType); +// Given two sets of signatures for scriptPubKey, possibly with OP_0 placeholders, +// combine them intelligently and return the result. +CScript CombineSignatures(CScript scriptPubKey, const CTransaction& txTo, unsigned int nIn, const CScript& scriptSig1, const CScript& scriptSig2); + #endif diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index a30e3ccb..61d9a64e 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -328,5 +328,118 @@ BOOST_AUTO_TEST_CASE(script_CHECKMULTISIG23) BOOST_CHECK(!VerifyScript(badsig6, scriptPubKey23, txTo23, 0, true, 0)); } +BOOST_AUTO_TEST_CASE(script_combineSigs) +{ + // Test the CombineSignatures function + CBasicKeyStore keystore; + vector keys; + for (int i = 0; i < 3; i++) + { + CKey key; + key.MakeNewKey(i%2 == 1); + keys.push_back(key); + keystore.AddKey(key); + } + + CTransaction txFrom; + txFrom.vout.resize(1); + txFrom.vout[0].scriptPubKey.SetDestination(keys[0].GetPubKey().GetID()); + CScript& scriptPubKey = txFrom.vout[0].scriptPubKey; + CTransaction txTo; + txTo.vin.resize(1); + txTo.vout.resize(1); + txTo.vin[0].prevout.n = 0; + txTo.vin[0].prevout.hash = txFrom.GetHash(); + CScript& scriptSig = txTo.vin[0].scriptSig; + txTo.vout[0].nValue = 1; + + CScript empty; + CScript combined = CombineSignatures(scriptPubKey, txTo, 0, empty, empty); + BOOST_CHECK(combined.empty()); + + // Single signature case: + SignSignature(keystore, txFrom, txTo, 0); // changes scriptSig + combined = CombineSignatures(scriptPubKey, txTo, 0, scriptSig, empty); + BOOST_CHECK(combined == scriptSig); + combined = CombineSignatures(scriptPubKey, txTo, 0, empty, scriptSig); + BOOST_CHECK(combined == scriptSig); + CScript scriptSigCopy = scriptSig; + // Signing again will give a different, valid signature: + SignSignature(keystore, txFrom, txTo, 0); + combined = CombineSignatures(scriptPubKey, txTo, 0, scriptSigCopy, scriptSig); + BOOST_CHECK(combined == scriptSigCopy || combined == scriptSig); + + // P2SH, single-signature case: + CScript pkSingle; pkSingle << keys[0].GetPubKey() << OP_CHECKSIG; + keystore.AddCScript(pkSingle); + scriptPubKey.SetDestination(pkSingle.GetID()); + SignSignature(keystore, txFrom, txTo, 0); + combined = CombineSignatures(scriptPubKey, txTo, 0, scriptSig, empty); + BOOST_CHECK(combined == scriptSig); + combined = CombineSignatures(scriptPubKey, txTo, 0, empty, scriptSig); + BOOST_CHECK(combined == scriptSig); + scriptSigCopy = scriptSig; + SignSignature(keystore, txFrom, txTo, 0); + combined = CombineSignatures(scriptPubKey, txTo, 0, scriptSigCopy, scriptSig); + BOOST_CHECK(combined == scriptSigCopy || combined == scriptSig); + // dummy scriptSigCopy with placeholder, should always choose non-placeholder: + scriptSigCopy = CScript() << OP_0 << static_cast >(pkSingle); + combined = CombineSignatures(scriptPubKey, txTo, 0, scriptSigCopy, scriptSig); + BOOST_CHECK(combined == scriptSig); + combined = CombineSignatures(scriptPubKey, txTo, 0, scriptSig, scriptSigCopy); + BOOST_CHECK(combined == scriptSig); + + // Hardest case: Multisig 2-of-3 + scriptPubKey.SetMultisig(2, keys); + keystore.AddCScript(scriptPubKey); + SignSignature(keystore, txFrom, txTo, 0); + combined = CombineSignatures(scriptPubKey, txTo, 0, scriptSig, empty); + BOOST_CHECK(combined == scriptSig); + combined = CombineSignatures(scriptPubKey, txTo, 0, empty, scriptSig); + BOOST_CHECK(combined == scriptSig); + + // A couple of partially-signed versions: + vector sig1; + uint256 hash1 = SignatureHash(scriptPubKey, txTo, 0, SIGHASH_ALL); + BOOST_CHECK(keys[0].Sign(hash1, sig1)); + sig1.push_back(SIGHASH_ALL); + vector sig2; + uint256 hash2 = SignatureHash(scriptPubKey, txTo, 0, SIGHASH_NONE); + BOOST_CHECK(keys[1].Sign(hash2, sig2)); + sig2.push_back(SIGHASH_NONE); + vector sig3; + uint256 hash3 = SignatureHash(scriptPubKey, txTo, 0, SIGHASH_SINGLE); + BOOST_CHECK(keys[2].Sign(hash3, sig3)); + sig3.push_back(SIGHASH_SINGLE); + + // Not fussy about order (or even existence) of placeholders or signatures: + CScript partial1a = CScript() << OP_0 << sig1 << OP_0; + CScript partial1b = CScript() << OP_0 << OP_0 << sig1; + CScript partial2a = CScript() << OP_0 << sig2; + CScript partial2b = CScript() << sig2 << OP_0; + CScript partial3a = CScript() << sig3; + CScript partial3b = CScript() << OP_0 << OP_0 << sig3; + CScript partial3c = CScript() << OP_0 << sig3 << OP_0; + CScript complete12 = CScript() << OP_0 << sig1 << sig2; + CScript complete13 = CScript() << OP_0 << sig1 << sig3; + CScript complete23 = CScript() << OP_0 << sig2 << sig3; + + combined = CombineSignatures(scriptPubKey, txTo, 0, partial1a, partial1b); + BOOST_CHECK(combined == partial1a); + combined = CombineSignatures(scriptPubKey, txTo, 0, partial1a, partial2a); + BOOST_CHECK(combined == complete12); + combined = CombineSignatures(scriptPubKey, txTo, 0, partial2a, partial1a); + BOOST_CHECK(combined == complete12); + combined = CombineSignatures(scriptPubKey, txTo, 0, partial1b, partial2b); + BOOST_CHECK(combined == complete12); + combined = CombineSignatures(scriptPubKey, txTo, 0, partial3b, partial1b); + BOOST_CHECK(combined == complete13); + combined = CombineSignatures(scriptPubKey, txTo, 0, partial2a, partial3a); + BOOST_CHECK(combined == complete23); + combined = CombineSignatures(scriptPubKey, txTo, 0, partial3b, partial2b); + BOOST_CHECK(combined == complete23); + combined = CombineSignatures(scriptPubKey, txTo, 0, partial3b, partial3a); + BOOST_CHECK(combined == partial3c); +} BOOST_AUTO_TEST_SUITE_END() diff --git a/src/wallet.cpp b/src/wallet.cpp index 127d5808..4d99ce65 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -899,7 +899,7 @@ int64 CWallet::GetImmatureBalance() const } // populate vCoins with vector of spendable COutputs -void CWallet::AvailableCoins(vector& vCoins) const +void CWallet::AvailableCoins(vector& vCoins, bool fOnlyConfirmed) const { vCoins.clear(); @@ -909,7 +909,10 @@ void CWallet::AvailableCoins(vector& vCoins) const { const CWalletTx* pcoin = &(*it).second; - if (!pcoin->IsFinal() || !pcoin->IsConfirmed()) + if (!pcoin->IsFinal()) + continue; + + if (fOnlyConfirmed && !pcoin->IsConfirmed()) continue; if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0) diff --git a/src/wallet.h b/src/wallet.h index 9807ecec..5bf38699 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -61,7 +61,6 @@ public: class CWallet : public CCryptoKeyStore { private: - void AvailableCoins(std::vector& vCoins) const; bool SelectCoins(int64 nTargetValue, std::set >& setCoinsRet, int64& nValueRet) const; CWalletDB *pwalletdbEncryption; @@ -113,6 +112,7 @@ public: // check whether we are allowed to upgrade (or already support) to the named feature bool CanSupportFeature(enum WalletFeature wf) { return nWalletMaxVersion >= wf; } + void AvailableCoins(std::vector& vCoins, bool fOnlyConfirmed=true) const; bool SelectCoinsMinConf(int64 nTargetValue, int nConfMine, int nConfTheirs, std::vector vCoins, std::set >& setCoinsRet, int64& nValueRet) const; // keystore implementation