From e679ec969c8b22c676ebb10bea1038f6c8f13b33 Mon Sep 17 00:00:00 2001 From: Gavin Andresen Date: Mon, 3 Oct 2011 13:05:43 -0400 Subject: [PATCH] OP_EVAL implementation OP_EVAL is a new opcode that evaluates an item on the stack as a script. It enables a new type of bitcoin address that needs an arbitrarily complex script to redeem. --- src/base58.h | 25 ++ src/bitcoinrpc.cpp | 122 +++--- src/db.cpp | 9 + src/db.h | 12 + src/keystore.cpp | 30 ++ src/keystore.h | 9 + src/main.cpp | 230 ++++++++--- src/main.h | 36 +- src/script.cpp | 656 ++++++++++++++++++++++-------- src/script.h | 222 +++------- src/test/multisig_tests.cpp | 99 ++--- src/test/script_op_eval_tests.cpp | 203 +++++++++ src/test/script_tests.cpp | 40 +- src/wallet.cpp | 22 +- src/wallet.h | 12 +- 15 files changed, 1155 insertions(+), 572 deletions(-) create mode 100644 src/test/script_op_eval_tests.cpp diff --git a/src/base58.h b/src/base58.h index 113fa14a..8fc546c5 100644 --- a/src/base58.h +++ b/src/base58.h @@ -268,6 +268,12 @@ public: SetHash160(Hash160(vchPubKey)); } + bool SetScriptHash160(const uint160& hash160) + { + SetData(fTestNet ? 112 : 1, &hash160, 20); + return true; + } + bool IsValid() const { int nExpectedSize = 20; @@ -275,9 +281,20 @@ public: switch(nVersion) { case 0: + nExpectedSize = 20; // Hash of public key + fExpectTestNet = false; + break; + case 1: + nExpectedSize = 20; // OP_EVAL, hash of CScript + fExpectTestNet = false; break; case 111: + nExpectedSize = 20; + fExpectTestNet = true; + break; + case 112: + nExpectedSize = 20; fExpectTestNet = true; break; @@ -286,6 +303,14 @@ public: } return fExpectTestNet == fTestNet && vchData.size() == nExpectedSize; } + bool IsScript() const + { + if (!IsValid()) + return false; + if (fTestNet) + return nVersion == 112; + return nVersion == 1; + } CBitcoinAddress() { diff --git a/src/bitcoinrpc.cpp b/src/bitcoinrpc.cpp index 122bf61b..3e42e703 100644 --- a/src/bitcoinrpc.cpp +++ b/src/bitcoinrpc.cpp @@ -667,7 +667,7 @@ Value getreceivedbyaccount(const Array& params, bool fHelp) if (params.size() > 1) nMinDepth = params[1].get_int(); - // Get the set of pub keys that have the label + // Get the set of pub keys assigned to account string strAccount = AccountFromValue(params[0]); set setAddress; GetAccountAddresses(strAccount, setAddress); @@ -936,56 +936,30 @@ Value sendmany(const Array& params, bool fHelp) return wtx.GetHash().GetHex(); } -Value sendmultisig(const Array& params, bool fHelp) +Value addmultisigaddress(const Array& params, bool fHelp) { - if (fHelp || params.size() < 4 || params.size() > 7) + if (fHelp || params.size() < 2 || params.size() > 3) { - string msg = "sendmultisig <[\"key\",\"key\"]> [minconf=1] [comment] [comment-to]\n" - " is one of: \"and\", \"or\", \"escrow\"\n" - " is an array of strings (in JSON array format); each key is a bitcoin address, hex or base58 public key\n" - " is a real and is rounded to the nearest 0.00000001"; - if (pwalletMain->IsCrypted()) - msg += "\nrequires wallet passphrase to be set with walletpassphrase first"; + string msg = "addmultisigaddress <'[\"key\",\"key\"]'> [account]\n" + "Add a nrequired-to-sign multisignature address to the wallet\"\n" + "each key is a bitcoin address, hex or base58 public key\n" + "If [account] is specified, assign address to [account]."; throw runtime_error(msg); } - string strAccount = AccountFromValue(params[0]); - string strType = params[1].get_str(); - const Array& keys = params[2].get_array(); - int64 nAmount = AmountFromValue(params[3]); - int nMinDepth = 1; - if (params.size() > 4) - nMinDepth = params[4].get_int(); - - CWalletTx wtx; - wtx.strFromAccount = strAccount; - if (params.size() > 5 && params[5].type() != null_type && !params[5].get_str().empty()) - wtx.mapValue["comment"] = params[5].get_str(); - if (params.size() > 6 && params[6].type() != null_type && !params[6].get_str().empty()) - wtx.mapValue["to"] = params[6].get_str(); - - if (pwalletMain->IsLocked()) - throw JSONRPCError(-13, "Error: Please enter the wallet passphrase with walletpassphrase first."); - - // Check funds - int64 nBalance = GetAccountBalance(strAccount, nMinDepth); - if (nAmount > nBalance) - throw JSONRPCError(-6, "Account has insufficient funds"); + int nRequired = params[0].get_int(); + const Array& keys = params[1].get_array(); + string strAccount; + if (params.size() > 2) + strAccount = AccountFromValue(params[2]); // Gather public keys - int nKeysNeeded = 0; - if (strType == "and" || strType == "or") - nKeysNeeded = 2; - else if (strType == "escrow") - nKeysNeeded = 3; - else - throw runtime_error("sendmultisig: must be one of: and or and_or"); - if (keys.size() != nKeysNeeded) + if (keys.size() < nRequired) throw runtime_error( - strprintf("sendmultisig: wrong number of keys (got %d, need %d)", keys.size(), nKeysNeeded)); + strprintf("addmultisigaddress: wrong number of keys (got %d, need at least %d)", keys.size(), nRequired)); std::vector pubkeys; - pubkeys.resize(nKeysNeeded); - for (int i = 0; i < nKeysNeeded; i++) + pubkeys.resize(keys.size()); + for (int i = 0; i < keys.size(); i++) { const std::string& ks = keys[i].get_str(); if (ks.size() == 130) // hex public key @@ -1003,32 +977,23 @@ Value sendmultisig(const Array& params, bool fHelp) CBitcoinAddress address(ks); if (!pwalletMain->GetKey(address, pubkeys[i])) throw runtime_error( - strprintf("sendmultisig: unknown address: %s",ks.c_str())); + strprintf("addmultisigaddress: unknown address: %s",ks.c_str())); } } - // Send - CScript scriptPubKey; - if (strType == "and") - scriptPubKey.SetMultisigAnd(pubkeys); - else if (strType == "or") - scriptPubKey.SetMultisigOr(pubkeys); - else - scriptPubKey.SetMultisigEscrow(pubkeys); + // Construct using OP_EVAL + CScript inner; + inner.SetMultisig(nRequired, pubkeys); - CReserveKey keyChange(pwalletMain); - int64 nFeeRequired = 0; - bool fCreated = pwalletMain->CreateTransaction(scriptPubKey, nAmount, wtx, keyChange, nFeeRequired); - if (!fCreated) - { - if (nAmount + nFeeRequired > pwalletMain->GetBalance()) - throw JSONRPCError(-6, "Insufficient funds"); - throw JSONRPCError(-4, "Transaction creation failed"); - } - if (!pwalletMain->CommitTransaction(wtx, keyChange)) - throw JSONRPCError(-4, "Transaction commit failed"); + uint160 scriptHash = Hash160(inner); + CScript scriptPubKey; + scriptPubKey.SetEval(inner); + pwalletMain->AddCScript(scriptHash, inner); + CBitcoinAddress address; + address.SetScriptHash160(scriptHash); - return wtx.GetHash().GetHex(); + pwalletMain->SetAddressBookName(address, strAccount); + return address.ToString(); } @@ -1700,6 +1665,24 @@ Value validateaddress(const Array& params, bool fHelp) std::string strPubKey(vchPubKey.begin(), vchPubKey.end()); ret.push_back(Pair("pubkey58", EncodeBase58(vchPubKey))); } + else if (pwalletMain->HaveCScript(address.GetHash160())) + { + ret.push_back(Pair("isscript", true)); + CScript subscript; + pwalletMain->GetCScript(address.GetHash160(), subscript); + ret.push_back(Pair("ismine", ::IsMine(*pwalletMain, subscript))); + std::vector addresses; + txntype whichType; + int nRequired; + ExtractAddresses(subscript, pwalletMain, whichType, addresses, nRequired); + ret.push_back(Pair("script", GetTxnTypeName(whichType))); + Array a; + BOOST_FOREACH(const CBitcoinAddress& addr, addresses) + a.push_back(addr.ToString()); + ret.push_back(Pair("addresses", a)); + if (whichType == TX_MULTISIG) + ret.push_back(Pair("sigsrequired", nRequired)); + } else ret.push_back(Pair("ismine", false)); if (pwalletMain->mapAddressBook.count(address)) @@ -1946,7 +1929,7 @@ pair pCallTable[] = make_pair("move", &movecmd), make_pair("sendfrom", &sendfrom), make_pair("sendmany", &sendmany), - make_pair("sendmultisig", &sendmultisig), + make_pair("addmultisigaddress", &addmultisigaddress), make_pair("gettransaction", &gettransaction), make_pair("listtransactions", &listtransactions), make_pair("signmessage", &signmessage), @@ -2590,16 +2573,15 @@ int CommandLineRPC(int argc, char *argv[]) params[1] = v.get_obj(); } if (strMethod == "sendmany" && n > 2) ConvertTo(params[2]); - if (strMethod == "sendmultisig" && n > 2) + if (strMethod == "addmultisigaddress" && n > 0) ConvertTo(params[0]); + if (strMethod == "addmultisigaddress" && n > 1) { - string s = params[2].get_str(); + string s = params[1].get_str(); Value v; if (!read_string(s, v) || v.type() != array_type) - throw runtime_error("sendmultisig: type mismatch "+s); - params[2] = v.get_array(); + throw runtime_error("addmultisigaddress: type mismatch "+s); + params[1] = v.get_array(); } - if (strMethod == "sendmultisig" && n > 3) ConvertTo(params[3]); - if (strMethod == "sendmultisig" && n > 4) ConvertTo(params[4]); // Execute Object reply = CallRPC(strMethod, params); diff --git a/src/db.cpp b/src/db.cpp index 7195e065..52ad695c 100644 --- a/src/db.cpp +++ b/src/db.cpp @@ -934,6 +934,15 @@ int CWalletDB::LoadWallet(CWallet* pwallet) if (nMinVersion > CLIENT_VERSION) return DB_TOO_NEW; } + else if (strType == "cscript") + { + uint160 hash; + ssKey >> hash; + std::vector script; + ssValue >> script; + if (!pwallet->LoadCScript(hash, script)) + return DB_CORRUPT; + } } pcursor->close(); } diff --git a/src/db.h b/src/db.h index 15bfb29c..99dd88b4 100644 --- a/src/db.h +++ b/src/db.h @@ -420,6 +420,18 @@ public: return Write(std::make_pair(std::string("mkey"), nID), kMasterKey, true); } + bool ReadCScript(const uint160 &hash, std::vector& data) + { + data.clear(); + return Read(std::make_pair(std::string("cscript"), hash), data); + } + + bool WriteCScript(const uint160& hash, const std::vector& data) + { + nWalletDBUpdated++; + return Write(std::make_pair(std::string("cscript"), hash), data, false); + } + bool WriteBestBlock(const CBlockLocator& locator) { nWalletDBUpdated++; diff --git a/src/keystore.cpp b/src/keystore.cpp index 6cf557fa..c9b9b4a5 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -33,6 +33,36 @@ bool CBasicKeyStore::AddKey(const CKey& key) return true; } +bool CBasicKeyStore::AddCScript(const uint160 &hash, const std::vector& data) +{ + CRITICAL_BLOCK(cs_KeyStore) + mapData[hash] = data; + return true; +} + +bool CBasicKeyStore::HaveCScript(const uint160& hash) const +{ + bool result; + CRITICAL_BLOCK(cs_KeyStore) + result = (mapData.count(hash) > 0); + return result; +} + + +bool CBasicKeyStore::GetCScript(const uint160 &hash, std::vector& dataOut) const +{ + CRITICAL_BLOCK(cs_KeyStore) + { + DataMap::const_iterator mi = mapData.find(hash); + if (mi != mapData.end()) + { + dataOut = (*mi).second; + return true; + } + } + return false; +} + bool CCryptoKeyStore::SetCrypted() { CRITICAL_BLOCK(cs_KeyStore) diff --git a/src/keystore.h b/src/keystore.h index 3b91de6f..942fb9ae 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -31,6 +31,10 @@ public: virtual void GetKeys(std::set &setAddress) const =0; virtual bool GetPubKey(const CBitcoinAddress &address, std::vector& vchPubKeyOut) const; + virtual bool AddCScript(const uint160 &hash, const std::vector& data) =0; + virtual bool HaveCScript(const uint160 &hash) const =0; + virtual bool GetCScript(const uint160 &hash, std::vector& dataOut) const =0; + // Generate a new key, and add it to the store virtual std::vector GenerateNewKey(); virtual bool GetSecret(const CBitcoinAddress &address, CSecret& vchSecret) const @@ -44,12 +48,14 @@ public: }; typedef std::map KeyMap; +typedef std::map > DataMap; // Basic key store, that keeps keys in an address->secret map class CBasicKeyStore : public CKeyStore { protected: KeyMap mapKeys; + DataMap mapData; public: bool AddKey(const CKey& key); @@ -86,6 +92,9 @@ public: } return false; } + virtual bool AddCScript(const uint160 &hash, const std::vector& data); + virtual bool HaveCScript(const uint160 &hash) const; + virtual bool GetCScript(const uint160 &hash, std::vector& dataOut) const; }; typedef std::map, std::vector > > CryptedKeyMap; diff --git a/src/main.cpp b/src/main.cpp index 052701e5..0fb383f5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -246,6 +246,65 @@ bool CTransaction::ReadFromDisk(COutPoint prevout) return ReadFromDisk(txdb, prevout, txindex); } +bool CTransaction::IsStandard() const +{ + BOOST_FOREACH(const CTxIn& txin, vin) + { + // Biggest 'standard' txin is a 2-signature 2-of-3 escrow + // in an OP_EVAL, which is 2 ~80-byte signatures, 3 + // ~65-byte public keys, plus a few script ops. + if (txin.scriptSig.size() > 400) + return error("nonstandard txin, size %d\n", txin.scriptSig.size()); + if (!txin.scriptSig.IsPushOnly()) + return error("nonstandard txin: %s", txin.scriptSig.ToString().c_str()); + } + BOOST_FOREACH(const CTxOut& txout, vout) + if (!::IsStandard(txout.scriptPubKey)) + return error("nonstandard txout: %s", txout.scriptPubKey.ToString().c_str()); + return true; +} + +// +// Check transaction inputs, and make sure any +// OP_EVAL transactions are evaluating IsStandard scripts +// +// Why bother? To avoid denial-of-service attacks; an attacker +// can submit a standard DUP HASH... OP_EVAL transaction, +// which will get accepted into blocks. The script being +// EVAL'ed can be anything; an attacker could use a very +// expensive-to-check-upon-redemption script like: +// DUP CHECKSIG DROP ... repeated 100 times... OP_1 +// +bool CTransaction::IsStandardInputs(std::map > mapInputs) const +{ + if (fTestNet) + return true; // Allow non-standard on testnet + + for (int i = 0; i < vin.size(); i++) + { + COutPoint prevout = vin[i].prevout; + assert(mapInputs.count(prevout.hash) > 0); + CTransaction& txPrev = mapInputs[prevout.hash].second; + + vector > vSolutions; + txntype whichType; + if (!Solver(txPrev.vout[vin[i].prevout.n].scriptPubKey, whichType, vSolutions)) + return false; + if (whichType == TX_SCRIPTHASH) + { + vector > stack; + int nUnused; + if (!EvalScript(stack, vin[i].scriptSig, *this, i, 0, nUnused)) + return false; + const vector& subscript = stack.back(); + if (!::IsStandard(CScript(subscript.begin(), subscript.end()))) + return false; + } + } + + return true; +} + int CMerkleTx::SetMerkleBranch(const CBlock* pblock) @@ -369,15 +428,6 @@ bool CTransaction::AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs, bool* pfMi if ((int64)nLockTime > INT_MAX) return error("AcceptToMemoryPool() : not accepting nLockTime beyond 2038 yet"); - // Safety limits - unsigned int nSize = ::GetSerializeSize(*this, SER_NETWORK); - // Checking ECDSA signatures is a CPU bottleneck, so to avoid denial-of-service - // attacks disallow transactions with more than one SigOp per 34 bytes. - // 34 bytes because a TxOut is: - // 20-byte address + 8 byte bitcoin amount + 5 bytes of ops + 1 byte script length - if (GetSigOpCount() > nSize / 34 || nSize < 100) - return error("AcceptToMemoryPool() : transaction with out-of-bounds SigOpCount"); - // Rather not work on nonstandard transactions (unless -testnet) if (!fTestNet && !IsStandard()) return error("AcceptToMemoryPool() : nonstandard transaction type"); @@ -421,15 +471,34 @@ bool CTransaction::AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs, bool* pfMi if (fCheckInputs) { - // Check against previous transactions + map > mapInputs; map mapUnused; + if (!FetchInputs(txdb, mapUnused, false, false, mapInputs)) + { + if (pfMissingInputs) + *pfMissingInputs = true; + return error("AcceptToMemoryPool() : FetchInputs failed %s", hash.ToString().substr(0,10).c_str()); + } + + // Check for non-standard OP_EVALs in inputs + if (!IsStandardInputs(mapInputs)) + return error("AcceptToMemoryPool() : nonstandard transaction input"); + + // Check against previous transactions int64 nFees = 0; - if (!ConnectInputs(txdb, mapUnused, CDiskTxPos(1,1,1), pindexBest, nFees, false, false)) + int nSigOps = 0; + if (!ConnectInputs(mapInputs, mapUnused, CDiskTxPos(1,1,1), pindexBest, nFees, false, false, nSigOps)) { if (pfMissingInputs) *pfMissingInputs = true; return error("AcceptToMemoryPool() : ConnectInputs failed %s", hash.ToString().substr(0,10).c_str()); } + // Checking ECDSA signatures is a CPU bottleneck, so to avoid denial-of-service + // attacks disallow transactions with more than one SigOp per 65 bytes. + // 65 bytes because that is the minimum size of an ECDSA signature + unsigned int nSize = ::GetSerializeSize(*this, SER_NETWORK); + if (nSigOps > nSize / 65 || nSize < 100) + return error("AcceptToMemoryPool() : transaction with out-of-bounds SigOpCount"); // Don't accept it if it can't get into a block if (nFees < GetMinFee(1000, true, true)) @@ -826,8 +895,61 @@ bool CTransaction::DisconnectInputs(CTxDB& txdb) } -bool CTransaction::ConnectInputs(CTxDB& txdb, map& mapTestPool, CDiskTxPos posThisTx, - CBlockIndex* pindexBlock, int64& nFees, bool fBlock, bool fMiner, int64 nMinFee) +bool CTransaction::FetchInputs(CTxDB& txdb, const map& mapTestPool, + bool fBlock, bool fMiner, map >& inputsRet) +{ + if (IsCoinBase()) + return true; // Coinbase transactions have no inputs to fetch. + + for (int i = 0; i < vin.size(); i++) + { + COutPoint prevout = vin[i].prevout; + if (inputsRet.count(prevout.hash)) + continue; // Got it already + + // Read txindex + CTxIndex& txindex = inputsRet[prevout.hash].first; + bool fFound = true; + if ((fBlock || fMiner) && mapTestPool.count(prevout.hash)) + { + // Get txindex from current proposed changes + txindex = mapTestPool.find(prevout.hash)->second; + } + else + { + // Read txindex from txdb + fFound = txdb.ReadTxIndex(prevout.hash, txindex); + } + if (!fFound && (fBlock || fMiner)) + return fMiner ? false : error("FetchInputs() : %s prev tx %s index entry not found", GetHash().ToString().substr(0,10).c_str(), prevout.hash.ToString().substr(0,10).c_str()); + + // Read txPrev + CTransaction& txPrev = inputsRet[prevout.hash].second; + if (!fFound || txindex.pos == CDiskTxPos(1,1,1)) + { + // Get prev tx from single transactions in memory + CRITICAL_BLOCK(cs_mapTransactions) + { + if (!mapTransactions.count(prevout.hash)) + return error("FetchInputs() : %s mapTransactions prev not found %s", GetHash().ToString().substr(0,10).c_str(), prevout.hash.ToString().substr(0,10).c_str()); + txPrev = mapTransactions[prevout.hash]; + } + if (!fFound) + txindex.vSpent.resize(txPrev.vout.size()); + } + else + { + // Get prev tx from disk + if (!txPrev.ReadFromDisk(txindex.pos)) + return error("FetchInputs() : %s ReadFromDisk prev tx %s failed", GetHash().ToString().substr(0,10).c_str(), prevout.hash.ToString().substr(0,10).c_str()); + } + } + return true; +} + +bool CTransaction::ConnectInputs(map > inputs, + map& mapTestPool, CDiskTxPos posThisTx, + CBlockIndex* pindexBlock, int64& nFees, bool fBlock, bool fMiner, int& nSigOpsRet, int64 nMinFee) { // Take over previous transactions' spent pointers // fBlock is true when this is called from AcceptBlock when a new best-block is added to the blockchain @@ -839,43 +961,9 @@ bool CTransaction::ConnectInputs(CTxDB& txdb, map& mapTestPoo for (int i = 0; i < vin.size(); i++) { COutPoint prevout = vin[i].prevout; - - // Read txindex - CTxIndex txindex; - bool fFound = true; - if ((fBlock || fMiner) && mapTestPool.count(prevout.hash)) - { - // Get txindex from current proposed changes - txindex = mapTestPool[prevout.hash]; - } - else - { - // Read txindex from txdb - fFound = txdb.ReadTxIndex(prevout.hash, txindex); - } - if (!fFound && (fBlock || fMiner)) - return fMiner ? false : error("ConnectInputs() : %s prev tx %s index entry not found", GetHash().ToString().substr(0,10).c_str(), prevout.hash.ToString().substr(0,10).c_str()); - - // Read txPrev - CTransaction txPrev; - if (!fFound || txindex.pos == CDiskTxPos(1,1,1)) - { - // Get prev tx from single transactions in memory - CRITICAL_BLOCK(cs_mapTransactions) - { - if (!mapTransactions.count(prevout.hash)) - return error("ConnectInputs() : %s mapTransactions prev not found %s", GetHash().ToString().substr(0,10).c_str(), prevout.hash.ToString().substr(0,10).c_str()); - txPrev = mapTransactions[prevout.hash]; - } - if (!fFound) - txindex.vSpent.resize(txPrev.vout.size()); - } - else - { - // Get prev tx from disk - if (!txPrev.ReadFromDisk(txindex.pos)) - return error("ConnectInputs() : %s ReadFromDisk prev tx %s failed", GetHash().ToString().substr(0,10).c_str(), prevout.hash.ToString().substr(0,10).c_str()); - } + assert(inputs.count(prevout.hash) > 0); + CTxIndex& txindex = inputs[prevout.hash].first; + CTransaction& txPrev = inputs[prevout.hash].second; if (prevout.n >= txPrev.vout.size() || prevout.n >= txindex.vSpent.size()) return DoS(100, error("ConnectInputs() : %s prevout.n out of range %d %d %d prev tx %s\n%s", GetHash().ToString().substr(0,10).c_str(), prevout.n, txPrev.vout.size(), txindex.vSpent.size(), prevout.hash.ToString().substr(0,10).c_str(), txPrev.ToString().c_str())); @@ -891,7 +979,7 @@ bool CTransaction::ConnectInputs(CTxDB& txdb, map& mapTestPoo // still computed and checked, and any change will be caught at the next checkpoint. if (!(fBlock && IsInitialBlockDownload())) // Verify signature - if (!VerifySignature(txPrev, *this, i)) + if (!VerifySignature(txPrev, *this, i, nSigOpsRet)) return DoS(100,error("ConnectInputs() : %s VerifySignature failed", GetHash().ToString().substr(0,10).c_str())); // Check for conflicts (double-spend) @@ -965,7 +1053,8 @@ bool CTransaction::ClientConnectInputs() return false; // Verify signature - if (!VerifySignature(txPrev, *this, i)) + int nUnused = 0; + if (!VerifySignature(txPrev, *this, i, nUnused)) return error("ConnectInputs() : VerifySignature failed"); ///// this is redundant with the mapNextTx stuff, not sure which I want to get rid of @@ -1023,14 +1112,21 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex) map mapQueuedChanges; int64 nFees = 0; + int nSigOps = 0; BOOST_FOREACH(CTransaction& tx, vtx) { CDiskTxPos posThisTx(pindex->nFile, pindex->nBlockPos, nTxPos); nTxPos += ::GetSerializeSize(tx, SER_DISK); - if (!tx.ConnectInputs(txdb, mapQueuedChanges, posThisTx, pindex, nFees, true, false)) + map > mapInputs; + if (!tx.FetchInputs(txdb, mapQueuedChanges, true, false, mapInputs)) + return false; + if (!tx.ConnectInputs(mapInputs, mapQueuedChanges, posThisTx, pindex, nFees, true, false, nSigOps)) return false; + if (nSigOps > MAX_BLOCK_SIGOPS) + return DoS(100, error("ConnectBlock() : too many sigops")); } + // Write queued txindex changes for (map::iterator mi = mapQueuedChanges.begin(); mi != mapQueuedChanges.end(); ++mi) { @@ -1291,8 +1387,21 @@ bool CBlock::CheckBlock() const if (!tx.CheckTransaction()) return DoS(tx.nDoS, error("CheckBlock() : CheckTransaction failed")); - // Check that it's not full of nonstandard transactions - if (GetSigOpCount() > MAX_BLOCK_SIGOPS) + // This code should be removed when a compatibility-breaking block chain split has passed. + // Compatibility check for old clients that counted sigops differently: + int nSigOps = 0; + BOOST_FOREACH(const CTransaction& tx, vtx) + { + BOOST_FOREACH(const CTxIn& txin, tx.vin) + { + nSigOps += txin.scriptSig.GetSigOpCount(); + } + BOOST_FOREACH(const CTxOut& txout, tx.vout) + { + nSigOps += txout.scriptPubKey.GetSigOpCount(); + } + } + if (nSigOps > MAX_BLOCK_SIGOPS) return DoS(100, error("CheckBlock() : out-of-bounds SigOpCount")); // Check merkleroot @@ -2827,9 +2936,6 @@ CBlock* CreateNewBlock(CReserveKey& reservekey) unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK); if (nBlockSize + nTxSize >= MAX_BLOCK_SIZE_GEN) continue; - int nTxSigOps = tx.GetSigOpCount(); - if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS) - continue; // Transaction fee required depends on block size bool fAllowFree = (nBlockSize + nTxSize < 4000 || CTransaction::AllowFree(dPriority)); @@ -2838,7 +2944,13 @@ CBlock* CreateNewBlock(CReserveKey& reservekey) // Connecting shouldn't fail due to dependency on other memory pool transactions // because we're already processing them in order of dependency map mapTestPoolTmp(mapTestPool); - if (!tx.ConnectInputs(txdb, mapTestPoolTmp, CDiskTxPos(1,1,1), pindexPrev, nFees, false, true, nMinFee)) + map > mapInputs; + if (!tx.FetchInputs(txdb, mapTestPoolTmp, false, true, mapInputs)) + continue; + int nTxSigOps = 0; + if (!tx.ConnectInputs(mapInputs, mapTestPoolTmp, CDiskTxPos(1,1,1), pindexPrev, nFees, false, true, nTxSigOps, nMinFee)) + continue; + if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS) continue; swap(mapTestPool, mapTestPoolTmp); diff --git a/src/main.h b/src/main.h index 7f8da4e8..bf31234c 100644 --- a/src/main.h +++ b/src/main.h @@ -493,26 +493,8 @@ public: return (vin.size() == 1 && vin[0].prevout.IsNull()); } - int GetSigOpCount() const - { - int n = 0; - BOOST_FOREACH(const CTxIn& txin, vin) - n += txin.scriptSig.GetSigOpCount(); - BOOST_FOREACH(const CTxOut& txout, vout) - n += txout.scriptPubKey.GetSigOpCount(); - return n; - } - - bool IsStandard() const - { - BOOST_FOREACH(const CTxIn& txin, vin) - if (!txin.scriptSig.IsPushOnly()) - return error("nonstandard txin: %s", txin.scriptSig.ToString().c_str()); - BOOST_FOREACH(const CTxOut& txout, vout) - if (!::IsStandard(txout.scriptPubKey)) - return error("nonstandard txout: %s", txout.scriptPubKey.ToString().c_str()); - return true; - } + bool IsStandard() const; + bool IsStandardInputs(std::map > mapInputs) const; int64 GetValueOut() const { @@ -640,8 +622,11 @@ public: bool ReadFromDisk(CTxDB& txdb, COutPoint prevout); bool ReadFromDisk(COutPoint prevout); bool DisconnectInputs(CTxDB& txdb); - bool ConnectInputs(CTxDB& txdb, std::map& mapTestPool, CDiskTxPos posThisTx, - CBlockIndex* pindexBlock, int64& nFees, bool fBlock, bool fMiner, int64 nMinFee=0); + bool FetchInputs(CTxDB& txdb, const std::map& mapTestPool, + bool fBlock, bool fMiner, std::map >& inputsRet); + bool ConnectInputs(std::map > inputs, + std::map& mapTestPool, CDiskTxPos posThisTx, + CBlockIndex* pindexBlock, int64& nFees, bool fBlock, bool fMiner, int& nSigOpsRet, int64 nMinFee=0); bool ClientConnectInputs(); bool CheckTransaction() const; bool AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs=true, bool* pfMissingInputs=NULL); @@ -851,13 +836,6 @@ public: return (int64)nTime; } - int GetSigOpCount() const - { - int n = 0; - BOOST_FOREACH(const CTransaction& tx, vtx) - n += tx.GetSigOpCount(); - return n; - } uint256 BuildMerkleTree() const diff --git a/src/script.cpp b/src/script.cpp index 6a7913b0..c103d575 100644 --- a/src/script.cpp +++ b/src/script.cpp @@ -70,20 +70,186 @@ static inline void popstack(vector& stack) } -bool EvalScript(vector >& stack, const CScript& script, const CTransaction& txTo, unsigned int nIn, int nHashType) +const char* GetTxnTypeName(txntype t) +{ + switch (t) + { + case TX_NONSTANDARD: return "nonstandard"; + case TX_PUBKEY: return "pubkey"; + case TX_PUBKEYHASH: return "pubkeyhash"; + case TX_SCRIPTHASH: return "scripthash"; + case TX_MULTISIG: return "multisig"; + } + return NULL; +} + + +const char* GetOpName(opcodetype opcode) +{ + switch (opcode) + { + // push value + case OP_0 : return "0"; + case OP_PUSHDATA1 : return "OP_PUSHDATA1"; + case OP_PUSHDATA2 : return "OP_PUSHDATA2"; + case OP_PUSHDATA4 : return "OP_PUSHDATA4"; + case OP_1NEGATE : return "-1"; + case OP_RESERVED : return "OP_RESERVED"; + case OP_1 : return "1"; + case OP_2 : return "2"; + case OP_3 : return "3"; + case OP_4 : return "4"; + case OP_5 : return "5"; + case OP_6 : return "6"; + case OP_7 : return "7"; + case OP_8 : return "8"; + case OP_9 : return "9"; + case OP_10 : return "10"; + case OP_11 : return "11"; + case OP_12 : return "12"; + case OP_13 : return "13"; + case OP_14 : return "14"; + case OP_15 : return "15"; + case OP_16 : return "16"; + + // control + case OP_NOP : return "OP_NOP"; + case OP_VER : return "OP_VER"; + case OP_IF : return "OP_IF"; + case OP_NOTIF : return "OP_NOTIF"; + case OP_VERIF : return "OP_VERIF"; + case OP_VERNOTIF : return "OP_VERNOTIF"; + case OP_ELSE : return "OP_ELSE"; + case OP_ENDIF : return "OP_ENDIF"; + case OP_VERIFY : return "OP_VERIFY"; + case OP_RETURN : return "OP_RETURN"; + + // stack ops + case OP_TOALTSTACK : return "OP_TOALTSTACK"; + case OP_FROMALTSTACK : return "OP_FROMALTSTACK"; + case OP_2DROP : return "OP_2DROP"; + case OP_2DUP : return "OP_2DUP"; + case OP_3DUP : return "OP_3DUP"; + case OP_2OVER : return "OP_2OVER"; + case OP_2ROT : return "OP_2ROT"; + case OP_2SWAP : return "OP_2SWAP"; + case OP_IFDUP : return "OP_IFDUP"; + case OP_DEPTH : return "OP_DEPTH"; + case OP_DROP : return "OP_DROP"; + case OP_DUP : return "OP_DUP"; + case OP_NIP : return "OP_NIP"; + case OP_OVER : return "OP_OVER"; + case OP_PICK : return "OP_PICK"; + case OP_ROLL : return "OP_ROLL"; + case OP_ROT : return "OP_ROT"; + case OP_SWAP : return "OP_SWAP"; + case OP_TUCK : return "OP_TUCK"; + + // splice ops + case OP_CAT : return "OP_CAT"; + case OP_SUBSTR : return "OP_SUBSTR"; + case OP_LEFT : return "OP_LEFT"; + case OP_RIGHT : return "OP_RIGHT"; + case OP_SIZE : return "OP_SIZE"; + + // bit logic + case OP_INVERT : return "OP_INVERT"; + case OP_AND : return "OP_AND"; + case OP_OR : return "OP_OR"; + case OP_XOR : return "OP_XOR"; + case OP_EQUAL : return "OP_EQUAL"; + case OP_EQUALVERIFY : return "OP_EQUALVERIFY"; + case OP_RESERVED1 : return "OP_RESERVED1"; + case OP_RESERVED2 : return "OP_RESERVED2"; + + // numeric + case OP_1ADD : return "OP_1ADD"; + case OP_1SUB : return "OP_1SUB"; + case OP_2MUL : return "OP_2MUL"; + case OP_2DIV : return "OP_2DIV"; + case OP_NEGATE : return "OP_NEGATE"; + case OP_ABS : return "OP_ABS"; + case OP_NOT : return "OP_NOT"; + case OP_0NOTEQUAL : return "OP_0NOTEQUAL"; + case OP_ADD : return "OP_ADD"; + case OP_SUB : return "OP_SUB"; + case OP_MUL : return "OP_MUL"; + case OP_DIV : return "OP_DIV"; + case OP_MOD : return "OP_MOD"; + case OP_LSHIFT : return "OP_LSHIFT"; + case OP_RSHIFT : return "OP_RSHIFT"; + case OP_BOOLAND : return "OP_BOOLAND"; + case OP_BOOLOR : return "OP_BOOLOR"; + case OP_NUMEQUAL : return "OP_NUMEQUAL"; + case OP_NUMEQUALVERIFY : return "OP_NUMEQUALVERIFY"; + case OP_NUMNOTEQUAL : return "OP_NUMNOTEQUAL"; + case OP_LESSTHAN : return "OP_LESSTHAN"; + case OP_GREATERTHAN : return "OP_GREATERTHAN"; + case OP_LESSTHANOREQUAL : return "OP_LESSTHANOREQUAL"; + case OP_GREATERTHANOREQUAL : return "OP_GREATERTHANOREQUAL"; + case OP_MIN : return "OP_MIN"; + case OP_MAX : return "OP_MAX"; + case OP_WITHIN : return "OP_WITHIN"; + + // crypto + case OP_RIPEMD160 : return "OP_RIPEMD160"; + case OP_SHA1 : return "OP_SHA1"; + case OP_SHA256 : return "OP_SHA256"; + case OP_HASH160 : return "OP_HASH160"; + case OP_HASH256 : return "OP_HASH256"; + case OP_CODESEPARATOR : return "OP_CODESEPARATOR"; + case OP_CHECKSIG : return "OP_CHECKSIG"; + case OP_CHECKSIGVERIFY : return "OP_CHECKSIGVERIFY"; + case OP_CHECKMULTISIG : return "OP_CHECKMULTISIG"; + case OP_CHECKMULTISIGVERIFY : return "OP_CHECKMULTISIGVERIFY"; + + // meta + case OP_EVAL : return "OP_EVAL"; + + // expanson + case OP_NOP2 : return "OP_NOP2"; + case OP_NOP3 : return "OP_NOP3"; + case OP_NOP4 : return "OP_NOP4"; + case OP_NOP5 : return "OP_NOP5"; + case OP_NOP6 : return "OP_NOP6"; + case OP_NOP7 : return "OP_NOP7"; + case OP_NOP8 : return "OP_NOP8"; + case OP_NOP9 : return "OP_NOP9"; + case OP_NOP10 : return "OP_NOP10"; + + + + // template matching params + case OP_SCRIPTHASH : return "OP_SCRIPTHASH"; + case OP_PUBKEYHASH : return "OP_PUBKEYHASH"; + case OP_PUBKEY : return "OP_PUBKEY"; + + case OP_INVALIDOPCODE : return "OP_INVALIDOPCODE"; + default: + return "OP_UNKNOWN"; + } +} + + +// +// Returns true if script is valid. +// +bool EvalScriptInner(vector >& stack, const CScript& script, const CTransaction& txTo, unsigned int nIn, int nHashType, + CScript::const_iterator pbegincodehash, CScript::const_iterator pendcodehash, int& nOpCount, int& nSigOpCount, int nRecurseDepth) { CAutoBN_CTX pctx; CScript::const_iterator pc = script.begin(); CScript::const_iterator pend = script.end(); - CScript::const_iterator pbegincodehash = script.begin(); opcodetype opcode; valtype vchPushValue; vector vfExec; vector altstack; if (script.size() > 10000) return false; - int nOpCount = 0; + // Limit OP_EVAL recursion + if (nRecurseDepth > 2) + return false; try { @@ -155,7 +321,7 @@ bool EvalScript(vector >& stack, const CScript& script, co // Control // case OP_NOP: - case OP_NOP1: case OP_NOP2: case OP_NOP3: case OP_NOP4: case OP_NOP5: + case OP_NOP2: case OP_NOP3: case OP_NOP4: case OP_NOP5: case OP_NOP6: case OP_NOP7: case OP_NOP8: case OP_NOP9: case OP_NOP10: break; @@ -751,12 +917,13 @@ bool EvalScript(vector >& stack, const CScript& script, co //PrintHex(vchPubKey.begin(), vchPubKey.end(), "pubkey: %s\n"); // Subset of script starting at the most recent codeseparator - CScript scriptCode(pbegincodehash, pend); + CScript scriptCode(pbegincodehash, pendcodehash); // Drop the signature, since there's no way for a signature to sign itself scriptCode.FindAndDelete(CScript(vchSig)); bool fSuccess = CheckSig(vchSig, vchPubKey, scriptCode, txTo, nIn, nHashType); + nSigOpCount++; popstack(stack); popstack(stack); @@ -800,7 +967,7 @@ bool EvalScript(vector >& stack, const CScript& script, co return false; // Subset of script starting at the most recent codeseparator - CScript scriptCode(pbegincodehash, pend); + CScript scriptCode(pbegincodehash, pendcodehash); // Drop the signatures, since there's no way for a signature to sign itself for (int k = 0; k < nSigsCount; k++) @@ -823,6 +990,7 @@ bool EvalScript(vector >& stack, const CScript& script, co } ikey++; nKeysCount--; + nSigOpCount++; // If there are more signatures left than keys left, // then too many signatures have failed @@ -844,6 +1012,26 @@ bool EvalScript(vector >& stack, const CScript& script, co } break; + case OP_EVAL: + { + // Evaluate the top item on the stack as a Script + // [serialized script ] -- [result(s) of executing script] + if (stack.size() < 1) + return false; + valtype& vchScript = stacktop(-1); + CScript subscript(vchScript.begin(), vchScript.end()); + popstack(stack); + + // Codeseparators not allowed + if (subscript.Find(OP_CODESEPARATOR)) + return false; + + if (!EvalScriptInner(stack, subscript, txTo, nIn, nHashType, + pbegincodehash, pendcodehash, nOpCount, nSigOpCount, nRecurseDepth++)) + return false; + } + break; + default: return false; } @@ -865,6 +1053,17 @@ bool EvalScript(vector >& stack, const CScript& script, co return true; } +bool EvalScript(vector >& stack, const CScript& script, + const CTransaction& txTo, unsigned int nIn, int nHashType, int& nSigOpCountRet) +{ + CScript::const_iterator pbegincodehash = script.begin(); + CScript::const_iterator pendcodehash = script.end(); + + int nOpCount = 0; + return EvalScriptInner(stack, script, txTo, nIn, nHashType, pbegincodehash, pendcodehash, + nOpCount, nSigOpCountRet, 0); +} + @@ -964,38 +1163,35 @@ bool CheckSig(vector vchSig, vector vchPubKey, CSc // -// Returns lists of public keys (or public key hashes), any one of which can -// satisfy scriptPubKey +// Return public keys or hashes from scriptPubKey, for 'standard' transaction types. // -bool Solver(const CScript& scriptPubKey, vector > >& vSolutionsRet) +bool Solver(const CScript& scriptPubKey, txntype& typeRet, vector >& vSolutionsRet) { // Templates - static vector vTemplates; - if (vTemplates.empty()) + static map mTemplates; + if (mTemplates.empty()) { // Standard tx, sender provides pubkey, receiver adds signature - vTemplates.push_back(CScript() << OP_PUBKEY << OP_CHECKSIG); + mTemplates.insert(make_pair(TX_PUBKEY, CScript() << OP_PUBKEY << OP_CHECKSIG)); // Bitcoin address tx, sender provides hash of pubkey, receiver provides signature and pubkey - vTemplates.push_back(CScript() << OP_DUP << OP_HASH160 << OP_PUBKEYHASH << OP_EQUALVERIFY << OP_CHECKSIG); - - // Sender provides two pubkeys, receivers provides two signatures - vTemplates.push_back(CScript() << OP_2 << OP_PUBKEY << OP_PUBKEY << OP_2 << OP_CHECKMULTISIG); + mTemplates.insert(make_pair(TX_PUBKEYHASH, CScript() << OP_DUP << OP_HASH160 << OP_PUBKEYHASH << OP_EQUALVERIFY << OP_CHECKSIG)); - // Sender provides two pubkeys, receivers provides one of two signatures - vTemplates.push_back(CScript() << OP_1 << OP_PUBKEY << OP_PUBKEY << OP_2 << OP_CHECKMULTISIG); + // Sender provides N pubkeys, receivers provides M signatures + mTemplates.insert(make_pair(TX_MULTISIG, CScript() << OP_SMALLINTEGER << OP_PUBKEYS << OP_SMALLINTEGER << OP_CHECKMULTISIG)); - // Sender provides three pubkeys, receiver provides 2 of 3 signatures. - vTemplates.push_back(CScript() << OP_2 << OP_PUBKEY << OP_PUBKEY << OP_PUBKEY << OP_3 << OP_CHECKMULTISIG); + // Sender provides script hash, receiver provides script and + // as many signatures as required to satisfy script + mTemplates.insert(make_pair(TX_SCRIPTHASH, CScript() << OP_DUP << OP_HASH160 << OP_SCRIPTHASH << OP_EQUALVERIFY << OP_EVAL)); } // Scan templates const CScript& script1 = scriptPubKey; - BOOST_FOREACH(const CScript& script2, vTemplates) + BOOST_FOREACH(const PAIRTYPE(txntype, CScript)& tplate, mTemplates) { + const CScript& script2 = tplate.second; vSolutionsRet.clear(); - vector > currentSolution; opcodetype opcode1, opcode2; vector vch1, vch2; @@ -1006,218 +1202,333 @@ bool Solver(const CScript& scriptPubKey, vector { if (pc1 == script1.end() && pc2 == script2.end()) { - return !vSolutionsRet.empty(); + // Found a match + typeRet = tplate.first; + if (typeRet == TX_MULTISIG) + { + // Additional checks for TX_MULTISIG: + unsigned char m = vSolutionsRet.front()[0]; + unsigned char n = vSolutionsRet.back()[0]; + if (m < 1 || n < 1 || m > n || vSolutionsRet.size()-2 != n) + return false; + } + return true; } if (!script1.GetOp(pc1, opcode1, vch1)) break; if (!script2.GetOp(pc2, opcode2, vch2)) break; + + // Template matching opcodes: + if (opcode2 == OP_PUBKEYS) + { + while (vch1.size() >= 33 && vch1.size() <= 120) + { + vSolutionsRet.push_back(vch1); + if (!script1.GetOp(pc1, opcode1, vch1)) + break; + } + if (!script2.GetOp(pc2, opcode2, vch2)) + break; + // Normal situation is to fall through + // to other if/else statments + } + if (opcode2 == OP_PUBKEY) { if (vch1.size() < 33 || vch1.size() > 120) break; - currentSolution.push_back(make_pair(opcode2, vch1)); + vSolutionsRet.push_back(vch1); } else if (opcode2 == OP_PUBKEYHASH) { if (vch1.size() != sizeof(uint160)) break; - currentSolution.push_back(make_pair(opcode2, vch1)); + vSolutionsRet.push_back(vch1); } - else if (opcode2 == OP_CHECKSIG) + else if (opcode2 == OP_SCRIPTHASH) { - vSolutionsRet.push_back(currentSolution); - currentSolution.clear(); + if (vch1.size() != sizeof(uint160)) + break; + vSolutionsRet.push_back(vch1); } - else if (opcode2 == OP_CHECKMULTISIG) - { // Dig out the "m" from before the pubkeys: - CScript::const_iterator it = script2.begin(); - opcodetype op_m; - script2.GetOp(it, op_m, vch1); - int m = CScript::DecodeOP_N(op_m); - int n = currentSolution.size(); - - if (m == 2 && n == 2) + else if (opcode2 == OP_SMALLINTEGER) + { // Single-byte small integer pushed onto vSolutions + if (opcode1 == OP_0 || + (opcode1 >= OP_1 && opcode1 <= OP_16)) { - vSolutionsRet.push_back(currentSolution); - currentSolution.clear(); - } - else if (m == 1 && n == 2) - { // 2 solutions: either first key or second - for (int i = 0; i < 2; i++) - { - vector > s; - s.push_back(currentSolution[i]); - vSolutionsRet.push_back(s); - } - currentSolution.clear(); - } - else if (m == 2 && n == 3) - { // 3 solutions: any pair - for (int i = 0; i < 2; i++) - for (int j = i+1; j < 3; j++) - { - vector > s; - s.push_back(currentSolution[i]); - s.push_back(currentSolution[j]); - vSolutionsRet.push_back(s); - } - currentSolution.clear(); + char n = (char)CScript::DecodeOP_N(opcode1); + vSolutionsRet.push_back(valtype(1, n)); } + else + break; } else if (opcode1 != opcode2 || vch1 != vch2) { + // Others must match exactly break; } } } vSolutionsRet.clear(); + typeRet = TX_NONSTANDARD; return false; } -bool Solver(const CKeyStore& keystore, const CScript& scriptPubKey, uint256 hash, int nHashType, CScript& scriptSigRet) +bool Sign1(const CBitcoinAddress& address, const CKeyStore& keystore, uint256 hash, int nHashType, CScript& scriptSigRet) { - scriptSigRet.clear(); + CKey key; + if (!keystore.GetKey(address, key)) + return false; - vector > > vSolutions; - if (!Solver(scriptPubKey, vSolutions)) + vector vchSig; + if (!key.Sign(hash, vchSig)) return false; + vchSig.push_back((unsigned char)nHashType); + scriptSigRet << vchSig; - // See if we have all the keys for any of the solutions: - int whichSolution = -1; - for (int i = 0; i < vSolutions.size(); i++) - { - int keysFound = 0; - CScript scriptSig; + return true; +} - BOOST_FOREACH(PAIRTYPE(opcodetype, valtype)& item, vSolutions[i]) - { - if (item.first == OP_PUBKEY) - { - const valtype& vchPubKey = item.second; - CKey key; - vector vchSig; - if (keystore.GetKey(Hash160(vchPubKey), key) && key.GetPubKey() == vchPubKey - && hash != 0 && key.Sign(hash, vchSig)) - { - vchSig.push_back((unsigned char)nHashType); - scriptSig << vchSig; - ++keysFound; - } - } - else if (item.first == OP_PUBKEYHASH) - { - CKey key; - vector vchSig; - if (keystore.GetKey(uint160(item.second), key) - && hash != 0 && key.Sign(hash, vchSig)) - { - vchSig.push_back((unsigned char)nHashType); - scriptSig << vchSig << key.GetPubKey(); - ++keysFound; - } - } - } - if (keysFound == vSolutions[i].size()) +bool SignN(const vector& multisigdata, const CKeyStore& keystore, uint256 hash, int nHashType, CScript& scriptSigRet) +{ + int nSigned = 0; + int nRequired = multisigdata.front()[0]; + for (vector::const_iterator it = multisigdata.begin()+1; it != multisigdata.begin()+multisigdata.size()-1; it++) + { + const valtype& pubkey = *it; + CBitcoinAddress address; + address.SetPubKey(pubkey); + if (Sign1(address, keystore, hash, nHashType, scriptSigRet)) { - whichSolution = i; - scriptSigRet = scriptSig; - break; + ++nSigned; + if (nSigned == nRequired) break; } } - if (whichSolution == -1) + return nSigned==nRequired; +} + +// +// Sign scriptPubKey with private keys stored in keystore, given transaction hash and hash type. +// Signatures are returned in scriptSigRet (or returns false if scriptPubKey can't be signed). +// Returns true if scriptPubKey could be completely satisified. +// +bool Solver(const CKeyStore& keystore, const CScript& scriptPubKey, uint256 hash, int nHashType, CScript& scriptSigRet) +{ + scriptSigRet.clear(); + + txntype whichType; + vector vSolutions; + if (!Solver(scriptPubKey, whichType, vSolutions)) return false; - // CHECKMULTISIG bug workaround: - if (vSolutions.size() != 1 || - vSolutions[0].size() != 1) + CBitcoinAddress address; + valtype subscript; + switch (whichType) { - scriptSigRet.insert(scriptSigRet.begin(), OP_0); + case TX_NONSTANDARD: + return false; + case TX_PUBKEY: + address.SetPubKey(vSolutions[0]); + return Sign1(address, keystore, hash, nHashType, scriptSigRet); + case TX_PUBKEYHASH: + address.SetHash160(uint160(vSolutions[0])); + if (!Sign1(address, keystore, hash, nHashType, scriptSigRet)) + return false; + else + { + valtype vch; + keystore.GetPubKey(address, vch); + scriptSigRet << vch; + } + break; + case TX_SCRIPTHASH: + if (!keystore.GetCScript(uint160(vSolutions[0]), subscript)) + return false; + if (!Solver(keystore, CScript(subscript.begin(), subscript.end()), hash, nHashType, scriptSigRet)) + return false; + if (hash != 0) + scriptSigRet << subscript; // signatures AND serialized script + break; + case TX_MULTISIG: + scriptSigRet << OP_0; // workaround CHECKMULTISIG bug + return (SignN(vSolutions, keystore, hash, nHashType, scriptSigRet)); } - return true; } bool IsStandard(const CScript& scriptPubKey) { - vector > > vSolutions; - return Solver(scriptPubKey, vSolutions); + vector vSolutions; + txntype whichType; + if (!Solver(scriptPubKey, whichType, vSolutions)) + return false; + + if (whichType == TX_MULTISIG) + { + unsigned char m = vSolutions.front()[0]; + unsigned char n = vSolutions.back()[0]; + // Support up to x-of-3 multisig txns as standard + if (n < 1 || n > 3) + return false; + if (m < 1 || m > n) + return false; + } + + return whichType != TX_NONSTANDARD; } +int HaveKeys(const vector& pubkeys, const CKeyStore& keystore) +{ + int nResult = 0; + BOOST_FOREACH(const valtype& pubkey, pubkeys) + { + CBitcoinAddress address; + address.SetPubKey(pubkey); + if (keystore.HaveKey(address)) + ++nResult; + } + return nResult; +} + bool IsMine(const CKeyStore &keystore, const CScript& scriptPubKey) { - vector > > vSolutions; - if (!Solver(scriptPubKey, vSolutions)) + vector vSolutions; + txntype whichType; + if (!Solver(scriptPubKey, whichType, vSolutions)) return false; - int keysFound = 0; - int keysRequired = 0; - for (int i = 0; i < vSolutions.size(); i++) + CBitcoinAddress address; + switch (whichType) { - BOOST_FOREACH(PAIRTYPE(opcodetype, valtype)& item, vSolutions[i]) - { - ++keysRequired; - if (item.first == OP_PUBKEY) - { - const valtype& vchPubKey = item.second; - vector vchPubKeyFound; - if (keystore.GetPubKey(Hash160(vchPubKey), vchPubKeyFound) && vchPubKeyFound == vchPubKey) - ++keysFound; - } - else if (item.first == OP_PUBKEYHASH) - { - if (keystore.HaveKey(uint160(item.second))) - ++keysFound; - } - } + case TX_NONSTANDARD: + return false; + case TX_PUBKEY: + address.SetPubKey(vSolutions[0]); + return keystore.HaveKey(address); + case TX_PUBKEYHASH: + address.SetHash160(uint160(vSolutions[0])); + return keystore.HaveKey(address); + case TX_SCRIPTHASH: + { + valtype subscript; + if (!keystore.GetCScript(uint160(vSolutions[0]), subscript)) + return false; + return IsMine(keystore, CScript(subscript.begin(), subscript.end())); } - - // Only consider transactions "mine" if we own ALL the - // keys involved. multi-signature transactions that are - // partially owned (somebody else has a key that can spend - // them) enable spend-out-from-under-you attacks, especially - // for shared-wallet situations. - return (keysFound == keysRequired); + case TX_MULTISIG: + { + // Only consider transactions "mine" if we own ALL the + // keys involved. multi-signature transactions that are + // partially owned (somebody else has a key that can spend + // them) enable spend-out-from-under-you attacks, especially + // in shared-wallet situations. + vector keys(vSolutions.begin()+1, vSolutions.begin()+vSolutions.size()-1); + return HaveKeys(vSolutions, keystore); + } + } + return false; } bool ExtractAddress(const CScript& scriptPubKey, const CKeyStore* keystore, CBitcoinAddress& addressRet) { - vector > > vSolutions; - if (!Solver(scriptPubKey, vSolutions)) + vector vSolutions; + txntype whichType; + if (!Solver(scriptPubKey, whichType, vSolutions)) return false; - for (int i = 0; i < vSolutions.size(); i++) + if (whichType == TX_PUBKEY) { - if (vSolutions[i].size() != 1) - continue; // Can't return more than one address... - - PAIRTYPE(opcodetype, valtype)& item = vSolutions[i][0]; - if (item.first == OP_PUBKEY) - addressRet.SetPubKey(item.second); - else if (item.first == OP_PUBKEYHASH) - addressRet.SetHash160((uint160)item.second); - if (keystore == NULL || keystore->HaveKey(addressRet)) - return true; + addressRet.SetPubKey(vSolutions[0]); + return true; } + else if (whichType == TX_PUBKEYHASH) + { + addressRet.SetHash160(uint160(vSolutions[0])); + return true; + } + else if (whichType == TX_SCRIPTHASH) + { + addressRet.SetScriptHash160(uint160(vSolutions[0])); + return true; + } + // Multisig txns have more than one address... return false; } +bool ExtractAddresses(const CScript& scriptPubKey, const CKeyStore* keystore, txntype& typeRet, vector& addressRet, int& nRequiredRet) +{ + addressRet.clear(); + typeRet = TX_NONSTANDARD; + vector vSolutions; + if (!Solver(scriptPubKey, typeRet, vSolutions)) + return false; + if (typeRet == TX_MULTISIG) + { + nRequiredRet = vSolutions.front()[0]; + int n = vSolutions.back()[0]; + for (vector::const_iterator it = vSolutions.begin()+1; it != vSolutions.begin()+vSolutions.size()-1; it++) + { + CBitcoinAddress address; + address.SetPubKey(*it); + addressRet.push_back(address); + } + } + else + { + nRequiredRet = 1; + CBitcoinAddress address; + if (typeRet == TX_PUBKEYHASH) + address.SetHash160(uint160(vSolutions.front())); + else if (typeRet == TX_SCRIPTHASH) + address.SetScriptHash160(uint160(vSolutions.front())); + else if (typeRet == TX_PUBKEY) + address.SetPubKey(vSolutions.front()); + addressRet.push_back(address); + } -bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CTransaction& txTo, unsigned int nIn, int nHashType) + return true; +} + +bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CTransaction& txTo, unsigned int nIn, int& nSigOpCountRet, int nHashType) { vector > stack; - if (!EvalScript(stack, scriptSig, txTo, nIn, nHashType)) + if (!EvalScript(stack, scriptSig, txTo, nIn, nHashType, nSigOpCountRet)) return false; - if (!EvalScript(stack, scriptPubKey, txTo, nIn, nHashType)) + if (!EvalScript(stack, scriptPubKey, txTo, nIn, nHashType, nSigOpCountRet)) return false; if (stack.empty()) return false; - return CastToBool(stack.back()); + bool fResult = CastToBool(stack.back()); + + // This code should be removed when a compatibility-breaking block chain split has passed. + // Special check for OP_EVAL backwards-compatibility: if scriptPubKey or scriptSig contains + // OP_EVAL, then result must be identical if OP_EVAL is treated as a no-op: + if (scriptSig.Find(OP_EVAL)+scriptPubKey.Find(OP_EVAL) > 0) + { + int nUnused = 0; + stack.clear(); + CScript sigCopy = scriptSig; + sigCopy.FindAndDelete(CScript(OP_EVAL)); + CScript pubKeyCopy = scriptPubKey; + pubKeyCopy.FindAndDelete(CScript(OP_EVAL)); + + if (!EvalScript(stack, sigCopy, txTo, nIn, nHashType, nUnused)) + return false; + if (!EvalScript(stack, pubKeyCopy, txTo, nIn, nHashType, nUnused)) + return false; + if (stack.empty()) + return false; + if (fResult != CastToBool(stack.back())) + return false; + } + + return fResult; } @@ -1238,15 +1549,16 @@ bool SignSignature(const CKeyStore &keystore, const CTransaction& txFrom, CTrans txin.scriptSig = scriptPrereq + txin.scriptSig; // Test solution + int nUnused = 0; if (scriptPrereq.empty()) - if (!VerifyScript(txin.scriptSig, txout.scriptPubKey, txTo, nIn, 0)) + if (!VerifyScript(txin.scriptSig, txout.scriptPubKey, txTo, nIn, nUnused, 0)) return false; return true; } -bool VerifySignature(const CTransaction& txFrom, const CTransaction& txTo, unsigned int nIn, int nHashType) +bool VerifySignature(const CTransaction& txFrom, const CTransaction& txTo, unsigned int nIn, int& nSigOpCountRet, int nHashType) { assert(nIn < txTo.vin.size()); const CTxIn& txin = txTo.vin[nIn]; @@ -1257,27 +1569,35 @@ bool VerifySignature(const CTransaction& txFrom, const CTransaction& txTo, unsig if (txin.prevout.hash != txFrom.GetHash()) return false; - if (!VerifyScript(txin.scriptSig, txout.scriptPubKey, txTo, nIn, nHashType)) + if (!VerifyScript(txin.scriptSig, txout.scriptPubKey, txTo, nIn, nSigOpCountRet, nHashType)) return false; return true; } -void CScript::SetMultisigAnd(const std::vector& keys) +void CScript::SetBitcoinAddress(const CBitcoinAddress& address) { - assert(keys.size() >= 2); this->clear(); - *this << OP_2 << keys[0].GetPubKey() << keys[1].GetPubKey() << OP_2 << OP_CHECKMULTISIG; + if (address.IsScript()) + *this << OP_DUP << OP_HASH160 << address.GetHash160() << OP_EQUALVERIFY << OP_EVAL; + else + *this << OP_DUP << OP_HASH160 << address.GetHash160() << OP_EQUALVERIFY << OP_CHECKSIG; } -void CScript::SetMultisigOr(const std::vector& keys) + +void CScript::SetMultisig(int nRequired, const std::vector& keys) { - assert(keys.size() >= 2); this->clear(); - *this << OP_1 << keys[0].GetPubKey() << keys[1].GetPubKey() << OP_2 << OP_CHECKMULTISIG; + + *this << EncodeOP_N(nRequired); + BOOST_FOREACH(const CKey& key, keys) + *this << key.GetPubKey(); + *this << EncodeOP_N(keys.size()) << OP_CHECKMULTISIG; } -void CScript::SetMultisigEscrow(const std::vector& keys) + +void CScript::SetEval(const CScript& subscript) { - assert(keys.size() >= 3); + assert(!subscript.empty()); + uint160 subscriptHash = Hash160(subscript); this->clear(); - *this << OP_2 << keys[0].GetPubKey() << keys[1].GetPubKey() << keys[1].GetPubKey() << OP_3 << OP_CHECKMULTISIG; + *this << OP_DUP << OP_HASH160 << subscriptHash << OP_EQUALVERIFY << OP_EVAL; } diff --git a/src/script.h b/src/script.h index a5a1e186..ee0be02a 100644 --- a/src/script.h +++ b/src/script.h @@ -24,6 +24,17 @@ enum }; +enum txntype +{ + TX_NONSTANDARD, + // 'standard' transaction types: + TX_PUBKEY, + TX_PUBKEYHASH, + TX_SCRIPTHASH, + TX_MULTISIG, +}; + +const char* GetTxnTypeName(txntype t); enum opcodetype { @@ -147,8 +158,10 @@ enum opcodetype OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY, + // meta + OP_EVAL, // Was OP_NOP1 + // expansion - OP_NOP1, OP_NOP2, OP_NOP3, OP_NOP4, @@ -162,162 +175,16 @@ enum opcodetype // template matching params + OP_SMALLINTEGER = 0xfa, + OP_PUBKEYS = 0xfb, + OP_SCRIPTHASH = 0xfc, OP_PUBKEYHASH = 0xfd, OP_PUBKEY = 0xfe, OP_INVALIDOPCODE = 0xff, }; - - - - - - - -inline const char* GetOpName(opcodetype opcode) -{ - switch (opcode) - { - // push value - case OP_0 : return "0"; - case OP_PUSHDATA1 : return "OP_PUSHDATA1"; - case OP_PUSHDATA2 : return "OP_PUSHDATA2"; - case OP_PUSHDATA4 : return "OP_PUSHDATA4"; - case OP_1NEGATE : return "-1"; - case OP_RESERVED : return "OP_RESERVED"; - case OP_1 : return "1"; - case OP_2 : return "2"; - case OP_3 : return "3"; - case OP_4 : return "4"; - case OP_5 : return "5"; - case OP_6 : return "6"; - case OP_7 : return "7"; - case OP_8 : return "8"; - case OP_9 : return "9"; - case OP_10 : return "10"; - case OP_11 : return "11"; - case OP_12 : return "12"; - case OP_13 : return "13"; - case OP_14 : return "14"; - case OP_15 : return "15"; - case OP_16 : return "16"; - - // control - case OP_NOP : return "OP_NOP"; - case OP_VER : return "OP_VER"; - case OP_IF : return "OP_IF"; - case OP_NOTIF : return "OP_NOTIF"; - case OP_VERIF : return "OP_VERIF"; - case OP_VERNOTIF : return "OP_VERNOTIF"; - case OP_ELSE : return "OP_ELSE"; - case OP_ENDIF : return "OP_ENDIF"; - case OP_VERIFY : return "OP_VERIFY"; - case OP_RETURN : return "OP_RETURN"; - - // stack ops - case OP_TOALTSTACK : return "OP_TOALTSTACK"; - case OP_FROMALTSTACK : return "OP_FROMALTSTACK"; - case OP_2DROP : return "OP_2DROP"; - case OP_2DUP : return "OP_2DUP"; - case OP_3DUP : return "OP_3DUP"; - case OP_2OVER : return "OP_2OVER"; - case OP_2ROT : return "OP_2ROT"; - case OP_2SWAP : return "OP_2SWAP"; - case OP_IFDUP : return "OP_IFDUP"; - case OP_DEPTH : return "OP_DEPTH"; - case OP_DROP : return "OP_DROP"; - case OP_DUP : return "OP_DUP"; - case OP_NIP : return "OP_NIP"; - case OP_OVER : return "OP_OVER"; - case OP_PICK : return "OP_PICK"; - case OP_ROLL : return "OP_ROLL"; - case OP_ROT : return "OP_ROT"; - case OP_SWAP : return "OP_SWAP"; - case OP_TUCK : return "OP_TUCK"; - - // splice ops - case OP_CAT : return "OP_CAT"; - case OP_SUBSTR : return "OP_SUBSTR"; - case OP_LEFT : return "OP_LEFT"; - case OP_RIGHT : return "OP_RIGHT"; - case OP_SIZE : return "OP_SIZE"; - - // bit logic - case OP_INVERT : return "OP_INVERT"; - case OP_AND : return "OP_AND"; - case OP_OR : return "OP_OR"; - case OP_XOR : return "OP_XOR"; - case OP_EQUAL : return "OP_EQUAL"; - case OP_EQUALVERIFY : return "OP_EQUALVERIFY"; - case OP_RESERVED1 : return "OP_RESERVED1"; - case OP_RESERVED2 : return "OP_RESERVED2"; - - // numeric - case OP_1ADD : return "OP_1ADD"; - case OP_1SUB : return "OP_1SUB"; - case OP_2MUL : return "OP_2MUL"; - case OP_2DIV : return "OP_2DIV"; - case OP_NEGATE : return "OP_NEGATE"; - case OP_ABS : return "OP_ABS"; - case OP_NOT : return "OP_NOT"; - case OP_0NOTEQUAL : return "OP_0NOTEQUAL"; - case OP_ADD : return "OP_ADD"; - case OP_SUB : return "OP_SUB"; - case OP_MUL : return "OP_MUL"; - case OP_DIV : return "OP_DIV"; - case OP_MOD : return "OP_MOD"; - case OP_LSHIFT : return "OP_LSHIFT"; - case OP_RSHIFT : return "OP_RSHIFT"; - case OP_BOOLAND : return "OP_BOOLAND"; - case OP_BOOLOR : return "OP_BOOLOR"; - case OP_NUMEQUAL : return "OP_NUMEQUAL"; - case OP_NUMEQUALVERIFY : return "OP_NUMEQUALVERIFY"; - case OP_NUMNOTEQUAL : return "OP_NUMNOTEQUAL"; - case OP_LESSTHAN : return "OP_LESSTHAN"; - case OP_GREATERTHAN : return "OP_GREATERTHAN"; - case OP_LESSTHANOREQUAL : return "OP_LESSTHANOREQUAL"; - case OP_GREATERTHANOREQUAL : return "OP_GREATERTHANOREQUAL"; - case OP_MIN : return "OP_MIN"; - case OP_MAX : return "OP_MAX"; - case OP_WITHIN : return "OP_WITHIN"; - - // crypto - case OP_RIPEMD160 : return "OP_RIPEMD160"; - case OP_SHA1 : return "OP_SHA1"; - case OP_SHA256 : return "OP_SHA256"; - case OP_HASH160 : return "OP_HASH160"; - case OP_HASH256 : return "OP_HASH256"; - case OP_CODESEPARATOR : return "OP_CODESEPARATOR"; - case OP_CHECKSIG : return "OP_CHECKSIG"; - case OP_CHECKSIGVERIFY : return "OP_CHECKSIGVERIFY"; - case OP_CHECKMULTISIG : return "OP_CHECKMULTISIG"; - case OP_CHECKMULTISIGVERIFY : return "OP_CHECKMULTISIGVERIFY"; - - // expanson - case OP_NOP1 : return "OP_NOP1"; - case OP_NOP2 : return "OP_NOP2"; - case OP_NOP3 : return "OP_NOP3"; - case OP_NOP4 : return "OP_NOP4"; - case OP_NOP5 : return "OP_NOP5"; - case OP_NOP6 : return "OP_NOP6"; - case OP_NOP7 : return "OP_NOP7"; - case OP_NOP8 : return "OP_NOP8"; - case OP_NOP9 : return "OP_NOP9"; - case OP_NOP10 : return "OP_NOP10"; - - - - // template matching params - case OP_PUBKEYHASH : return "OP_PUBKEYHASH"; - case OP_PUBKEY : return "OP_PUBKEY"; - - case OP_INVALIDOPCODE : return "OP_INVALIDOPCODE"; - default: - return "OP_UNKNOWN"; - } -}; - +const char* GetOpName(opcodetype opcode); @@ -574,6 +441,7 @@ public: return true; } + // Encode/decode small integers: static int DecodeOP_N(opcodetype opcode) { if (opcode == OP_0) @@ -581,22 +449,44 @@ public: assert(opcode >= OP_1 && opcode <= OP_16); return (int)opcode - (int)(OP_1 - 1); } + static opcodetype EncodeOP_N(int n) + { + assert(n >= 0 && n <= 16); + if (n == 0) + return OP_0; + return (opcodetype)(OP_1+n-1); + } - void FindAndDelete(const CScript& b) + int FindAndDelete(const CScript& b) { + int nFound = 0; if (b.empty()) - return; + return nFound; iterator pc = begin(); opcodetype opcode; do { while (end() - pc >= b.size() && memcmp(&pc[0], &b[0], b.size()) == 0) + { erase(pc, pc + b.size()); + ++nFound; + } } while (GetOp(pc, opcode)); + return nFound; + } + int Find(opcodetype op) const + { + int nFound = 0; + opcodetype opcode; + for (const_iterator pc = begin(); pc != end() && GetOp(pc, opcode);) + if (opcode == op) + ++nFound; + return nFound; } - + // This method should be removed when a compatibility-breaking block chain split has passed. + // Compatibility method for old clients that count sigops differently: int GetSigOpCount() const { int n = 0; @@ -614,11 +504,9 @@ public: return n; } - + // Called by CTransaction::IsStandard bool IsPushOnly() const { - if (size() > 200) - return false; const_iterator pc = begin(); while (pc < end()) { @@ -632,19 +520,13 @@ public: } - void SetBitcoinAddress(const CBitcoinAddress& address) - { - this->clear(); - *this << OP_DUP << OP_HASH160 << address.GetHash160() << OP_EQUALVERIFY << OP_CHECKSIG; - } - + void SetBitcoinAddress(const CBitcoinAddress& address); void SetBitcoinAddress(const std::vector& vchPubKey) { SetBitcoinAddress(CBitcoinAddress(vchPubKey)); } - void SetMultisigAnd(const std::vector& keys); - void SetMultisigOr(const std::vector& keys); - void SetMultisigEscrow(const std::vector& keys); + void SetMultisig(int nRequired, const std::vector& keys); + void SetEval(const CScript& subscript); void PrintHex() const @@ -685,14 +567,14 @@ public: +bool EvalScript(std::vector >& stack, const CScript& script, const CTransaction& txTo, unsigned int nIn, int nHashType, int& nSigOpCountRet); - -bool EvalScript(std::vector >& stack, const CScript& script, const CTransaction& txTo, unsigned int nIn, int nHashType); - +bool Solver(const CScript& scriptPubKey, txntype& typeRet, std::vector >& vSolutionsRet); bool IsStandard(const CScript& scriptPubKey); bool IsMine(const CKeyStore& keystore, const CScript& scriptPubKey); bool ExtractAddress(const CScript& scriptPubKey, const CKeyStore* pkeystore, CBitcoinAddress& addressRet); +bool ExtractAddresses(const CScript& scriptPubKey, const CKeyStore* pkeystore, txntype& typeRet, std::vector& addressRet, int& nRequiredRet); bool SignSignature(const CKeyStore& keystore, const CTransaction& txFrom, CTransaction& txTo, unsigned int nIn, int nHashType=SIGHASH_ALL, CScript scriptPrereq=CScript()); -bool VerifySignature(const CTransaction& txFrom, const CTransaction& txTo, unsigned int nIn, int nHashType=0); +bool VerifySignature(const CTransaction& txFrom, const CTransaction& txTo, unsigned int nIn, int& nSigOpCountRet, int nHashType=0); #endif diff --git a/src/test/multisig_tests.cpp b/src/test/multisig_tests.cpp index 459d1123..75c764dd 100644 --- a/src/test/multisig_tests.cpp +++ b/src/test/multisig_tests.cpp @@ -20,9 +20,7 @@ using namespace boost::assign; typedef vector valtype; extern uint256 SignatureHash(CScript scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType); -extern bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CTransaction& txTo, unsigned int nIn, int nHashType); -extern bool VerifySignature(const CTransaction& txFrom, const CTransaction& txTo, unsigned int nIn, int nHashType); -extern bool Solver(const CScript& scriptPubKey, vector > >& vSolutionsRet); +extern bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CTransaction& txTo, unsigned int nIn, int& nSigOpCount, int nHashType); BOOST_AUTO_TEST_SUITE(multisig_tests) @@ -76,24 +74,25 @@ BOOST_AUTO_TEST_CASE(multisig_verify) vector keys; CScript s; + int nUnused = 0; // Test a AND b: keys.clear(); keys += key[0],key[1]; // magic operator+= from boost.assign s = sign_multisig(a_and_b, keys, txTo[0], 0); - BOOST_CHECK(VerifyScript(s, a_and_b, txTo[0], 0, 0)); + BOOST_CHECK(VerifyScript(s, a_and_b, txTo[0], 0, nUnused, 0)); for (int i = 0; i < 4; i++) { keys.clear(); keys += key[i]; s = sign_multisig(a_and_b, keys, txTo[0], 0); - BOOST_CHECK_MESSAGE(!VerifyScript(s, a_and_b, txTo[0], 0, 0), strprintf("a&b 1: %d", i)); + BOOST_CHECK_MESSAGE(!VerifyScript(s, a_and_b, txTo[0], 0, nUnused, 0), strprintf("a&b 1: %d", i)); keys.clear(); keys += key[1],key[i]; s = sign_multisig(a_and_b, keys, txTo[0], 0); - BOOST_CHECK_MESSAGE(!VerifyScript(s, a_and_b, txTo[0], 0, 0), strprintf("a&b 2: %d", i)); + BOOST_CHECK_MESSAGE(!VerifyScript(s, a_and_b, txTo[0], 0, nUnused, 0), strprintf("a&b 2: %d", i)); } // Test a OR b: @@ -103,16 +102,16 @@ BOOST_AUTO_TEST_CASE(multisig_verify) keys += key[i]; s = sign_multisig(a_or_b, keys, txTo[1], 0); if (i == 0 || i == 1) - BOOST_CHECK_MESSAGE(VerifyScript(s, a_or_b, txTo[1], 0, 0), strprintf("a|b: %d", i)); + BOOST_CHECK_MESSAGE(VerifyScript(s, a_or_b, txTo[1], 0, nUnused, 0), strprintf("a|b: %d", i)); else - BOOST_CHECK_MESSAGE(!VerifyScript(s, a_or_b, txTo[1], 0, 0), strprintf("a|b: %d", i)); + BOOST_CHECK_MESSAGE(!VerifyScript(s, a_or_b, txTo[1], 0, nUnused, 0), strprintf("a|b: %d", i)); } s.clear(); s << OP_0 << OP_0; - BOOST_CHECK(!VerifyScript(s, a_or_b, txTo[1], 0, 0)); + BOOST_CHECK(!VerifyScript(s, a_or_b, txTo[1], 0, nUnused, 0)); s.clear(); s << OP_0 << OP_1; - BOOST_CHECK(!VerifyScript(s, a_or_b, txTo[1], 0, 0)); + BOOST_CHECK(!VerifyScript(s, a_or_b, txTo[1], 0, nUnused, 0)); for (int i = 0; i < 4; i++) @@ -122,16 +121,16 @@ BOOST_AUTO_TEST_CASE(multisig_verify) keys += key[i],key[j]; s = sign_multisig(escrow, keys, txTo[2], 0); if (i < j && i < 3 && j < 3) - BOOST_CHECK_MESSAGE(VerifyScript(s, escrow, txTo[2], 0, 0), strprintf("escrow 1: %d %d", i, j)); + BOOST_CHECK_MESSAGE(VerifyScript(s, escrow, txTo[2], 0, nUnused, 0), strprintf("escrow 1: %d %d", i, j)); else - BOOST_CHECK_MESSAGE(!VerifyScript(s, escrow, txTo[2], 0, 0), strprintf("escrow 2: %d %d", i, j)); + BOOST_CHECK_MESSAGE(!VerifyScript(s, escrow, txTo[2], 0, nUnused, 0), strprintf("escrow 2: %d %d", i, j)); } } BOOST_AUTO_TEST_CASE(multisig_IsStandard) { - CKey key[3]; - for (int i = 0; i < 3; i++) + CKey key[4]; + for (int i = 0; i < 4; i++) key[i].MakeNewKey(); CScript a_and_b; @@ -145,6 +144,21 @@ BOOST_AUTO_TEST_CASE(multisig_IsStandard) CScript escrow; escrow << OP_2 << key[0].GetPubKey() << key[1].GetPubKey() << key[2].GetPubKey() << OP_3 << OP_CHECKMULTISIG; BOOST_CHECK(::IsStandard(escrow)); + + CScript one_of_four; + one_of_four << OP_1 << key[0].GetPubKey() << key[1].GetPubKey() << key[2].GetPubKey() << key[3].GetPubKey() << OP_4 << OP_CHECKMULTISIG; + BOOST_CHECK(!::IsStandard(one_of_four)); + + CScript malformed[6]; + malformed[0] << OP_3 << key[0].GetPubKey() << key[1].GetPubKey() << OP_2 << OP_CHECKMULTISIG; + malformed[1] << OP_2 << key[0].GetPubKey() << key[1].GetPubKey() << OP_3 << OP_CHECKMULTISIG; + malformed[2] << OP_0 << key[0].GetPubKey() << key[1].GetPubKey() << OP_2 << OP_CHECKMULTISIG; + malformed[3] << OP_1 << key[0].GetPubKey() << key[1].GetPubKey() << OP_0 << OP_CHECKMULTISIG; + malformed[4] << OP_1 << key[0].GetPubKey() << key[1].GetPubKey() << OP_CHECKMULTISIG; + malformed[5] << OP_1 << key[0].GetPubKey() << key[1].GetPubKey(); + + for (int i = 0; i < 6; i++) + BOOST_CHECK(!::IsStandard(malformed[i])); } BOOST_AUTO_TEST_CASE(multisig_Solver1) @@ -170,13 +184,12 @@ BOOST_AUTO_TEST_CASE(multisig_Solver1) } { - vector > > solutions; + vector solutions; + txntype whichType; CScript s; s << key[0].GetPubKey() << OP_CHECKSIG; - BOOST_CHECK(Solver(s, solutions)); + BOOST_CHECK(Solver(s, whichType, solutions)); BOOST_CHECK(solutions.size() == 1); - if (solutions.size() == 1) - BOOST_CHECK(solutions[0].size() == 1); CBitcoinAddress addr; BOOST_CHECK(ExtractAddress(s, &keystore, addr)); BOOST_CHECK(addr == keyaddr[0]); @@ -184,13 +197,12 @@ BOOST_AUTO_TEST_CASE(multisig_Solver1) BOOST_CHECK(!IsMine(emptykeystore, s)); } { - vector > > solutions; + vector solutions; + txntype whichType; CScript s; s << OP_DUP << OP_HASH160 << Hash160(key[0].GetPubKey()) << OP_EQUALVERIFY << OP_CHECKSIG; - BOOST_CHECK(Solver(s, solutions)); + BOOST_CHECK(Solver(s, whichType, solutions)); BOOST_CHECK(solutions.size() == 1); - if (solutions.size() == 1) - BOOST_CHECK(solutions[0].size() == 1); CBitcoinAddress addr; BOOST_CHECK(ExtractAddress(s, &keystore, addr)); BOOST_CHECK(addr == keyaddr[0]); @@ -198,47 +210,40 @@ BOOST_AUTO_TEST_CASE(multisig_Solver1) BOOST_CHECK(!IsMine(emptykeystore, s)); } { - vector > > solutions; + vector solutions; + txntype whichType; CScript s; s << OP_2 << key[0].GetPubKey() << key[1].GetPubKey() << OP_2 << OP_CHECKMULTISIG; - BOOST_CHECK(Solver(s, solutions)); - BOOST_CHECK(solutions.size() == 1); - if (solutions.size() == 1) - BOOST_CHECK(solutions[0].size() == 2); + BOOST_CHECK(Solver(s, whichType, solutions)); + BOOST_CHECK_EQUAL(solutions.size(), 4); CBitcoinAddress addr; BOOST_CHECK(!ExtractAddress(s, &keystore, addr)); BOOST_CHECK(IsMine(keystore, s)); BOOST_CHECK(!IsMine(emptykeystore, s)); } { - vector > > solutions; + vector solutions; + txntype whichType; CScript s; s << OP_1 << key[0].GetPubKey() << key[1].GetPubKey() << OP_2 << OP_CHECKMULTISIG; - BOOST_CHECK(Solver(s, solutions)); - BOOST_CHECK(solutions.size() == 2); - if (solutions.size() == 2) - { - BOOST_CHECK(solutions[0].size() == 1); - BOOST_CHECK(solutions[1].size() == 1); - } - CBitcoinAddress addr; - BOOST_CHECK(ExtractAddress(s, &keystore, addr)); - BOOST_CHECK(addr == keyaddr[0]); + BOOST_CHECK(Solver(s, whichType, solutions)); + BOOST_CHECK_EQUAL(solutions.size(), 4); + vector addrs; + int nRequired; + BOOST_CHECK(ExtractAddresses(s, &keystore, whichType, addrs, nRequired)); + BOOST_CHECK(addrs[0] == keyaddr[0]); + BOOST_CHECK(addrs[1] == keyaddr[1]); + BOOST_CHECK(nRequired = 1); BOOST_CHECK(IsMine(keystore, s)); BOOST_CHECK(!IsMine(emptykeystore, s)); } { - vector > > solutions; + vector solutions; + txntype whichType; CScript s; s << OP_2 << key[0].GetPubKey() << key[1].GetPubKey() << key[2].GetPubKey() << OP_3 << OP_CHECKMULTISIG; - BOOST_CHECK(Solver(s, solutions)); - BOOST_CHECK(solutions.size() == 3); - if (solutions.size() == 3) - { - BOOST_CHECK(solutions[0].size() == 2); - BOOST_CHECK(solutions[1].size() == 2); - BOOST_CHECK(solutions[2].size() == 2); - } + BOOST_CHECK(Solver(s, whichType, solutions)); + BOOST_CHECK(solutions.size() == 5); } } diff --git a/src/test/script_op_eval_tests.cpp b/src/test/script_op_eval_tests.cpp new file mode 100644 index 00000000..857d04bc --- /dev/null +++ b/src/test/script_op_eval_tests.cpp @@ -0,0 +1,203 @@ +#include +#include +#include +#include +#include +#include + +#include "../main.h" +#include "../script.h" +#include "../wallet.h" + +using namespace std; + +// Test routines internal to script.cpp: +extern uint256 SignatureHash(CScript scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType); +extern bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CTransaction& txTo, unsigned int nIn, int& nSigOps, int nHashType); + +BOOST_AUTO_TEST_SUITE(script_op_eval_tests) + +BOOST_AUTO_TEST_CASE(script_op_eval1) +{ + // OP_EVAL looks like this: + // scriptSig: + // scriptPubKey: DUP HASH160 EQUALVERIFY EVAL + + // Test SignSignature() (and therefore the version of Solver() that signs transactions) + CBasicKeyStore keystore; + CKey key[4]; + for (int i = 0; i < 4; i++) + { + key[i].MakeNewKey(); + keystore.AddKey(key[i]); + } + + // 8 Scripts: checking all combinations of + // different keys, straight/EVAL, pubkey/pubkeyhash + CScript standardScripts[4]; + standardScripts[0] << key[0].GetPubKey() << OP_CHECKSIG; + standardScripts[1].SetBitcoinAddress(key[1].GetPubKey()); + standardScripts[2] << key[1].GetPubKey() << OP_CHECKSIG; + standardScripts[3].SetBitcoinAddress(key[2].GetPubKey()); + CScript evalScripts[4]; + uint160 sigScriptHashes[4]; + for (int i = 0; i < 4; i++) + { + sigScriptHashes[i] = Hash160(standardScripts[i]); + keystore.AddCScript(sigScriptHashes[i], standardScripts[i]); + evalScripts[i] << OP_DUP << OP_HASH160 << sigScriptHashes[i] << OP_EQUALVERIFY << OP_EVAL; + } + + CTransaction txFrom; // Funding transaction: + txFrom.vout.resize(8); + for (int i = 0; i < 4; i++) + { + txFrom.vout[i].scriptPubKey = evalScripts[i]; + txFrom.vout[i+4].scriptPubKey = standardScripts[i]; + } + BOOST_CHECK(txFrom.IsStandard()); + + CTransaction txTo[8]; // Spending transactions + for (int i = 0; i < 8; i++) + { + txTo[i].vin.resize(1); + txTo[i].vout.resize(1); + txTo[i].vin[0].prevout.n = i; + txTo[i].vin[0].prevout.hash = txFrom.GetHash(); + txTo[i].vout[0].nValue = 1; + BOOST_CHECK_MESSAGE(IsMine(keystore, txFrom.vout[i].scriptPubKey), strprintf("IsMine %d", i)); + } + for (int i = 0; i < 8; i++) + { + BOOST_CHECK_MESSAGE(SignSignature(keystore, txFrom, txTo[i], 0), strprintf("SignSignature %d", i)); + } + // All of the above should be OK, and the txTos have valid signatures + // Check to make sure signature verification fails if we use the wrong ScriptSig: + for (int i = 0; i < 8; i++) + for (int j = 0; j < 8; j++) + { + CScript sigSave = txTo[i].vin[0].scriptSig; + txTo[i].vin[0].scriptSig = txTo[j].vin[0].scriptSig; + int nUnused = 0; + bool sigOK = VerifySignature(txFrom, txTo[i], 0, nUnused); + if (i == j) + BOOST_CHECK_MESSAGE(sigOK, strprintf("VerifySignature %d %d", i, j)); + else + BOOST_CHECK_MESSAGE(!sigOK, strprintf("VerifySignature %d %d", i, j)); + txTo[i].vin[0].scriptSig = sigSave; + } +} + +BOOST_AUTO_TEST_CASE(script_op_eval2) +{ + // Test OP_EVAL edge cases + + CScript recurse; + recurse << OP_DUP << OP_EVAL; + + uint160 recurseHash = Hash160(recurse); + + CScript fund; + fund << OP_DUP << OP_HASH160 << recurseHash << OP_EQUALVERIFY << OP_EVAL; + + CTransaction txFrom; // Funding transaction: + txFrom.vout.resize(1); + txFrom.vout[0].scriptPubKey = fund; + + BOOST_CHECK(txFrom.IsStandard()); // Looks like a standard transaction until you try to spend it + + CTransaction txTo; + txTo.vin.resize(1); + txTo.vout.resize(1); + txTo.vin[0].prevout.n = 0; + txTo.vin[0].prevout.hash = txFrom.GetHash(); + txTo.vin[0].scriptSig = CScript() << static_cast >(recurse); + txTo.vout[0].nValue = 1; + + int nUnused = 0; + BOOST_CHECK(!VerifyScript(txTo.vin[0].scriptSig, txFrom.vout[0].scriptPubKey, txTo, 0, nUnused, 0)); + BOOST_CHECK(!VerifySignature(txFrom, txTo, 0, nUnused)); +} + +BOOST_AUTO_TEST_CASE(script_op_eval3) +{ + // Test the CScript::Set* methods + CBasicKeyStore keystore; + CKey key[4]; + std::vector keys; + for (int i = 0; i < 4; i++) + { + key[i].MakeNewKey(); + keystore.AddKey(key[i]); + keys.push_back(key[i]); + } + + CScript inner[4]; + inner[0].SetBitcoinAddress(key[0].GetPubKey()); + inner[1].SetMultisig(2, std::vector(keys.begin(), keys.begin()+2)); + inner[2].SetMultisig(1, std::vector(keys.begin(), keys.begin()+2)); + inner[3].SetMultisig(2, std::vector(keys.begin(), keys.begin()+3)); + + CScript outer[4]; + for (int i = 0; i < 4; i++) + { + outer[i].SetEval(inner[i]); + keystore.AddCScript(Hash160(inner[i]), inner[i]); + } + + CTransaction txFrom; // Funding transaction: + txFrom.vout.resize(4); + for (int i = 0; i < 4; i++) + { + txFrom.vout[i].scriptPubKey = outer[i]; + } + BOOST_CHECK(txFrom.IsStandard()); + + CTransaction txTo[4]; // Spending transactions + for (int i = 0; i < 4; i++) + { + txTo[i].vin.resize(1); + txTo[i].vout.resize(1); + txTo[i].vin[0].prevout.n = i; + txTo[i].vin[0].prevout.hash = txFrom.GetHash(); + txTo[i].vout[0].nValue = 1; + txTo[i].vout[0].scriptPubKey = inner[i]; + BOOST_CHECK_MESSAGE(IsMine(keystore, txFrom.vout[i].scriptPubKey), strprintf("IsMine %d", i)); + } + for (int i = 0; i < 4; i++) + { + BOOST_CHECK_MESSAGE(SignSignature(keystore, txFrom, txTo[i], 0), strprintf("SignSignature %d", i)); + BOOST_CHECK_MESSAGE(txTo[i].IsStandard(), strprintf("txTo[%d].IsStandard", i)); + } +} + +BOOST_AUTO_TEST_CASE(script_op_eval_backcompat) +{ + // Check backwards-incompatibility-testing code + CScript returnsEleven; + returnsEleven << OP_11; + + // This will validate on new clients, but will + // be invalid on old clients (that interpret OP_EVAL as a no-op) + CScript fund; + fund << OP_EVAL << OP_11 << OP_EQUAL; + + CTransaction txFrom; // Funding transaction: + txFrom.vout.resize(1); + txFrom.vout[0].scriptPubKey = fund; + + CTransaction txTo; + txTo.vin.resize(1); + txTo.vout.resize(1); + txTo.vin[0].prevout.n = 0; + txTo.vin[0].prevout.hash = txFrom.GetHash(); + txTo.vin[0].scriptSig = CScript() << static_cast >(returnsEleven); + txTo.vout[0].nValue = 1; + + int nUnused = 0; + BOOST_CHECK(!VerifyScript(txTo.vin[0].scriptSig, txFrom.vout[0].scriptPubKey, txTo, 0, nUnused, 0)); + BOOST_CHECK(!VerifySignature(txFrom, txTo, 0, nUnused)); +} + + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index f3fa5c3a..3d1c2187 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -7,7 +7,7 @@ using namespace std; extern uint256 SignatureHash(CScript scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType); -extern bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CTransaction& txTo, unsigned int nIn, int nHashType); +extern bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CTransaction& txTo, unsigned int nIn, int& nSigOps, int nHashType); extern bool VerifySignature(const CTransaction& txFrom, const CTransaction& txTo, unsigned int nIn, int nHashType); BOOST_AUTO_TEST_SUITE(script_tests) @@ -21,19 +21,21 @@ BOOST_AUTO_TEST_CASE(script_PushData) static const unsigned char pushdata2[] = { OP_PUSHDATA2, 1, 0, 0x5a }; static const unsigned char pushdata4[] = { OP_PUSHDATA4, 1, 0, 0, 0, 0x5a }; + int nUnused = 0; + vector > directStack; - BOOST_CHECK(EvalScript(directStack, CScript(&direct[0], &direct[sizeof(direct)]), CTransaction(), 0, 0)); + BOOST_CHECK(EvalScript(directStack, CScript(&direct[0], &direct[sizeof(direct)]), CTransaction(), 0, 0, nUnused)); vector > pushdata1Stack; - BOOST_CHECK(EvalScript(pushdata1Stack, CScript(&pushdata1[0], &pushdata1[sizeof(pushdata1)]), CTransaction(), 0, 0)); + BOOST_CHECK(EvalScript(pushdata1Stack, CScript(&pushdata1[0], &pushdata1[sizeof(pushdata1)]), CTransaction(), 0, 0, nUnused)); BOOST_CHECK(pushdata1Stack == directStack); vector > pushdata2Stack; - BOOST_CHECK(EvalScript(pushdata2Stack, CScript(&pushdata2[0], &pushdata2[sizeof(pushdata2)]), CTransaction(), 0, 0)); + BOOST_CHECK(EvalScript(pushdata2Stack, CScript(&pushdata2[0], &pushdata2[sizeof(pushdata2)]), CTransaction(), 0, 0, nUnused)); BOOST_CHECK(pushdata2Stack == directStack); vector > pushdata4Stack; - BOOST_CHECK(EvalScript(pushdata4Stack, CScript(&pushdata4[0], &pushdata4[sizeof(pushdata4)]), CTransaction(), 0, 0)); + BOOST_CHECK(EvalScript(pushdata4Stack, CScript(&pushdata4[0], &pushdata4[sizeof(pushdata4)]), CTransaction(), 0, 0, nUnused)); BOOST_CHECK(pushdata4Stack == directStack); } @@ -71,6 +73,7 @@ sign_multisig(CScript scriptPubKey, CKey key, CTransaction transaction) BOOST_AUTO_TEST_CASE(script_CHECKMULTISIG12) { + int nUnused = 0; CKey key1, key2, key3; key1.MakeNewKey(); key2.MakeNewKey(); @@ -91,19 +94,20 @@ BOOST_AUTO_TEST_CASE(script_CHECKMULTISIG12) txTo12.vout[0].nValue = 1; CScript goodsig1 = sign_multisig(scriptPubKey12, key1, txTo12); - BOOST_CHECK(VerifyScript(goodsig1, scriptPubKey12, txTo12, 0, 0)); + BOOST_CHECK(VerifyScript(goodsig1, scriptPubKey12, txTo12, 0, nUnused, 0)); txTo12.vout[0].nValue = 2; - BOOST_CHECK(!VerifyScript(goodsig1, scriptPubKey12, txTo12, 0, 0)); + BOOST_CHECK(!VerifyScript(goodsig1, scriptPubKey12, txTo12, 0, nUnused, 0)); CScript goodsig2 = sign_multisig(scriptPubKey12, key2, txTo12); - BOOST_CHECK(VerifyScript(goodsig2, scriptPubKey12, txTo12, 0, 0)); + BOOST_CHECK(VerifyScript(goodsig2, scriptPubKey12, txTo12, 0, nUnused, 0)); CScript badsig1 = sign_multisig(scriptPubKey12, key3, txTo12); - BOOST_CHECK(!VerifyScript(badsig1, scriptPubKey12, txTo12, 0, 0)); + BOOST_CHECK(!VerifyScript(badsig1, scriptPubKey12, txTo12, 0, nUnused, 0)); } BOOST_AUTO_TEST_CASE(script_CHECKMULTISIG23) { + int nUnused = 0; CKey key1, key2, key3, key4; key1.MakeNewKey(); key2.MakeNewKey(); @@ -127,46 +131,46 @@ BOOST_AUTO_TEST_CASE(script_CHECKMULTISIG23) std::vector keys; keys.push_back(key1); keys.push_back(key2); CScript goodsig1 = sign_multisig(scriptPubKey23, keys, txTo23); - BOOST_CHECK(VerifyScript(goodsig1, scriptPubKey23, txTo23, 0, 0)); + BOOST_CHECK(VerifyScript(goodsig1, scriptPubKey23, txTo23, 0, nUnused, 0)); keys.clear(); keys.push_back(key1); keys.push_back(key3); CScript goodsig2 = sign_multisig(scriptPubKey23, keys, txTo23); - BOOST_CHECK(VerifyScript(goodsig2, scriptPubKey23, txTo23, 0, 0)); + BOOST_CHECK(VerifyScript(goodsig2, scriptPubKey23, txTo23, 0, nUnused, 0)); keys.clear(); keys.push_back(key2); keys.push_back(key3); CScript goodsig3 = sign_multisig(scriptPubKey23, keys, txTo23); - BOOST_CHECK(VerifyScript(goodsig3, scriptPubKey23, txTo23, 0, 0)); + BOOST_CHECK(VerifyScript(goodsig3, scriptPubKey23, txTo23, 0, nUnused, 0)); keys.clear(); keys.push_back(key2); keys.push_back(key2); // Can't re-use sig CScript badsig1 = sign_multisig(scriptPubKey23, keys, txTo23); - BOOST_CHECK(!VerifyScript(badsig1, scriptPubKey23, txTo23, 0, 0)); + BOOST_CHECK(!VerifyScript(badsig1, scriptPubKey23, txTo23, 0, nUnused, 0)); keys.clear(); keys.push_back(key2); keys.push_back(key1); // sigs must be in correct order CScript badsig2 = sign_multisig(scriptPubKey23, keys, txTo23); - BOOST_CHECK(!VerifyScript(badsig2, scriptPubKey23, txTo23, 0, 0)); + BOOST_CHECK(!VerifyScript(badsig2, scriptPubKey23, txTo23, 0, nUnused, 0)); keys.clear(); keys.push_back(key3); keys.push_back(key2); // sigs must be in correct order CScript badsig3 = sign_multisig(scriptPubKey23, keys, txTo23); - BOOST_CHECK(!VerifyScript(badsig3, scriptPubKey23, txTo23, 0, 0)); + BOOST_CHECK(!VerifyScript(badsig3, scriptPubKey23, txTo23, 0, nUnused, 0)); keys.clear(); keys.push_back(key4); keys.push_back(key2); // sigs must match pubkeys CScript badsig4 = sign_multisig(scriptPubKey23, keys, txTo23); - BOOST_CHECK(!VerifyScript(badsig4, scriptPubKey23, txTo23, 0, 0)); + BOOST_CHECK(!VerifyScript(badsig4, scriptPubKey23, txTo23, 0, nUnused, 0)); keys.clear(); keys.push_back(key1); keys.push_back(key4); // sigs must match pubkeys CScript badsig5 = sign_multisig(scriptPubKey23, keys, txTo23); - BOOST_CHECK(!VerifyScript(badsig5, scriptPubKey23, txTo23, 0, 0)); + BOOST_CHECK(!VerifyScript(badsig5, scriptPubKey23, txTo23, 0, nUnused, 0)); keys.clear(); // Must have signatures CScript badsig6 = sign_multisig(scriptPubKey23, keys, txTo23); - BOOST_CHECK(!VerifyScript(badsig6, scriptPubKey23, txTo23, 0, 0)); + BOOST_CHECK(!VerifyScript(badsig6, scriptPubKey23, txTo23, 0, nUnused, 0)); } diff --git a/src/wallet.cpp b/src/wallet.cpp index a662111d..5d44b1fb 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -42,6 +42,15 @@ bool CWallet::AddCryptedKey(const vector &vchPubKey, const vector return false; } +bool CWallet::AddCScript(const uint160 &hash, const std::vector& data) +{ + if (!CCryptoKeyStore::AddCScript(hash, data)) + return false; + if (!fFileBacked) + return true; + return CWalletDB(strWalletFile).WriteCScript(hash, data); +} + bool CWallet::Unlock(const SecureString& strWalletPassphrase) { if (!IsLocked()) @@ -374,6 +383,16 @@ int64 CWallet::GetDebit(const CTxIn &txin) const return 0; } +bool CWallet::IsChange(const CTxOut& txout) const +{ + CBitcoinAddress address; + if (ExtractAddress(txout.scriptPubKey, this, address) && !address.IsScript()) + CRITICAL_BLOCK(cs_wallet) + if (!mapAddressBook.count(address)) + return true; + return false; +} + int64 CWalletTx::GetTxTime() const { return nTimeReceived; @@ -443,8 +462,7 @@ void CWalletTx::GetAmounts(int64& nGeneratedImmature, int64& nGeneratedMature, l nFee = nDebit - nValueOut; } - // Sent/received. Standard client will never generate a send-to-multiple-recipients, - // but non-standard clients might (so return a list of address/amount pairs) + // Sent/received. BOOST_FOREACH(const CTxOut& txout, vout) { CBitcoinAddress address; diff --git a/src/wallet.h b/src/wallet.h index 78f055a6..34090ec8 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -69,6 +69,8 @@ public: bool AddCryptedKey(const std::vector &vchPubKey, const std::vector &vchCryptedSecret); // Adds an encrypted key to the store, without saving it to disk (used by LoadWallet) bool LoadCryptedKey(const std::vector &vchPubKey, const std::vector &vchCryptedSecret) { return CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret); } + bool AddCScript(const uint160& hash, const std::vector& data); + bool LoadCScript(const uint160& hash, const std::vector& data) { return CCryptoKeyStore::AddCScript(hash, data); } bool Unlock(const SecureString& strWalletPassphrase); bool ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase); @@ -114,15 +116,7 @@ public: throw std::runtime_error("CWallet::GetCredit() : value out of range"); return (IsMine(txout) ? txout.nValue : 0); } - bool IsChange(const CTxOut& txout) const - { - CBitcoinAddress address; - if (ExtractAddress(txout.scriptPubKey, this, address)) - CRITICAL_BLOCK(cs_wallet) - if (!mapAddressBook.count(address)) - return true; - return false; - } + bool IsChange(const CTxOut& txout) const; int64 GetChange(const CTxOut& txout) const { if (!MoneyRange(txout.nValue))