Browse Source

Implement raw transaction RPC calls

Implement listunspent / getrawtransaction / createrawtransaction /
signrawtransaction, to support creation and
signing-on-multiple-device multisignature transactions.
tags/v0.15.1
Gavin Andresen 9 years ago
parent
commit
a2709fad7f
12 changed files with 750 additions and 56 deletions
  1. 1
    0
      bitcoin-qt.pro
  2. 23
    43
      src/bitcoinrpc.cpp
  3. 1
    0
      src/makefile.linux-mingw
  4. 1
    0
      src/makefile.mingw
  5. 1
    0
      src/makefile.osx
  6. 1
    0
      src/makefile.unix
  7. 470
    0
      src/rpcrawtransaction.cpp
  8. 129
    10
      src/script.cpp
  9. 4
    0
      src/script.h
  10. 113
    0
      src/test/script_tests.cpp
  11. 5
    2
      src/wallet.cpp
  12. 1
    1
      src/wallet.h

+ 1
- 0
bitcoin-qt.pro View File

@@ -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 \

+ 23
- 43
src/bitcoinrpc.cpp View File

@@ -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 <hex string>\n"
"Submits raw transaction (serialized, hex-encoded) to local node and network.");

// parse hex string from parameter
vector<unsigned char> 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<std::stri
if (strMethod == "sendmany" && n > 2) ConvertTo<boost::int64_t>(params[2]);
if (strMethod == "addmultisigaddress" && n > 0) ConvertTo<boost::int64_t>(params[0]);
if (strMethod == "addmultisigaddress" && n > 1) ConvertTo<Array>(params[1]);
if (strMethod == "listunspent" && n > 0) ConvertTo<boost::int64_t>(params[0]);
if (strMethod == "listunspent" && n > 1) ConvertTo<boost::int64_t>(params[1]);
if (strMethod == "getrawtransaction" && n > 1) ConvertTo<boost::int64_t>(params[1]);
if (strMethod == "createrawtransaction" && n > 0) ConvertTo<Array>(params[0]);
if (strMethod == "createrawtransaction" && n > 1) ConvertTo<Object>(params[1]);
if (strMethod == "signrawtransaction" && n > 1) ConvertTo<Array>(params[1]);
if (strMethod == "signrawtransaction" && n > 2) ConvertTo<Array>(params[2]);

return params;
}

+ 1
- 0
src/makefile.linux-mingw View File

@@ -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 \

+ 1
- 0
src/makefile.mingw View File

@@ -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 \

+ 1
- 0
src/makefile.osx View File

@@ -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 \

+ 1
- 0
src/makefile.unix View File

@@ -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 \

+ 470
- 0
src/rpcrawtransaction.cpp View File

@@ -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 <boost/assign/list_of.hpp>

#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<CTxDestination> 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<uint256, CBlockIndex*>::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 <txid> [verbose=0]\n"
"If verbose=0, returns a string that is\n"
"serialized, hex-encoded data for <txid>.\n"
"If verbose is non-zero, returns an Object\n"
"with information about <txid>.");

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<COutput> 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<CBitcoinAddress> 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 <hex string>\n"
"Return a JSON object representing the serialized, hex-encoded transaction.");

RPCTypeCheck(params, list_of(str_type));

vector<unsigned char> 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 <hex string> [{\"txid\":txid,\"vout\":n,\"scriptPubKey\":hex},...] [<privatekey1>,...]\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<unsigned char> txData(ParseHex(params[0].get_str()));
CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION);
vector<CTransaction> 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<COutPoint, CScript> mapPrevOut;
{
MapPrevTx mapPrevTx;
CTxDB txdb("r");
map<uint256, CTxIndex> 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<unsigned char> 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 <hex string>\n"
"Submits raw transaction (serialized, hex-encoded) to local node and network.");

RPCTypeCheck(params, list_of(str_type));

// parse hex string from parameter
vector<unsigned char> 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();
}

+ 129
- 10
src/script.cpp View File

@@ -1331,15 +1331,12 @@ bool SignN(const vector<valtype>& multisigdata, const CKeyStore& keystore, uint2
{
int nSigned = 0;
int nRequired = multisigdata.front()[0];
for (vector<valtype>::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<valtype>(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<valtype>(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<valtype>& 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<valtype>& vSolutions,
vector<valtype>& sigs1, vector<valtype>& sigs2)
{
// Combine all the signatures we've got:
set<valtype> 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<valtype, valtype> 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<valtype>& vSolutions,
vector<valtype>& sigs1, vector<valtype>& 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<vector<unsigned char> > 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<vector<unsigned char> > vSolutions;
Solver(scriptPubKey, txType, vSolutions);

vector<valtype> stack1;
EvalScript(stack1, scriptSig1, CTransaction(), 0, 0);
vector<valtype> 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;

+ 4
- 0
src/script.h View File

@@ -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

+ 113
- 0
src/test/script_tests.cpp View File

@@ -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<CKey> 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<vector<unsigned char> >(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<unsigned char> sig1;
uint256 hash1 = SignatureHash(scriptPubKey, txTo, 0, SIGHASH_ALL);
BOOST_CHECK(keys[0].Sign(hash1, sig1));
sig1.push_back(SIGHASH_ALL);
vector<unsigned char> sig2;
uint256 hash2 = SignatureHash(scriptPubKey, txTo, 0, SIGHASH_NONE);
BOOST_CHECK(keys[1].Sign(hash2, sig2));
sig2.push_back(SIGHASH_NONE);
vector<unsigned char> 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()

+ 5
- 2
src/wallet.cpp View File

@@ -899,7 +899,7 @@ int64 CWallet::GetImmatureBalance() const
}

// populate vCoins with vector of spendable COutputs
void CWallet::AvailableCoins(vector<COutput>& vCoins) const
void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed) const
{
vCoins.clear();

@@ -909,7 +909,10 @@ void CWallet::AvailableCoins(vector<COutput>& 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)

+ 1
- 1
src/wallet.h View File

@@ -61,7 +61,6 @@ public:
class CWallet : public CCryptoKeyStore
{
private:
void AvailableCoins(std::vector<COutput>& vCoins) const;
bool SelectCoins(int64 nTargetValue, std::set<std::pair<const CWalletTx*,unsigned int> >& 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<COutput>& vCoins, bool fOnlyConfirmed=true) const;
bool SelectCoinsMinConf(int64 nTargetValue, int nConfMine, int nConfTheirs, std::vector<COutput> vCoins, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, int64& nValueRet) const;

// keystore implementation

Loading…
Cancel
Save