From 7e55c1ab650163011a690daf74cb1cc89bdd563b Mon Sep 17 00:00:00 2001 From: Gavin Andresen Date: Thu, 29 Sep 2011 09:46:52 -0400 Subject: [PATCH 01/12] Collapse no-op ExtractAddress/ExtractAddressInner --- src/script.cpp | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/script.cpp b/src/script.cpp index 12d3f9e83..4b2dc9a1a 100644 --- a/src/script.cpp +++ b/src/script.cpp @@ -1117,7 +1117,7 @@ bool IsMine(const CKeyStore &keystore, const CScript& scriptPubKey) return true; } -bool static ExtractAddressInner(const CScript& scriptPubKey, const CKeyStore* keystore, CBitcoinAddress& addressRet) +bool ExtractAddress(const CScript& scriptPubKey, const CKeyStore* keystore, CBitcoinAddress& addressRet) { vector > vSolution; if (!Solver(scriptPubKey, vSolution)) @@ -1137,15 +1137,6 @@ bool static ExtractAddressInner(const CScript& scriptPubKey, const CKeyStore* ke } -bool ExtractAddress(const CScript& scriptPubKey, const CKeyStore* keystore, CBitcoinAddress& addressRet) -{ - if (keystore) - return ExtractAddressInner(scriptPubKey, keystore, addressRet); - else - return ExtractAddressInner(scriptPubKey, NULL, addressRet); - return false; -} - bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CTransaction& txTo, unsigned int nIn, int nHashType) { From 1466b8b78ad8cabf93ac3f65f5929213c5dd3c8f Mon Sep 17 00:00:00 2001 From: Gavin Andresen Date: Tue, 11 Oct 2011 19:50:06 -0400 Subject: [PATCH 02/12] Rework unit tests so test_bitcoin.cpp does not #include them all --- src/makefile.osx | 5 ++++- src/makefile.unix | 6 ++++-- src/test/DoS_tests.cpp | 7 ++++--- src/test/base58_tests.cpp | 4 +++- src/test/base64_tests.cpp | 12 +++++++----- src/test/miner_tests.cpp | 2 +- src/test/script_tests.cpp | 4 ++-- src/test/test_bitcoin.cpp | 15 ++------------- src/test/transaction_tests.cpp | 4 ++-- src/test/uint160_tests.cpp | 2 +- src/test/uint256_tests.cpp | 2 +- src/test/util_tests.cpp | 4 +++- 12 files changed, 34 insertions(+), 33 deletions(-) diff --git a/src/makefile.osx b/src/makefile.osx index 4b0b521a3..bd72e6bc0 100644 --- a/src/makefile.osx +++ b/src/makefile.osx @@ -10,6 +10,7 @@ CXX=llvm-g++ DEPSDIR=/opt/local INCLUDEPATHS= \ + -I"$(CURDIR)" \ -I"$(DEPSDIR)/include" \ -I"$(DEPSDIR)/include/db48" @@ -112,6 +113,8 @@ obj/nogui/%.o: %.cpp bitcoind: $(OBJS:obj/%=obj/nogui/%) $(CXX) $(CFLAGS) -o $@ $(LIBPATHS) $^ $(LIBS) +TESTOBJS := $(patsubst test/%.cpp,obj/test/%.o,$(wildcard test/*.cpp)) + obj/test/%.o: test/%.cpp $(CXX) -c $(CFLAGS) -MMD -o $@ $< @cp $(@:%.o=%.d) $(@:%.o=%.P); \ @@ -119,7 +122,7 @@ obj/test/%.o: test/%.cpp -e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \ rm -f $(@:%.o=%.d) -test_bitcoin: obj/test/test_bitcoin.o $(filter-out obj/nogui/init.o,$(OBJS:obj/%=obj/nogui/%)) +test_bitcoin: $(TESTOBJS) $(filter-out obj/nogui/init.o,$(OBJS:obj/%=obj/nogui/%)) $(CXX) $(CFLAGS) -o $@ $(LIBPATHS) $^ $(LIBS) $(DEPSDIR)/lib/libboost_unit_test_framework-mt.a clean: diff --git a/src/makefile.unix b/src/makefile.unix index a436f968b..055f7ab29 100644 --- a/src/makefile.unix +++ b/src/makefile.unix @@ -6,7 +6,7 @@ USE_UPNP:=0 DEFS=-DNOPCH -DEFS += $(addprefix -I,$(BOOST_INCLUDE_PATH) $(BDB_INCLUDE_PATH) $(OPENSSL_INCLUDE_PATH)) +DEFS += $(addprefix -I,$(CURDIR) $(BOOST_INCLUDE_PATH) $(BDB_INCLUDE_PATH) $(OPENSSL_INCLUDE_PATH)) LIBS += $(addprefix -l,$(BOOST_LIB_PATH) $(BDB_LIB_PATH) $(OPENSSL_LIB_PATH)) LMODE = dynamic @@ -141,6 +141,8 @@ obj/nogui/%.o: %.cpp bitcoind: $(OBJS:obj/%=obj/nogui/%) $(CXX) $(xCXXFLAGS) -o $@ $^ $(LDFLAGS) $(LIBS) +TESTOBJS := $(patsubst test/%.cpp,obj/test/%.o,$(wildcard test/*.cpp)) + obj/test/%.o: test/%.cpp $(CXX) -c $(xCXXFLAGS) -MMD -o $@ $< @cp $(@:%.o=%.d) $(@:%.o=%.P); \ @@ -148,7 +150,7 @@ obj/test/%.o: test/%.cpp -e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \ rm -f $(@:%.o=%.d) -test_bitcoin: obj/test/test_bitcoin.o $(filter-out obj/nogui/init.o,$(OBJS:obj/%=obj/nogui/%)) +test_bitcoin: $(TESTOBJS) $(filter-out obj/nogui/init.o,$(OBJS:obj/%=obj/nogui/%)) $(CXX) $(xCXXFLAGS) -o $@ $(LIBPATHS) $^ -Wl,-Bstatic -lboost_unit_test_framework $(LDFLAGS) $(LIBS) clean: diff --git a/src/test/DoS_tests.cpp b/src/test/DoS_tests.cpp index 01e669125..e9b7b4517 100644 --- a/src/test/DoS_tests.cpp +++ b/src/test/DoS_tests.cpp @@ -5,9 +5,10 @@ #include #include -#include "../main.h" -#include "../net.h" -#include "../util.h" +#include "main.h" +#include "wallet.h" +#include "net.h" +#include "util.h" using namespace std; diff --git a/src/test/base58_tests.cpp b/src/test/base58_tests.cpp index c7fa74e96..d52ac7498 100644 --- a/src/test/base58_tests.cpp +++ b/src/test/base58_tests.cpp @@ -1,6 +1,8 @@ #include -#include "../util.h" +#include "main.h" +#include "wallet.h" +#include "util.h" BOOST_AUTO_TEST_SUITE(base58_tests) diff --git a/src/test/base64_tests.cpp b/src/test/base64_tests.cpp index f30f7f893..fff30ef5e 100644 --- a/src/test/base64_tests.cpp +++ b/src/test/base64_tests.cpp @@ -1,18 +1,20 @@ #include -#include "../util.h" +#include "main.h" +#include "wallet.h" +#include "util.h" BOOST_AUTO_TEST_SUITE(base64_tests) BOOST_AUTO_TEST_CASE(base64_testvectors) { - static const string vstrIn[] = {"","f","fo","foo","foob","fooba","foobar"}; - static const string vstrOut[] = {"","Zg==","Zm8=","Zm9v","Zm9vYg==","Zm9vYmE=","Zm9vYmFy"}; + static const std::string vstrIn[] = {"","f","fo","foo","foob","fooba","foobar"}; + static const std::string vstrOut[] = {"","Zg==","Zm8=","Zm9v","Zm9vYg==","Zm9vYmE=","Zm9vYmFy"}; for (int i=0; i -#include "../uint256.h" +#include "uint256.h" extern void SHA256Transform(void* pstate, void* pinput, const void* pinit); diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index 13feb86b9..f3fa5c3a1 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -2,8 +2,8 @@ #include #include -#include "../main.h" -#include "../wallet.h" +#include "main.h" +#include "wallet.h" using namespace std; extern uint256 SignatureHash(CScript scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType); diff --git a/src/test/test_bitcoin.cpp b/src/test/test_bitcoin.cpp index 39a7c88e1..c7f45a087 100644 --- a/src/test/test_bitcoin.cpp +++ b/src/test/test_bitcoin.cpp @@ -1,19 +1,8 @@ #define BOOST_TEST_MODULE Bitcoin Test Suite #include -#include "../main.h" -#include "../wallet.h" - -#include "uint160_tests.cpp" -#include "uint256_tests.cpp" -#include "script_tests.cpp" -#include "transaction_tests.cpp" -#include "DoS_tests.cpp" -#include "base64_tests.cpp" -#include "util_tests.cpp" -#include "base58_tests.cpp" -#include "miner_tests.cpp" -#include "Checkpoints_tests.cpp" +#include "main.h" +#include "wallet.h" CWallet* pwalletMain; diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index e6eb0f054..3268343bb 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -1,7 +1,7 @@ #include -#include "../main.h" -#include "../wallet.h" +#include "main.h" +#include "wallet.h" using namespace std; diff --git a/src/test/uint160_tests.cpp b/src/test/uint160_tests.cpp index 42c8275af..35cb35b25 100644 --- a/src/test/uint160_tests.cpp +++ b/src/test/uint160_tests.cpp @@ -1,6 +1,6 @@ #include -#include "../uint256.h" +#include "uint256.h" BOOST_AUTO_TEST_SUITE(uint160_tests) diff --git a/src/test/uint256_tests.cpp b/src/test/uint256_tests.cpp index c5d45e215..efdc8a6ae 100644 --- a/src/test/uint256_tests.cpp +++ b/src/test/uint256_tests.cpp @@ -1,6 +1,6 @@ #include -#include "../uint256.h" +#include "uint256.h" BOOST_AUTO_TEST_SUITE(uint256_tests) diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index 8c8b99e1b..9571c4738 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -2,7 +2,9 @@ #include #include -#include "../util.h" +#include "main.h" +#include "wallet.h" +#include "util.h" using namespace std; From bf798734db4539a39edd6badf54a1c3aecf193e5 Mon Sep 17 00:00:00 2001 From: Gavin Andresen Date: Wed, 28 Sep 2011 12:30:06 -0400 Subject: [PATCH 03/12] Support 3 new multisignature IsStandard transactions Initial support for (a and b), (a or b), and 2-of-3 escrow transactions (where a, b, and c are keys). --- src/bitcoinrpc.cpp | 118 ++++++++++++++- src/script.cpp | 223 +++++++++++++++++++--------- src/script.h | 25 ++-- src/test/multisig_tests.cpp | 288 ++++++++++++++++++++++++++++++++++++ src/wallet.cpp | 9 +- 5 files changed, 575 insertions(+), 88 deletions(-) create mode 100644 src/test/multisig_tests.cpp diff --git a/src/bitcoinrpc.cpp b/src/bitcoinrpc.cpp index 1f05fa862..122bf61b8 100644 --- a/src/bitcoinrpc.cpp +++ b/src/bitcoinrpc.cpp @@ -936,6 +936,101 @@ Value sendmany(const Array& params, bool fHelp) return wtx.GetHash().GetHex(); } +Value sendmultisig(const Array& params, bool fHelp) +{ + if (fHelp || params.size() < 4 || params.size() > 7) + { + 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"; + 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"); + + // 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) + throw runtime_error( + strprintf("sendmultisig: wrong number of keys (got %d, need %d)", keys.size(), nKeysNeeded)); + std::vector pubkeys; + pubkeys.resize(nKeysNeeded); + for (int i = 0; i < nKeysNeeded; i++) + { + const std::string& ks = keys[i].get_str(); + if (ks.size() == 130) // hex public key + pubkeys[i].SetPubKey(ParseHex(ks)); + else if (ks.size() > 34) // base58-encoded + { + std::vector vchPubKey; + if (DecodeBase58(ks, vchPubKey)) + pubkeys[i].SetPubKey(vchPubKey); + else + throw runtime_error("Error base58 decoding key: "+ks); + } + else // bitcoin address for key in this wallet + { + CBitcoinAddress address(ks); + if (!pwalletMain->GetKey(address, pubkeys[i])) + throw runtime_error( + strprintf("sendmultisig: 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); + + 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"); + + return wtx.GetHash().GetHex(); +} + struct tallyitem { @@ -1596,7 +1691,17 @@ Value validateaddress(const Array& params, bool fHelp) // version of the address: string currentAddress = address.ToString(); ret.push_back(Pair("address", currentAddress)); - ret.push_back(Pair("ismine", (pwalletMain->HaveKey(address) > 0))); + if (pwalletMain->HaveKey(address)) + { + ret.push_back(Pair("ismine", true)); + std::vector vchPubKey; + pwalletMain->GetPubKey(address, vchPubKey); + ret.push_back(Pair("pubkey", HexStr(vchPubKey))); + std::string strPubKey(vchPubKey.begin(), vchPubKey.end()); + ret.push_back(Pair("pubkey58", EncodeBase58(vchPubKey))); + } + else + ret.push_back(Pair("ismine", false)); if (pwalletMain->mapAddressBook.count(address)) ret.push_back(Pair("account", pwalletMain->mapAddressBook[address])); } @@ -1841,6 +1946,7 @@ pair pCallTable[] = make_pair("move", &movecmd), make_pair("sendfrom", &sendfrom), make_pair("sendmany", &sendmany), + make_pair("sendmultisig", &sendmultisig), make_pair("gettransaction", &gettransaction), make_pair("listtransactions", &listtransactions), make_pair("signmessage", &signmessage), @@ -2484,6 +2590,16 @@ int CommandLineRPC(int argc, char *argv[]) params[1] = v.get_obj(); } if (strMethod == "sendmany" && n > 2) ConvertTo(params[2]); + if (strMethod == "sendmultisig" && n > 2) + { + string s = params[2].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(); + } + 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/script.cpp b/src/script.cpp index 4b2dc9a1a..6a7913b0d 100644 --- a/src/script.cpp +++ b/src/script.cpp @@ -963,8 +963,11 @@ bool CheckSig(vector vchSig, vector vchPubKey, CSc - -bool Solver(const CScript& scriptPubKey, vector >& vSolutionRet) +// +// Returns lists of public keys (or public key hashes), any one of which can +// satisfy scriptPubKey +// +bool Solver(const CScript& scriptPubKey, vector > >& vSolutionsRet) { // Templates static vector vTemplates; @@ -975,13 +978,24 @@ bool Solver(const CScript& scriptPubKey, vector >& vSo // 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); + + // 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 three pubkeys, receiver provides 2 of 3 signatures. + vTemplates.push_back(CScript() << OP_2 << OP_PUBKEY << OP_PUBKEY << OP_PUBKEY << OP_3 << OP_CHECKMULTISIG); } // Scan templates const CScript& script1 = scriptPubKey; BOOST_FOREACH(const CScript& script2, vTemplates) { - vSolutionRet.clear(); + vSolutionsRet.clear(); + + vector > currentSolution; opcodetype opcode1, opcode2; vector vch1, vch2; @@ -992,9 +1006,7 @@ bool Solver(const CScript& scriptPubKey, vector >& vSo { if (pc1 == script1.end() && pc2 == script2.end()) { - // Found a match - reverse(vSolutionRet.begin(), vSolutionRet.end()); - return true; + return !vSolutionsRet.empty(); } if (!script1.GetOp(pc1, opcode1, vch1)) break; @@ -1004,13 +1016,54 @@ bool Solver(const CScript& scriptPubKey, vector >& vSo { if (vch1.size() < 33 || vch1.size() > 120) break; - vSolutionRet.push_back(make_pair(opcode2, vch1)); + currentSolution.push_back(make_pair(opcode2, vch1)); } else if (opcode2 == OP_PUBKEYHASH) { if (vch1.size() != sizeof(uint160)) break; - vSolutionRet.push_back(make_pair(opcode2, vch1)); + currentSolution.push_back(make_pair(opcode2, vch1)); + } + else if (opcode2 == OP_CHECKSIG) + { + vSolutionsRet.push_back(currentSolution); + currentSolution.clear(); + } + 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) + { + 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(); + } } else if (opcode1 != opcode2 || vch1 != vch2) { @@ -1019,7 +1072,7 @@ bool Solver(const CScript& scriptPubKey, vector >& vSo } } - vSolutionRet.clear(); + vSolutionsRet.clear(); return false; } @@ -1028,51 +1081,61 @@ bool Solver(const CKeyStore& keystore, const CScript& scriptPubKey, uint256 hash { scriptSigRet.clear(); - vector > vSolution; - if (!Solver(scriptPubKey, vSolution)) + vector > > vSolutions; + if (!Solver(scriptPubKey, vSolutions)) return false; - // Compile solution - BOOST_FOREACH(PAIRTYPE(opcodetype, valtype)& item, vSolution) + // See if we have all the keys for any of the solutions: + int whichSolution = -1; + for (int i = 0; i < vSolutions.size(); i++) { - if (item.first == OP_PUBKEY) + int keysFound = 0; + CScript scriptSig; + + BOOST_FOREACH(PAIRTYPE(opcodetype, valtype)& item, vSolutions[i]) { - // Sign - const valtype& vchPubKey = item.second; - CKey key; - if (!keystore.GetKey(Hash160(vchPubKey), key)) - return false; - if (key.GetPubKey() != vchPubKey) - return false; - if (hash != 0) + if (item.first == OP_PUBKEY) { + const valtype& vchPubKey = item.second; + CKey key; vector vchSig; - if (!key.Sign(hash, vchSig)) - return false; - vchSig.push_back((unsigned char)nHashType); - scriptSigRet << 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) - { - // Sign and give pubkey - CKey key; - if (!keystore.GetKey(uint160(item.second), key)) - return false; - if (hash != 0) + else if (item.first == OP_PUBKEYHASH) { + CKey key; vector vchSig; - if (!key.Sign(hash, vchSig)) - return false; - vchSig.push_back((unsigned char)nHashType); - scriptSigRet << vchSig << key.GetPubKey(); + if (keystore.GetKey(uint160(item.second), key) + && hash != 0 && key.Sign(hash, vchSig)) + { + vchSig.push_back((unsigned char)nHashType); + scriptSig << vchSig << key.GetPubKey(); + ++keysFound; + } } } - else + if (keysFound == vSolutions[i].size()) { - return false; + whichSolution = i; + scriptSigRet = scriptSig; + break; } } + if (whichSolution == -1) + return false; + + // CHECKMULTISIG bug workaround: + if (vSolutions.size() != 1 || + vSolutions[0].size() != 1) + { + scriptSigRet.insert(scriptSigRet.begin(), OP_0); + } return true; } @@ -1080,51 +1143,59 @@ bool Solver(const CKeyStore& keystore, const CScript& scriptPubKey, uint256 hash bool IsStandard(const CScript& scriptPubKey) { - vector > vSolution; - return Solver(scriptPubKey, vSolution); + vector > > vSolutions; + return Solver(scriptPubKey, vSolutions); } bool IsMine(const CKeyStore &keystore, const CScript& scriptPubKey) { - vector > vSolution; - if (!Solver(scriptPubKey, vSolution)) + vector > > vSolutions; + if (!Solver(scriptPubKey, vSolutions)) return false; - // Compile solution - BOOST_FOREACH(PAIRTYPE(opcodetype, valtype)& item, vSolution) + int keysFound = 0; + int keysRequired = 0; + for (int i = 0; i < vSolutions.size(); i++) { - if (item.first == OP_PUBKEY) - { - const valtype& vchPubKey = item.second; - vector vchPubKeyFound; - if (!keystore.GetPubKey(Hash160(vchPubKey), vchPubKeyFound)) - return false; - if (vchPubKeyFound != vchPubKey) - return false; - } - else if (item.first == OP_PUBKEYHASH) - { - if (!keystore.HaveKey(uint160(item.second))) - return false; - } - else + BOOST_FOREACH(PAIRTYPE(opcodetype, valtype)& item, vSolutions[i]) { - return false; + ++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; + } } } - return true; + // 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); } bool ExtractAddress(const CScript& scriptPubKey, const CKeyStore* keystore, CBitcoinAddress& addressRet) { - vector > vSolution; - if (!Solver(scriptPubKey, vSolution)) + vector > > vSolutions; + if (!Solver(scriptPubKey, vSolutions)) return false; - BOOST_FOREACH(PAIRTYPE(opcodetype, valtype)& item, vSolution) + for (int i = 0; i < vSolutions.size(); i++) { + 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) @@ -1132,7 +1203,6 @@ bool ExtractAddress(const CScript& scriptPubKey, const CKeyStore* keystore, CBit if (keystore == NULL || keystore->HaveKey(addressRet)) return true; } - return false; } @@ -1192,3 +1262,22 @@ bool VerifySignature(const CTransaction& txFrom, const CTransaction& txTo, unsig return true; } + +void CScript::SetMultisigAnd(const std::vector& keys) +{ + assert(keys.size() >= 2); + this->clear(); + *this << OP_2 << keys[0].GetPubKey() << keys[1].GetPubKey() << OP_2 << OP_CHECKMULTISIG; +} +void CScript::SetMultisigOr(const std::vector& keys) +{ + assert(keys.size() >= 2); + this->clear(); + *this << OP_1 << keys[0].GetPubKey() << keys[1].GetPubKey() << OP_2 << OP_CHECKMULTISIG; +} +void CScript::SetMultisigEscrow(const std::vector& keys) +{ + assert(keys.size() >= 3); + this->clear(); + *this << OP_2 << keys[0].GetPubKey() << keys[1].GetPubKey() << keys[1].GetPubKey() << OP_3 << OP_CHECKMULTISIG; +} diff --git a/src/script.h b/src/script.h index e61ea2fd7..a5a1e1868 100644 --- a/src/script.h +++ b/src/script.h @@ -574,6 +574,13 @@ public: return true; } + static int DecodeOP_N(opcodetype opcode) + { + if (opcode == OP_0) + return 0; + assert(opcode >= OP_1 && opcode <= OP_16); + return (int)opcode - (int)(OP_1 - 1); + } void FindAndDelete(const CScript& b) { @@ -625,21 +632,6 @@ public: } - CBitcoinAddress GetBitcoinAddress() const - { - opcodetype opcode; - std::vector vch; - CScript::const_iterator pc = begin(); - if (!GetOp(pc, opcode, vch) || opcode != OP_DUP) return 0; - if (!GetOp(pc, opcode, vch) || opcode != OP_HASH160) return 0; - if (!GetOp(pc, opcode, vch) || vch.size() != sizeof(uint160)) return 0; - uint160 hash160 = uint160(vch); - if (!GetOp(pc, opcode, vch) || opcode != OP_EQUALVERIFY) return 0; - if (!GetOp(pc, opcode, vch) || opcode != OP_CHECKSIG) return 0; - if (pc != end()) return 0; - return CBitcoinAddress(hash160); - } - void SetBitcoinAddress(const CBitcoinAddress& address) { this->clear(); @@ -650,6 +642,9 @@ public: { SetBitcoinAddress(CBitcoinAddress(vchPubKey)); } + void SetMultisigAnd(const std::vector& keys); + void SetMultisigOr(const std::vector& keys); + void SetMultisigEscrow(const std::vector& keys); void PrintHex() const diff --git a/src/test/multisig_tests.cpp b/src/test/multisig_tests.cpp new file mode 100644 index 000000000..459d11236 --- /dev/null +++ b/src/test/multisig_tests.cpp @@ -0,0 +1,288 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "keystore.h" +#include "main.h" +#include "script.h" +#include "wallet.h" + +using namespace std; +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); + +BOOST_AUTO_TEST_SUITE(multisig_tests) + +CScript +sign_multisig(CScript scriptPubKey, vector keys, CTransaction transaction, int whichIn) +{ + uint256 hash = SignatureHash(scriptPubKey, transaction, whichIn, SIGHASH_ALL); + + CScript result; + result << OP_0; // CHECKMULTISIG bug workaround + BOOST_FOREACH(CKey key, keys) + { + vector vchSig; + BOOST_CHECK(key.Sign(hash, vchSig)); + vchSig.push_back((unsigned char)SIGHASH_ALL); + result << vchSig; + } + return result; +} + +BOOST_AUTO_TEST_CASE(multisig_verify) +{ + CKey key[4]; + for (int i = 0; i < 4; i++) + key[i].MakeNewKey(); + + CScript a_and_b; + a_and_b << OP_2 << key[0].GetPubKey() << key[1].GetPubKey() << OP_2 << OP_CHECKMULTISIG; + + CScript a_or_b; + a_or_b << OP_1 << key[0].GetPubKey() << key[1].GetPubKey() << OP_2 << OP_CHECKMULTISIG; + + CScript escrow; + escrow << OP_2 << key[0].GetPubKey() << key[1].GetPubKey() << key[2].GetPubKey() << OP_3 << OP_CHECKMULTISIG; + + CTransaction txFrom; // Funding transaction + txFrom.vout.resize(3); + txFrom.vout[0].scriptPubKey = a_and_b; + txFrom.vout[1].scriptPubKey = a_or_b; + txFrom.vout[2].scriptPubKey = escrow; + + CTransaction txTo[3]; // Spending transaction + for (int i = 0; i < 3; 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; + } + + vector keys; + CScript s; + + // 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)); + + 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)); + + 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)); + } + + // Test a OR b: + for (int i = 0; i < 4; i++) + { + keys.clear(); + 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)); + else + BOOST_CHECK_MESSAGE(!VerifyScript(s, a_or_b, txTo[1], 0, 0), strprintf("a|b: %d", i)); + } + s.clear(); + s << OP_0 << OP_0; + BOOST_CHECK(!VerifyScript(s, a_or_b, txTo[1], 0, 0)); + s.clear(); + s << OP_0 << OP_1; + BOOST_CHECK(!VerifyScript(s, a_or_b, txTo[1], 0, 0)); + + + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + { + keys.clear(); + 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)); + else + BOOST_CHECK_MESSAGE(!VerifyScript(s, escrow, txTo[2], 0, 0), strprintf("escrow 2: %d %d", i, j)); + } +} + +BOOST_AUTO_TEST_CASE(multisig_IsStandard) +{ + CKey key[3]; + for (int i = 0; i < 3; i++) + key[i].MakeNewKey(); + + CScript a_and_b; + a_and_b << OP_2 << key[0].GetPubKey() << key[1].GetPubKey() << OP_2 << OP_CHECKMULTISIG; + BOOST_CHECK(::IsStandard(a_and_b)); + + CScript a_or_b; + a_or_b << OP_1 << key[0].GetPubKey() << key[1].GetPubKey() << OP_2 << OP_CHECKMULTISIG; + BOOST_CHECK(::IsStandard(a_or_b)); + + CScript escrow; + escrow << OP_2 << key[0].GetPubKey() << key[1].GetPubKey() << key[2].GetPubKey() << OP_3 << OP_CHECKMULTISIG; + BOOST_CHECK(::IsStandard(escrow)); +} + +BOOST_AUTO_TEST_CASE(multisig_Solver1) +{ + // Tests Solver() that returns lists of keys that are + // required to satisfy a ScriptPubKey + // + // Also tests IsMine() and ExtractAddress() + // + // Note: ExtractAddress for the multisignature transactions + // always returns false for this release, even if you have + // one key that would satisfy an (a|b) or 2-of-3 keys needed + // to spend an escrow transaction. + // + CBasicKeyStore keystore, emptykeystore; + CKey key[3]; + CBitcoinAddress keyaddr[3]; + for (int i = 0; i < 3; i++) + { + key[i].MakeNewKey(); + keystore.AddKey(key[i]); + keyaddr[i].SetPubKey(key[i].GetPubKey()); + } + + { + vector > > solutions; + CScript s; + s << key[0].GetPubKey() << OP_CHECKSIG; + BOOST_CHECK(Solver(s, 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]); + BOOST_CHECK(IsMine(keystore, s)); + BOOST_CHECK(!IsMine(emptykeystore, s)); + } + { + vector > > solutions; + CScript s; + s << OP_DUP << OP_HASH160 << Hash160(key[0].GetPubKey()) << OP_EQUALVERIFY << OP_CHECKSIG; + BOOST_CHECK(Solver(s, 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]); + BOOST_CHECK(IsMine(keystore, s)); + BOOST_CHECK(!IsMine(emptykeystore, s)); + } + { + vector > > solutions; + 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); + CBitcoinAddress addr; + BOOST_CHECK(!ExtractAddress(s, &keystore, addr)); + BOOST_CHECK(IsMine(keystore, s)); + BOOST_CHECK(!IsMine(emptykeystore, s)); + } + { + vector > > solutions; + 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(IsMine(keystore, s)); + BOOST_CHECK(!IsMine(emptykeystore, s)); + } + { + vector > > solutions; + 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_AUTO_TEST_CASE(multisig_Sign) +{ + // 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]); + } + + CScript a_and_b; + a_and_b << OP_2 << key[0].GetPubKey() << key[1].GetPubKey() << OP_2 << OP_CHECKMULTISIG; + + CScript a_or_b; + a_or_b << OP_1 << key[0].GetPubKey() << key[1].GetPubKey() << OP_2 << OP_CHECKMULTISIG; + + CScript escrow; + escrow << OP_2 << key[0].GetPubKey() << key[1].GetPubKey() << key[2].GetPubKey() << OP_3 << OP_CHECKMULTISIG; + + CTransaction txFrom; // Funding transaction + txFrom.vout.resize(3); + txFrom.vout[0].scriptPubKey = a_and_b; + txFrom.vout[1].scriptPubKey = a_or_b; + txFrom.vout[2].scriptPubKey = escrow; + + CTransaction txTo[3]; // Spending transaction + for (int i = 0; i < 3; 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; + } + + for (int i = 0; i < 3; i++) + { + BOOST_CHECK_MESSAGE(SignSignature(keystore, txFrom, txTo[i], 0), strprintf("SignSignature %d", i)); + } +} + + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/wallet.cpp b/src/wallet.cpp index 87f5dfd65..a662111d4 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -997,12 +997,11 @@ bool CWallet::CreateTransaction(const vector >& vecSend, CW vector vchPubKey = reservekey.GetReservedKey(); // assert(mapKeys.count(vchPubKey)); - // Fill a vout to ourself, using same address type as the payment + // Fill a vout to ourself + // TODO: pass in scriptChange instead of reservekey so + // change transaction isn't always pay-to-bitcoin-address CScript scriptChange; - if (vecSend[0].first.GetBitcoinAddress().IsValid()) - scriptChange.SetBitcoinAddress(vchPubKey); - else - scriptChange << vchPubKey << OP_CHECKSIG; + scriptChange.SetBitcoinAddress(vchPubKey); // Insert change txn at random position: vector::iterator position = wtxNew.vout.begin()+GetRandInt(wtxNew.vout.size()); From cc40ba2151a627b6da9af4932e0bee58e69aacb5 Mon Sep 17 00:00:00 2001 From: Gavin Andresen Date: Mon, 3 Oct 2011 16:14:13 -0400 Subject: [PATCH 04/12] Global fixture to send output to console instead of debug.log --- src/test/test_bitcoin.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/test/test_bitcoin.cpp b/src/test/test_bitcoin.cpp index c7f45a087..4b52b74cf 100644 --- a/src/test/test_bitcoin.cpp +++ b/src/test/test_bitcoin.cpp @@ -4,6 +4,16 @@ #include "main.h" #include "wallet.h" +extern bool fPrintToConsole; +struct TestingSetup { + TestingSetup() { + fPrintToConsole = true; // don't want to write to debug.log file + } + ~TestingSetup() { } +}; + +BOOST_GLOBAL_FIXTURE(TestingSetup); + CWallet* pwalletMain; void Shutdown(void* parg) From e679ec969c8b22c676ebb10bea1038f6c8f13b33 Mon Sep 17 00:00:00 2001 From: Gavin Andresen Date: Mon, 3 Oct 2011 13:05:43 -0400 Subject: [PATCH 05/12] 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 113fa14a5..8fc546c50 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 122bf61b8..3e42e7038 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 7195e065e..52ad695c7 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 15bfb29c8..99dd88b49 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 6cf557faf..c9b9b4a5d 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 3b91de6fb..942fb9ae9 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 052701e5f..0fb383f59 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 7f8da4e89..bf31234c2 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 6a7913b0d..c103d5751 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 a5a1e1868..ee0be02a8 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 459d11236..75c764dd6 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 000000000..857d04bc6 --- /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 f3fa5c3a1..3d1c21870 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 a662111d4..5d44b1fb8 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 78f055a60..34090ec80 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)) From d7062ef1bd3319689d402027131f2c46efeddcb6 Mon Sep 17 00:00:00 2001 From: Gavin Andresen Date: Thu, 13 Oct 2011 16:03:58 -0400 Subject: [PATCH 06/12] Put OP_EVAL string in coinbase of generated blocks --- src/base58.h | 10 +++++----- src/main.cpp | 7 +++++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/base58.h b/src/base58.h index 8fc546c50..4be119890 100644 --- a/src/base58.h +++ b/src/base58.h @@ -270,7 +270,7 @@ public: bool SetScriptHash160(const uint160& hash160) { - SetData(fTestNet ? 112 : 1, &hash160, 20); + SetData(fTestNet ? 111^2 : 2, &hash160, 20); return true; } @@ -284,7 +284,7 @@ public: nExpectedSize = 20; // Hash of public key fExpectTestNet = false; break; - case 1: + case 2: nExpectedSize = 20; // OP_EVAL, hash of CScript fExpectTestNet = false; break; @@ -293,7 +293,7 @@ public: nExpectedSize = 20; fExpectTestNet = true; break; - case 112: + case 111^2: nExpectedSize = 20; fExpectTestNet = true; break; @@ -308,8 +308,8 @@ public: if (!IsValid()) return false; if (fTestNet) - return nVersion == 112; - return nVersion == 1; + return nVersion == 111^2; + return nVersion == 2; } CBitcoinAddress() diff --git a/src/main.cpp b/src/main.cpp index 0fb383f59..1b5bf5280 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2999,6 +2999,13 @@ void IncrementExtraNonce(CBlock* pblock, CBlockIndex* pindexPrev, unsigned int& } ++nExtraNonce; pblock->vtx[0].vin[0].scriptSig = CScript() << pblock->nTime << CBigNum(nExtraNonce); + + // Put "OP_EVAL" in the coinbase so everybody can tell when + // a majority of miners support it + const char* pOpEvalName = GetOpName(OP_EVAL); + pblock->vtx[0].vin[0].scriptSig += CScript() << std::vector(pOpEvalName, pOpEvalName+strlen(pOpEvalName)); + assert(pblock->vtx[0].vin[0].scriptSig.size() <= 100); + pblock->hashMerkleRoot = pblock->BuildMerkleTree(); } From fae3e2aab646a4813c6b9ad28b4c9cf4330c1a59 Mon Sep 17 00:00:00 2001 From: Gavin Andresen Date: Wed, 19 Oct 2011 09:50:15 -0400 Subject: [PATCH 07/12] Disable addmultisigaddress if not testnet --- src/bitcoinrpc.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/bitcoinrpc.cpp b/src/bitcoinrpc.cpp index 3e42e7038..47fd9830a 100644 --- a/src/bitcoinrpc.cpp +++ b/src/bitcoinrpc.cpp @@ -946,6 +946,8 @@ Value addmultisigaddress(const Array& params, bool fHelp) "If [account] is specified, assign address to [account]."; throw runtime_error(msg); } + if (!fTestNet) + throw runtime_error("addmultisigaddress available only when running -testnet\n"); int nRequired = params[0].get_int(); const Array& keys = params[1].get_array(); From a0871afb2b1d6d358c833fd08bca2f13c840fd4d Mon Sep 17 00:00:00 2001 From: Gavin Andresen Date: Fri, 21 Oct 2011 13:12:05 -0400 Subject: [PATCH 08/12] Interpret OP_EVAL as OP_NOP until Feb 1, 2012 --- src/script.cpp | 12 +++++++ src/test/script_op_eval_tests.cpp | 54 +++++++++++++++++++++++++++++-- 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/src/script.cpp b/src/script.cpp index c103d5751..e60399120 100644 --- a/src/script.cpp +++ b/src/script.cpp @@ -1014,6 +1014,18 @@ bool EvalScriptInner(vector >& stack, const CScript& scrip case OP_EVAL: { + // This code should be removed when OP_EVAL has + // a majority of hashing power on the network. + // OP_EVAL behaves just like OP_NOP until + // opevaltime : + if (!fTestNet || fDebug) + { + // 1328054400 is Feb 1, 2012 + int64 nEvalSwitchTime = GetArg("opevaltime", 1328054400); + if (GetTime() < nEvalSwitchTime) + break; + } + // Evaluate the top item on the stack as a Script // [serialized script ] -- [result(s) of executing script] if (stack.size() < 1) diff --git a/src/test/script_op_eval_tests.cpp b/src/test/script_op_eval_tests.cpp index 857d04bc6..6c683b572 100644 --- a/src/test/script_op_eval_tests.cpp +++ b/src/test/script_op_eval_tests.cpp @@ -15,7 +15,22 @@ 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& nSigOps, int nHashType); -BOOST_AUTO_TEST_SUITE(script_op_eval_tests) +static const int64 nEvalSwitchover = 1328054400; + +struct CEvalFixture { + CEvalFixture() + { + // Set mock time to AFTER OP_EVAL deployed + SetMockTime(nEvalSwitchover+1); + } + ~CEvalFixture() + { + // Reset back to use-real-time + SetMockTime(0); + } +}; + +BOOST_FIXTURE_TEST_SUITE(script_op_eval_tests, CEvalFixture) BOOST_AUTO_TEST_CASE(script_op_eval1) { @@ -171,14 +186,16 @@ BOOST_AUTO_TEST_CASE(script_op_eval3) } } -BOOST_AUTO_TEST_CASE(script_op_eval_backcompat) +BOOST_AUTO_TEST_CASE(script_op_eval_backcompat1) { // Check backwards-incompatibility-testing code CScript returnsEleven; returnsEleven << OP_11; - // This will validate on new clients, but will + // This should validate on new clients, but will // be invalid on old clients (that interpret OP_EVAL as a no-op) + // ... except there's a special rule that makes new clients reject + // it. CScript fund; fund << OP_EVAL << OP_11 << OP_EQUAL; @@ -199,5 +216,36 @@ BOOST_AUTO_TEST_CASE(script_op_eval_backcompat) BOOST_CHECK(!VerifySignature(txFrom, txTo, 0, nUnused)); } +BOOST_AUTO_TEST_CASE(script_op_eval_switchover) +{ + // Use SetMockTime to test OP_EVAL switchover code + CScript notValid; + notValid << OP_11 << OP_12 << OP_EQUALVERIFY; + + // This will be valid under old rules, invalid under new: + CScript fund; + fund << OP_EVAL; + + 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 >(notValid); + txTo.vout[0].nValue = 1; + + SetMockTime(nEvalSwitchover-1); + + int nUnused = 0; + BOOST_CHECK(VerifyScript(txTo.vin[0].scriptSig, txFrom.vout[0].scriptPubKey, txTo, 0, nUnused, 0)); + + // After eval switchover time, it should validate: + SetMockTime(nEvalSwitchover); + BOOST_CHECK(!VerifyScript(txTo.vin[0].scriptSig, txFrom.vout[0].scriptPubKey, txTo, 0, nUnused, 0)); +} BOOST_AUTO_TEST_SUITE_END() From 2a45a494b0bec6a0f1fc6ab7f26c260b85e7ff3e Mon Sep 17 00:00:00 2001 From: Gavin Andresen Date: Tue, 8 Nov 2011 13:20:29 -0500 Subject: [PATCH 09/12] Use block times for 'hard' OP_EVAL switchover, and refactored EvalScript so it takes a flag for how to interpret OP_EVAL. Also increased IsStandard size of scriptSigs to 500 bytes, so a 3-of-3 multisig transaction IsStandard. --- src/bitcoinrpc.cpp | 4 +- src/db.cpp | 2 +- src/db.h | 25 ++++---- src/keystore.cpp | 17 +++--- src/keystore.h | 14 +++-- src/main.cpp | 52 +++++++++++------ src/main.h | 4 +- src/script.cpp | 95 +++++++++++++------------------ src/script.h | 14 ++--- src/test/multisig_tests.cpp | 31 +++++----- src/test/script_op_eval_tests.cpp | 39 ++++--------- src/test/script_tests.cpp | 38 ++++++------- src/wallet.cpp | 14 ++++- src/wallet.h | 5 +- 14 files changed, 177 insertions(+), 177 deletions(-) diff --git a/src/bitcoinrpc.cpp b/src/bitcoinrpc.cpp index 47fd9830a..1a1c99157 100644 --- a/src/bitcoinrpc.cpp +++ b/src/bitcoinrpc.cpp @@ -1674,10 +1674,10 @@ Value validateaddress(const Array& params, bool fHelp) pwalletMain->GetCScript(address.GetHash160(), subscript); ret.push_back(Pair("ismine", ::IsMine(*pwalletMain, subscript))); std::vector addresses; - txntype whichType; + txnouttype whichType; int nRequired; ExtractAddresses(subscript, pwalletMain, whichType, addresses, nRequired); - ret.push_back(Pair("script", GetTxnTypeName(whichType))); + ret.push_back(Pair("script", GetTxnOutputType(whichType))); Array a; BOOST_FOREACH(const CBitcoinAddress& addr, addresses) a.push_back(addr.ToString()); diff --git a/src/db.cpp b/src/db.cpp index 52ad695c7..f43b2a565 100644 --- a/src/db.cpp +++ b/src/db.cpp @@ -938,7 +938,7 @@ int CWalletDB::LoadWallet(CWallet* pwallet) { uint160 hash; ssKey >> hash; - std::vector script; + CScript script; ssValue >> script; if (!pwallet->LoadCScript(hash, script)) return DB_CORRUPT; diff --git a/src/db.h b/src/db.h index 99dd88b49..e593ae28e 100644 --- a/src/db.h +++ b/src/db.h @@ -13,17 +13,17 @@ #include -class CTxIndex; +class CAccount; +class CAccountingEntry; +class CAddress; +class CBlockLocator; class CDiskBlockIndex; class CDiskTxPos; +class CMasterKey; class COutPoint; -class CAddress; -class CWalletTx; +class CTxIndex; class CWallet; -class CAccount; -class CAccountingEntry; -class CBlockLocator; - +class CWalletTx; extern unsigned int nWalletDBUpdated; extern DbEnv dbenv; @@ -420,16 +420,17 @@ public: return Write(std::make_pair(std::string("mkey"), nID), kMasterKey, true); } - bool ReadCScript(const uint160 &hash, std::vector& data) + // Support for BIP 0013 : see https://en.bitcoin.it/wiki/BIP_0013 + bool ReadCScript(const uint160 &hash, CScript& redeemScript) { - data.clear(); - return Read(std::make_pair(std::string("cscript"), hash), data); + redeemScript.clear(); + return Read(std::make_pair(std::string("cscript"), hash), redeemScript); } - bool WriteCScript(const uint160& hash, const std::vector& data) + bool WriteCScript(const uint160& hash, const CScript& redeemScript) { nWalletDBUpdated++; - return Write(std::make_pair(std::string("cscript"), hash), data, false); + return Write(std::make_pair(std::string("cscript"), hash), redeemScript, false); } bool WriteBestBlock(const CBlockLocator& locator) diff --git a/src/keystore.cpp b/src/keystore.cpp index c9b9b4a5d..21fb0f91b 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -4,8 +4,9 @@ // file license.txt or http://www.opensource.org/licenses/mit-license.php. #include "headers.h" -#include "db.h" #include "crypter.h" +#include "db.h" +#include "script.h" std::vector CKeyStore::GenerateNewKey() { @@ -33,10 +34,10 @@ bool CBasicKeyStore::AddKey(const CKey& key) return true; } -bool CBasicKeyStore::AddCScript(const uint160 &hash, const std::vector& data) +bool CBasicKeyStore::AddCScript(const uint160 &hash, const CScript& redeemScript) { CRITICAL_BLOCK(cs_KeyStore) - mapData[hash] = data; + mapScripts[hash] = redeemScript; return true; } @@ -44,19 +45,19 @@ bool CBasicKeyStore::HaveCScript(const uint160& hash) const { bool result; CRITICAL_BLOCK(cs_KeyStore) - result = (mapData.count(hash) > 0); + result = (mapScripts.count(hash) > 0); return result; } -bool CBasicKeyStore::GetCScript(const uint160 &hash, std::vector& dataOut) const +bool CBasicKeyStore::GetCScript(const uint160 &hash, CScript& redeemScriptOut) const { CRITICAL_BLOCK(cs_KeyStore) { - DataMap::const_iterator mi = mapData.find(hash); - if (mi != mapData.end()) + ScriptMap::const_iterator mi = mapScripts.find(hash); + if (mi != mapScripts.end()) { - dataOut = (*mi).second; + redeemScriptOut = (*mi).second; return true; } } diff --git a/src/keystore.h b/src/keystore.h index 942fb9ae9..669bf9017 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -6,6 +6,7 @@ #define BITCOIN_KEYSTORE_H #include "crypter.h" +#include "script.h" // A virtual base class for key stores class CKeyStore @@ -31,9 +32,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; + // Support for BIP 0013 : see https://en.bitcoin.it/wiki/BIP_0013 + virtual bool AddCScript(const uint160 &hash, const CScript& redeemScript) =0; virtual bool HaveCScript(const uint160 &hash) const =0; - virtual bool GetCScript(const uint160 &hash, std::vector& dataOut) const =0; + virtual bool GetCScript(const uint160 &hash, CScript& redeemScriptOut) const =0; // Generate a new key, and add it to the store virtual std::vector GenerateNewKey(); @@ -48,14 +50,14 @@ public: }; typedef std::map KeyMap; -typedef std::map > DataMap; +typedef std::map ScriptMap; // Basic key store, that keeps keys in an address->secret map class CBasicKeyStore : public CKeyStore { protected: KeyMap mapKeys; - DataMap mapData; + ScriptMap mapScripts; public: bool AddKey(const CKey& key); @@ -92,9 +94,9 @@ public: } return false; } - virtual bool AddCScript(const uint160 &hash, const std::vector& data); + virtual bool AddCScript(const uint160 &hash, const CScript& redeemScript); virtual bool HaveCScript(const uint160 &hash) const; - virtual bool GetCScript(const uint160 &hash, std::vector& dataOut) const; + virtual bool GetCScript(const uint160 &hash, CScript& redeemScriptOut) const; }; typedef std::map, std::vector > > CryptedKeyMap; diff --git a/src/main.cpp b/src/main.cpp index 1b5bf5280..81dd76fc4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -250,13 +250,13 @@ 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 + // Biggest 'standard' txin is a 3-signature 3-of-3 CHECKMULTISIG + // in an OP_EVAL, which is 3 ~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.size() > 500) + return error("nonstandard txin, size %d is too large\n", txin.scriptSig.size()); if (!txin.scriptSig.IsPushOnly()) - return error("nonstandard txin: %s", txin.scriptSig.ToString().c_str()); + return error("nonstandard txin (opcodes other than PUSH): %s", txin.scriptSig.ToString().c_str()); } BOOST_FOREACH(const CTxOut& txout, vout) if (!::IsStandard(txout.scriptPubKey)) @@ -275,7 +275,7 @@ bool CTransaction::IsStandard() const // expensive-to-check-upon-redemption script like: // DUP CHECKSIG DROP ... repeated 100 times... OP_1 // -bool CTransaction::IsStandardInputs(std::map > mapInputs) const +bool CTransaction::AreInputsStandard(std::map > mapInputs) const { if (fTestNet) return true; // Allow non-standard on testnet @@ -287,18 +287,20 @@ bool CTransaction::IsStandardInputs(std::map > vSolutions; - txntype whichType; - if (!Solver(txPrev.vout[vin[i].prevout.n].scriptPubKey, whichType, vSolutions)) - return false; + txnouttype whichType; + // get the scriptPubKey corresponding to this input: + CScript& prevScript = txPrev.vout[prevout.n].scriptPubKey; + if (!Solver(prevScript, whichType, vSolutions)) + return error("nonstandard txin (spending nonstandard txout %s)", prevScript.ToString().c_str()); 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()))) + if (!EvalScript(stack, vin[i].scriptSig, *this, i, 0, true, nUnused)) return false; + CScript subscript(stack.back().begin(), stack.back().end()); + if (!::IsStandard(subscript)) + return error("nonstandard txin (nonstandard OP_EVAL subscript %s)", subscript.ToString().c_str()); } } @@ -481,7 +483,7 @@ bool CTransaction::AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs, bool* pfMi } // Check for non-standard OP_EVALs in inputs - if (!IsStandardInputs(mapInputs)) + if (!AreInputsStandard(mapInputs)) return error("AcceptToMemoryPool() : nonstandard transaction input"); // Check against previous transactions @@ -978,9 +980,27 @@ bool CTransaction::ConnectInputs(map > inp // (before the last blockchain checkpoint). This is safe because block merkle hashes are // still computed and checked, and any change will be caught at the next checkpoint. if (!(fBlock && IsInitialBlockDownload())) + { + bool fStrictOpEval = true; + // This code should be removed when OP_EVAL has + // a majority of hashing power on the network. + if (fBlock) + { + // To avoid being on the short end of a block-chain split, + // interpret OP_EVAL as a NO_OP until blocks with timestamps + // after opevaltime: + int64 nEvalSwitchTime = GetArg("opevaltime", 1328054400); // Feb 1, 2012 + fStrictOpEval = (pindexBlock->nTime >= nEvalSwitchTime); + } + // if !fBlock, then always be strict-- don't accept + // invalid-under-new-rules OP_EVAL transactions into + // our memory pool (don't relay them, don't include them + // in blocks we mine). + // Verify signature - if (!VerifySignature(txPrev, *this, i, nSigOpsRet)) + if (!VerifySignature(txPrev, *this, i, nSigOpsRet, fStrictOpEval)) return DoS(100,error("ConnectInputs() : %s VerifySignature failed", GetHash().ToString().substr(0,10).c_str())); + } // Check for conflicts (double-spend) // This doesn't trigger the DoS code on purpose; if it did, it would make it easier @@ -1054,7 +1074,7 @@ bool CTransaction::ClientConnectInputs() // Verify signature int nUnused = 0; - if (!VerifySignature(txPrev, *this, i, nUnused)) + if (!VerifySignature(txPrev, *this, i, nUnused, false)) return error("ConnectInputs() : VerifySignature failed"); ///// this is redundant with the mapNextTx stuff, not sure which I want to get rid of diff --git a/src/main.h b/src/main.h index bf31234c2..26d14b0d3 100644 --- a/src/main.h +++ b/src/main.h @@ -494,7 +494,7 @@ public: } bool IsStandard() const; - bool IsStandardInputs(std::map > mapInputs) const; + bool AreInputsStandard(std::map > mapInputs) const; int64 GetValueOut() const { @@ -622,6 +622,8 @@ public: bool ReadFromDisk(CTxDB& txdb, COutPoint prevout); bool ReadFromDisk(COutPoint prevout); bool DisconnectInputs(CTxDB& txdb); + + // Fetch from memory and/or disk. inputsRet keys are transaction hashes. bool FetchInputs(CTxDB& txdb, const std::map& mapTestPool, bool fBlock, bool fMiner, std::map >& inputsRet); bool ConnectInputs(std::map > inputs, diff --git a/src/script.cpp b/src/script.cpp index e60399120..5487c01fa 100644 --- a/src/script.cpp +++ b/src/script.cpp @@ -70,7 +70,7 @@ static inline void popstack(vector& stack) } -const char* GetTxnTypeName(txntype t) +const char* GetTxnOutputType(txnouttype t) { switch (t) { @@ -230,12 +230,12 @@ const char* GetOpName(opcodetype opcode) } } - // // 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) + CScript::const_iterator pbegincodehash, CScript::const_iterator pendcodehash, int& nOpCount, int& nSigOpCount, + bool fStrictOpEval, int nRecurseDepth) { CAutoBN_CTX pctx; CScript::const_iterator pc = script.begin(); @@ -1014,17 +1014,9 @@ bool EvalScriptInner(vector >& stack, const CScript& scrip case OP_EVAL: { - // This code should be removed when OP_EVAL has - // a majority of hashing power on the network. - // OP_EVAL behaves just like OP_NOP until - // opevaltime : - if (!fTestNet || fDebug) - { - // 1328054400 is Feb 1, 2012 - int64 nEvalSwitchTime = GetArg("opevaltime", 1328054400); - if (GetTime() < nEvalSwitchTime) - break; - } + if (!fStrictOpEval) + break; // Act as a NO_OP + // Evaluate the top item on the stack as a Script // [serialized script ] -- [result(s) of executing script] @@ -1034,12 +1026,14 @@ bool EvalScriptInner(vector >& stack, const CScript& scrip CScript subscript(vchScript.begin(), vchScript.end()); popstack(stack); - // Codeseparators not allowed + // Codeseparators not allowed; they don't make sense 'inside' an OP_EVAL, because + // their purpose is to change which parts of the scriptPubKey script is copied + // and signed by OP_CHECKSIG, but OP_EVAl'ed code is in the scriptSig, not the scriptPubKey. if (subscript.Find(OP_CODESEPARATOR)) return false; if (!EvalScriptInner(stack, subscript, txTo, nIn, nHashType, - pbegincodehash, pendcodehash, nOpCount, nSigOpCount, nRecurseDepth++)) + pbegincodehash, pendcodehash, nOpCount, nSigOpCount, fStrictOpEval, nRecurseDepth++)) return false; } break; @@ -1066,14 +1060,15 @@ bool EvalScriptInner(vector >& stack, const CScript& scrip } bool EvalScript(vector >& stack, const CScript& script, - const CTransaction& txTo, unsigned int nIn, int nHashType, int& nSigOpCountRet) + const CTransaction& txTo, unsigned int nIn, int nHashType, + bool fStrictOpEval, 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); + nOpCount, nSigOpCountRet, fStrictOpEval, 0); } @@ -1177,10 +1172,10 @@ bool CheckSig(vector vchSig, vector vchPubKey, CSc // // Return public keys or hashes from scriptPubKey, for 'standard' transaction types. // -bool Solver(const CScript& scriptPubKey, txntype& typeRet, vector >& vSolutionsRet) +bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, vector >& vSolutionsRet) { // Templates - static map mTemplates; + static map mTemplates; if (mTemplates.empty()) { // Standard tx, sender provides pubkey, receiver adds signature @@ -1199,7 +1194,7 @@ bool Solver(const CScript& scriptPubKey, txntype& typeRet, vector vSolutions; if (!Solver(scriptPubKey, whichType, vSolutions)) return false; CBitcoinAddress address; - valtype subscript; + CScript subscript; switch (whichType) { case TX_NONSTANDARD: @@ -1359,10 +1354,11 @@ bool Solver(const CKeyStore& keystore, const CScript& scriptPubKey, uint256 hash case TX_SCRIPTHASH: if (!keystore.GetCScript(uint160(vSolutions[0]), subscript)) return false; - if (!Solver(keystore, CScript(subscript.begin(), subscript.end()), hash, nHashType, scriptSigRet)) + if (!Solver(keystore, subscript, hash, nHashType, scriptSigRet)) return false; if (hash != 0) - scriptSigRet << subscript; // signatures AND serialized script + // static_cast to get vector.operator<< instead of CScript.operator<< + scriptSigRet << static_cast(subscript); // signatures AND serialized script break; case TX_MULTISIG: scriptSigRet << OP_0; // workaround CHECKMULTISIG bug @@ -1375,7 +1371,7 @@ bool Solver(const CKeyStore& keystore, const CScript& scriptPubKey, uint256 hash bool IsStandard(const CScript& scriptPubKey) { vector vSolutions; - txntype whichType; + txnouttype whichType; if (!Solver(scriptPubKey, whichType, vSolutions)) return false; @@ -1410,7 +1406,7 @@ int HaveKeys(const vector& pubkeys, const CKeyStore& keystore) bool IsMine(const CKeyStore &keystore, const CScript& scriptPubKey) { vector vSolutions; - txntype whichType; + txnouttype whichType; if (!Solver(scriptPubKey, whichType, vSolutions)) return false; @@ -1427,10 +1423,10 @@ bool IsMine(const CKeyStore &keystore, const CScript& scriptPubKey) return keystore.HaveKey(address); case TX_SCRIPTHASH: { - valtype subscript; + CScript subscript; if (!keystore.GetCScript(uint160(vSolutions[0]), subscript)) return false; - return IsMine(keystore, CScript(subscript.begin(), subscript.end())); + return IsMine(keystore, subscript); } case TX_MULTISIG: { @@ -1449,7 +1445,7 @@ bool IsMine(const CKeyStore &keystore, const CScript& scriptPubKey) bool ExtractAddress(const CScript& scriptPubKey, const CKeyStore* keystore, CBitcoinAddress& addressRet) { vector vSolutions; - txntype whichType; + txnouttype whichType; if (!Solver(scriptPubKey, whichType, vSolutions)) return false; @@ -1472,7 +1468,7 @@ bool ExtractAddress(const CScript& scriptPubKey, const CKeyStore* keystore, CBit return false; } -bool ExtractAddresses(const CScript& scriptPubKey, const CKeyStore* keystore, txntype& typeRet, vector& addressRet, int& nRequiredRet) +bool ExtractAddresses(const CScript& scriptPubKey, const CKeyStore* keystore, txnouttype& typeRet, vector& addressRet, int& nRequiredRet) { addressRet.clear(); typeRet = TX_NONSTANDARD; @@ -1484,10 +1480,10 @@ bool ExtractAddresses(const CScript& scriptPubKey, const CKeyStore* keystore, tx { nRequiredRet = vSolutions.front()[0]; int n = vSolutions.back()[0]; - for (vector::const_iterator it = vSolutions.begin()+1; it != vSolutions.begin()+vSolutions.size()-1; it++) + for (int i = 1; i < vSolutions.size()-1; i++) { CBitcoinAddress address; - address.SetPubKey(*it); + address.SetPubKey(vSolutions[i]); addressRet.push_back(address); } } @@ -1507,12 +1503,13 @@ bool ExtractAddresses(const CScript& scriptPubKey, const CKeyStore* keystore, tx return true; } -bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CTransaction& txTo, unsigned int nIn, int& nSigOpCountRet, int nHashType) +bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CTransaction& txTo, unsigned int nIn, int& nSigOpCountRet, + int nHashType, bool fStrictOpEval) { vector > stack; - if (!EvalScript(stack, scriptSig, txTo, nIn, nHashType, nSigOpCountRet)) + if (!EvalScript(stack, scriptSig, txTo, nIn, nHashType, fStrictOpEval, nSigOpCountRet)) return false; - if (!EvalScript(stack, scriptPubKey, txTo, nIn, nHashType, nSigOpCountRet)) + if (!EvalScript(stack, scriptPubKey, txTo, nIn, nHashType, fStrictOpEval, nSigOpCountRet)) return false; if (stack.empty()) return false; @@ -1521,24 +1518,8 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const C // 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; - } + if (fResult && fStrictOpEval && (scriptPubKey.Find(OP_EVAL) || scriptSig.Find(OP_EVAL))) + return VerifyScript(scriptSig, scriptPubKey, txTo, nIn, nSigOpCountRet, nHashType, false); return fResult; } @@ -1563,14 +1544,14 @@ bool SignSignature(const CKeyStore &keystore, const CTransaction& txFrom, CTrans // Test solution int nUnused = 0; if (scriptPrereq.empty()) - if (!VerifyScript(txin.scriptSig, txout.scriptPubKey, txTo, nIn, nUnused, 0)) + if (!VerifyScript(txin.scriptSig, txout.scriptPubKey, txTo, nIn, nUnused, 0, true)) return false; return true; } -bool VerifySignature(const CTransaction& txFrom, const CTransaction& txTo, unsigned int nIn, int& nSigOpCountRet, int nHashType) +bool VerifySignature(const CTransaction& txFrom, const CTransaction& txTo, unsigned int nIn, int& nSigOpCountRet, int nHashType, bool fStrictOpEval) { assert(nIn < txTo.vin.size()); const CTxIn& txin = txTo.vin[nIn]; @@ -1581,7 +1562,7 @@ 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, nSigOpCountRet, nHashType)) + if (!VerifyScript(txin.scriptSig, txout.scriptPubKey, txTo, nIn, nSigOpCountRet, nHashType, fStrictOpEval)) return false; return true; diff --git a/src/script.h b/src/script.h index ee0be02a8..b671e1596 100644 --- a/src/script.h +++ b/src/script.h @@ -6,7 +6,6 @@ #define H_BITCOIN_SCRIPT #include "base58.h" -#include "keystore.h" #include #include @@ -14,6 +13,7 @@ #include class CTransaction; +class CKeyStore; enum { @@ -24,7 +24,7 @@ enum }; -enum txntype +enum txnouttype { TX_NONSTANDARD, // 'standard' transaction types: @@ -34,7 +34,7 @@ enum txntype TX_MULTISIG, }; -const char* GetTxnTypeName(txntype t); +const char* GetTxnOutputType(txnouttype t); enum opcodetype { @@ -567,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 fStrictOpEval, int& nSigOpCountRet); -bool Solver(const CScript& scriptPubKey, txntype& typeRet, std::vector >& vSolutionsRet); +bool Solver(const CScript& scriptPubKey, txnouttype& 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 ExtractAddresses(const CScript& scriptPubKey, const CKeyStore* pkeystore, txnouttype& 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& nSigOpCountRet, int nHashType=0); +bool VerifySignature(const CTransaction& txFrom, const CTransaction& txTo, unsigned int nIn, int& nSigOpCountRet, int nHashType=0, bool fStrictOpEval=true); #endif diff --git a/src/test/multisig_tests.cpp b/src/test/multisig_tests.cpp index 75c764dd6..58f62b954 100644 --- a/src/test/multisig_tests.cpp +++ b/src/test/multisig_tests.cpp @@ -20,7 +20,8 @@ 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& nSigOpCount, int nHashType); +extern bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CTransaction& txTo, unsigned int nIn, int& nSigOpCount, + int nHashType, bool fStrictOpEval); BOOST_AUTO_TEST_SUITE(multisig_tests) @@ -80,19 +81,19 @@ BOOST_AUTO_TEST_CASE(multisig_verify) 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, nUnused, 0)); + BOOST_CHECK(VerifyScript(s, a_and_b, txTo[0], 0, nUnused, 0, true)); 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, nUnused, 0), strprintf("a&b 1: %d", i)); + BOOST_CHECK_MESSAGE(!VerifyScript(s, a_and_b, txTo[0], 0, nUnused, 0, true), 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, nUnused, 0), strprintf("a&b 2: %d", i)); + BOOST_CHECK_MESSAGE(!VerifyScript(s, a_and_b, txTo[0], 0, nUnused, 0, true), strprintf("a&b 2: %d", i)); } // Test a OR b: @@ -102,16 +103,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, nUnused, 0), strprintf("a|b: %d", i)); + BOOST_CHECK_MESSAGE(VerifyScript(s, a_or_b, txTo[1], 0, nUnused, 0, true), strprintf("a|b: %d", i)); else - BOOST_CHECK_MESSAGE(!VerifyScript(s, a_or_b, txTo[1], 0, nUnused, 0), strprintf("a|b: %d", i)); + BOOST_CHECK_MESSAGE(!VerifyScript(s, a_or_b, txTo[1], 0, nUnused, 0, true), strprintf("a|b: %d", i)); } s.clear(); s << OP_0 << OP_0; - BOOST_CHECK(!VerifyScript(s, a_or_b, txTo[1], 0, nUnused, 0)); + BOOST_CHECK(!VerifyScript(s, a_or_b, txTo[1], 0, nUnused, 0, true)); s.clear(); s << OP_0 << OP_1; - BOOST_CHECK(!VerifyScript(s, a_or_b, txTo[1], 0, nUnused, 0)); + BOOST_CHECK(!VerifyScript(s, a_or_b, txTo[1], 0, nUnused, 0, true)); for (int i = 0; i < 4; i++) @@ -121,9 +122,9 @@ 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, nUnused, 0), strprintf("escrow 1: %d %d", i, j)); + BOOST_CHECK_MESSAGE(VerifyScript(s, escrow, txTo[2], 0, nUnused, 0, true), strprintf("escrow 1: %d %d", i, j)); else - BOOST_CHECK_MESSAGE(!VerifyScript(s, escrow, txTo[2], 0, nUnused, 0), strprintf("escrow 2: %d %d", i, j)); + BOOST_CHECK_MESSAGE(!VerifyScript(s, escrow, txTo[2], 0, nUnused, 0, true), strprintf("escrow 2: %d %d", i, j)); } } @@ -185,7 +186,7 @@ BOOST_AUTO_TEST_CASE(multisig_Solver1) { vector solutions; - txntype whichType; + txnouttype whichType; CScript s; s << key[0].GetPubKey() << OP_CHECKSIG; BOOST_CHECK(Solver(s, whichType, solutions)); @@ -198,7 +199,7 @@ BOOST_AUTO_TEST_CASE(multisig_Solver1) } { vector solutions; - txntype whichType; + txnouttype whichType; CScript s; s << OP_DUP << OP_HASH160 << Hash160(key[0].GetPubKey()) << OP_EQUALVERIFY << OP_CHECKSIG; BOOST_CHECK(Solver(s, whichType, solutions)); @@ -211,7 +212,7 @@ BOOST_AUTO_TEST_CASE(multisig_Solver1) } { vector solutions; - txntype whichType; + txnouttype whichType; CScript s; s << OP_2 << key[0].GetPubKey() << key[1].GetPubKey() << OP_2 << OP_CHECKMULTISIG; BOOST_CHECK(Solver(s, whichType, solutions)); @@ -223,7 +224,7 @@ BOOST_AUTO_TEST_CASE(multisig_Solver1) } { vector solutions; - txntype whichType; + txnouttype whichType; CScript s; s << OP_1 << key[0].GetPubKey() << key[1].GetPubKey() << OP_2 << OP_CHECKMULTISIG; BOOST_CHECK(Solver(s, whichType, solutions)); @@ -239,7 +240,7 @@ BOOST_AUTO_TEST_CASE(multisig_Solver1) } { vector solutions; - txntype whichType; + txnouttype whichType; CScript s; s << OP_2 << key[0].GetPubKey() << key[1].GetPubKey() << key[2].GetPubKey() << OP_3 << OP_CHECKMULTISIG; BOOST_CHECK(Solver(s, whichType, solutions)); diff --git a/src/test/script_op_eval_tests.cpp b/src/test/script_op_eval_tests.cpp index 6c683b572..c44642c6e 100644 --- a/src/test/script_op_eval_tests.cpp +++ b/src/test/script_op_eval_tests.cpp @@ -13,24 +13,10 @@ 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); +extern bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CTransaction& txTo, unsigned int nIn, int& nSigOps, + int nHashType, bool fStrictOpEval); -static const int64 nEvalSwitchover = 1328054400; - -struct CEvalFixture { - CEvalFixture() - { - // Set mock time to AFTER OP_EVAL deployed - SetMockTime(nEvalSwitchover+1); - } - ~CEvalFixture() - { - // Reset back to use-real-time - SetMockTime(0); - } -}; - -BOOST_FIXTURE_TEST_SUITE(script_op_eval_tests, CEvalFixture) +BOOST_AUTO_TEST_SUITE(script_op_eval_tests) BOOST_AUTO_TEST_CASE(script_op_eval1) { @@ -130,8 +116,8 @@ BOOST_AUTO_TEST_CASE(script_op_eval2) 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_CHECK(!VerifyScript(txTo.vin[0].scriptSig, txFrom.vout[0].scriptPubKey, txTo, 0, nUnused, 0, true)); + BOOST_CHECK(!VerifySignature(txFrom, txTo, 0, nUnused, true)); } BOOST_AUTO_TEST_CASE(script_op_eval3) @@ -212,13 +198,13 @@ BOOST_AUTO_TEST_CASE(script_op_eval_backcompat1) 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_CHECK(!VerifyScript(txTo.vin[0].scriptSig, txFrom.vout[0].scriptPubKey, txTo, 0, nUnused, 0, true)); + BOOST_CHECK(!VerifySignature(txFrom, txTo, 0, nUnused, true)); } BOOST_AUTO_TEST_CASE(script_op_eval_switchover) { - // Use SetMockTime to test OP_EVAL switchover code + // Test OP_EVAL switchover code CScript notValid; notValid << OP_11 << OP_12 << OP_EQUALVERIFY; @@ -238,14 +224,11 @@ BOOST_AUTO_TEST_CASE(script_op_eval_switchover) txTo.vin[0].scriptSig = CScript() << static_cast >(notValid); txTo.vout[0].nValue = 1; - SetMockTime(nEvalSwitchover-1); - int nUnused = 0; - BOOST_CHECK(VerifyScript(txTo.vin[0].scriptSig, txFrom.vout[0].scriptPubKey, txTo, 0, nUnused, 0)); + BOOST_CHECK(VerifyScript(txTo.vin[0].scriptSig, txFrom.vout[0].scriptPubKey, txTo, 0, nUnused, 0, false)); - // After eval switchover time, it should validate: - SetMockTime(nEvalSwitchover); - BOOST_CHECK(!VerifyScript(txTo.vin[0].scriptSig, txFrom.vout[0].scriptPubKey, txTo, 0, nUnused, 0)); + // Under strict op_eval switchover, it should be considered invalid: + BOOST_CHECK(!VerifyScript(txTo.vin[0].scriptSig, txFrom.vout[0].scriptPubKey, txTo, 0, nUnused, 0, true)); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index 3d1c21870..22885a643 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -7,8 +7,8 @@ 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& nSigOps, int nHashType); -extern bool VerifySignature(const CTransaction& txFrom, 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, bool fStrictOpEval); BOOST_AUTO_TEST_SUITE(script_tests) @@ -24,18 +24,18 @@ BOOST_AUTO_TEST_CASE(script_PushData) int nUnused = 0; vector > directStack; - BOOST_CHECK(EvalScript(directStack, CScript(&direct[0], &direct[sizeof(direct)]), CTransaction(), 0, 0, nUnused)); + BOOST_CHECK(EvalScript(directStack, CScript(&direct[0], &direct[sizeof(direct)]), CTransaction(), 0, 0, true, nUnused)); vector > pushdata1Stack; - BOOST_CHECK(EvalScript(pushdata1Stack, CScript(&pushdata1[0], &pushdata1[sizeof(pushdata1)]), CTransaction(), 0, 0, nUnused)); + BOOST_CHECK(EvalScript(pushdata1Stack, CScript(&pushdata1[0], &pushdata1[sizeof(pushdata1)]), CTransaction(), 0, 0, true, nUnused)); BOOST_CHECK(pushdata1Stack == directStack); vector > pushdata2Stack; - BOOST_CHECK(EvalScript(pushdata2Stack, CScript(&pushdata2[0], &pushdata2[sizeof(pushdata2)]), CTransaction(), 0, 0, nUnused)); + BOOST_CHECK(EvalScript(pushdata2Stack, CScript(&pushdata2[0], &pushdata2[sizeof(pushdata2)]), CTransaction(), 0, 0, true, nUnused)); BOOST_CHECK(pushdata2Stack == directStack); vector > pushdata4Stack; - BOOST_CHECK(EvalScript(pushdata4Stack, CScript(&pushdata4[0], &pushdata4[sizeof(pushdata4)]), CTransaction(), 0, 0, nUnused)); + BOOST_CHECK(EvalScript(pushdata4Stack, CScript(&pushdata4[0], &pushdata4[sizeof(pushdata4)]), CTransaction(), 0, 0, true, nUnused)); BOOST_CHECK(pushdata4Stack == directStack); } @@ -94,15 +94,15 @@ 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, nUnused, 0)); + BOOST_CHECK(VerifyScript(goodsig1, scriptPubKey12, txTo12, 0, nUnused, 0, true)); txTo12.vout[0].nValue = 2; - BOOST_CHECK(!VerifyScript(goodsig1, scriptPubKey12, txTo12, 0, nUnused, 0)); + BOOST_CHECK(!VerifyScript(goodsig1, scriptPubKey12, txTo12, 0, nUnused, 0, true)); CScript goodsig2 = sign_multisig(scriptPubKey12, key2, txTo12); - BOOST_CHECK(VerifyScript(goodsig2, scriptPubKey12, txTo12, 0, nUnused, 0)); + BOOST_CHECK(VerifyScript(goodsig2, scriptPubKey12, txTo12, 0, nUnused, 0, true)); CScript badsig1 = sign_multisig(scriptPubKey12, key3, txTo12); - BOOST_CHECK(!VerifyScript(badsig1, scriptPubKey12, txTo12, 0, nUnused, 0)); + BOOST_CHECK(!VerifyScript(badsig1, scriptPubKey12, txTo12, 0, nUnused, 0, true)); } BOOST_AUTO_TEST_CASE(script_CHECKMULTISIG23) @@ -131,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, nUnused, 0)); + BOOST_CHECK(VerifyScript(goodsig1, scriptPubKey23, txTo23, 0, nUnused, 0, true)); keys.clear(); keys.push_back(key1); keys.push_back(key3); CScript goodsig2 = sign_multisig(scriptPubKey23, keys, txTo23); - BOOST_CHECK(VerifyScript(goodsig2, scriptPubKey23, txTo23, 0, nUnused, 0)); + BOOST_CHECK(VerifyScript(goodsig2, scriptPubKey23, txTo23, 0, nUnused, 0, true)); keys.clear(); keys.push_back(key2); keys.push_back(key3); CScript goodsig3 = sign_multisig(scriptPubKey23, keys, txTo23); - BOOST_CHECK(VerifyScript(goodsig3, scriptPubKey23, txTo23, 0, nUnused, 0)); + BOOST_CHECK(VerifyScript(goodsig3, scriptPubKey23, txTo23, 0, nUnused, 0, true)); 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, nUnused, 0)); + BOOST_CHECK(!VerifyScript(badsig1, scriptPubKey23, txTo23, 0, nUnused, 0, true)); 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, nUnused, 0)); + BOOST_CHECK(!VerifyScript(badsig2, scriptPubKey23, txTo23, 0, nUnused, 0, true)); 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, nUnused, 0)); + BOOST_CHECK(!VerifyScript(badsig3, scriptPubKey23, txTo23, 0, nUnused, 0, true)); 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, nUnused, 0)); + BOOST_CHECK(!VerifyScript(badsig4, scriptPubKey23, txTo23, 0, nUnused, 0, true)); 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, nUnused, 0)); + BOOST_CHECK(!VerifyScript(badsig5, scriptPubKey23, txTo23, 0, nUnused, 0, true)); keys.clear(); // Must have signatures CScript badsig6 = sign_multisig(scriptPubKey23, keys, txTo23); - BOOST_CHECK(!VerifyScript(badsig6, scriptPubKey23, txTo23, 0, nUnused, 0)); + BOOST_CHECK(!VerifyScript(badsig6, scriptPubKey23, txTo23, 0, nUnused, 0, true)); } diff --git a/src/wallet.cpp b/src/wallet.cpp index 5d44b1fb8..25eb6247f 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -42,13 +42,13 @@ bool CWallet::AddCryptedKey(const vector &vchPubKey, const vector return false; } -bool CWallet::AddCScript(const uint160 &hash, const std::vector& data) +bool CWallet::AddCScript(const uint160 &hash, const CScript& redeemScript) { - if (!CCryptoKeyStore::AddCScript(hash, data)) + if (!CCryptoKeyStore::AddCScript(hash, redeemScript)) return false; if (!fFileBacked) return true; - return CWalletDB(strWalletFile).WriteCScript(hash, data); + return CWalletDB(strWalletFile).WriteCScript(hash, redeemScript); } bool CWallet::Unlock(const SecureString& strWalletPassphrase) @@ -386,6 +386,14 @@ int64 CWallet::GetDebit(const CTxIn &txin) const bool CWallet::IsChange(const CTxOut& txout) const { CBitcoinAddress address; + + // TODO: fix handling of 'change' outputs. The assumption is that any + // payment to a TX_PUBKEYHASH that is mine but isn't in the address book + // is change. That assumption is likely to break when we implement multisignature + // wallets that return change back into a multi-signature-protected address; + // a better way of identifying which outputs are 'the send' and which are + // 'the change' will need to be implemented (maybe extend CWalletTx to remember + // which output, if any, was change). if (ExtractAddress(txout.scriptPubKey, this, address) && !address.IsScript()) CRITICAL_BLOCK(cs_wallet) if (!mapAddressBook.count(address)) diff --git a/src/wallet.h b/src/wallet.h index 34090ec80..a7d07c993 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -7,6 +7,7 @@ #include "bignum.h" #include "key.h" +#include "keystore.h" #include "script.h" class CWalletTx; @@ -69,8 +70,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 AddCScript(const uint160& hash, const CScript& redeemScript); + bool LoadCScript(const uint160& hash, const CScript& redeemScript) { return CCryptoKeyStore::AddCScript(hash, redeemScript); } bool Unlock(const SecureString& strWalletPassphrase); bool ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase); From be237c119e82a4c01f4ef3da4264faa6c900e9e3 Mon Sep 17 00:00:00 2001 From: Gavin Andresen Date: Mon, 5 Dec 2011 10:32:35 -0500 Subject: [PATCH 10/12] Fix logic for IsChange() for send-to-self transactions. --- src/wallet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wallet.cpp b/src/wallet.cpp index 25eb6247f..3dc804c85 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -394,7 +394,7 @@ bool CWallet::IsChange(const CTxOut& txout) const // a better way of identifying which outputs are 'the send' and which are // 'the change' will need to be implemented (maybe extend CWalletTx to remember // which output, if any, was change). - if (ExtractAddress(txout.scriptPubKey, this, address) && !address.IsScript()) + if (ExtractAddress(txout.scriptPubKey, this, address)) CRITICAL_BLOCK(cs_wallet) if (!mapAddressBook.count(address)) return true; From 9e470585b35a84fcb7f6aa41ac0216c117e2a5e1 Mon Sep 17 00:00:00 2001 From: Gavin Andresen Date: Fri, 16 Dec 2011 18:03:48 -0500 Subject: [PATCH 11/12] Update bitcoin address numbers for latest luke-jr/sipa scheme --- src/base58.h | 33 +++++++++++++++++++++++---------- src/main.cpp | 2 +- src/script.cpp | 2 +- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/base58.h b/src/base58.h index 4be119890..cba638f4e 100644 --- a/src/base58.h +++ b/src/base58.h @@ -253,14 +253,26 @@ public: }; // base58-encoded bitcoin addresses -// Addresses have version 0 or 111 (testnet) +// Public-key-hash-addresses have version 0 (or 192 testnet) // The data vector contains RIPEMD160(SHA256(pubkey)), where pubkey is the serialized public key +// Script-hash-addresses (OP_EVAL) have version 5 (or 196 testnet) +// The data vector contains RIPEMD160(SHA256(cscript)), where cscript is the serialized redemption script class CBitcoinAddress : public CBase58Data { public: - void SetHash160(const uint160& hash160) + enum { - SetData(fTestNet ? 111 : 0, &hash160, 20); + PUBKEY_ADDRESS = 0, + SCRIPT_ADDRESS = 5, + PUBKEY_ADDRESS_TEST = 192, + PUBKEY_ADDRESS_TEST_LEGACY = 111, // Deprecated: old testnet address + SCRIPT_ADDRESS_TEST = 196, + }; + + bool SetHash160(const uint160& hash160) + { + SetData(fTestNet ? PUBKEY_ADDRESS_TEST : PUBKEY_ADDRESS, &hash160, 20); + return true; } void SetPubKey(const std::vector& vchPubKey) @@ -270,7 +282,7 @@ public: bool SetScriptHash160(const uint160& hash160) { - SetData(fTestNet ? 111^2 : 2, &hash160, 20); + SetData(fTestNet ? SCRIPT_ADDRESS_TEST : SCRIPT_ADDRESS, &hash160, 20); return true; } @@ -280,20 +292,21 @@ public: bool fExpectTestNet = false; switch(nVersion) { - case 0: + case PUBKEY_ADDRESS: nExpectedSize = 20; // Hash of public key fExpectTestNet = false; break; - case 2: + case SCRIPT_ADDRESS: nExpectedSize = 20; // OP_EVAL, hash of CScript fExpectTestNet = false; break; - case 111: + case PUBKEY_ADDRESS_TEST_LEGACY: + case PUBKEY_ADDRESS_TEST: nExpectedSize = 20; fExpectTestNet = true; break; - case 111^2: + case SCRIPT_ADDRESS_TEST: nExpectedSize = 20; fExpectTestNet = true; break; @@ -308,8 +321,8 @@ public: if (!IsValid()) return false; if (fTestNet) - return nVersion == 111^2; - return nVersion == 2; + return nVersion == SCRIPT_ADDRESS_TEST; + return nVersion == SCRIPT_ADDRESS; } CBitcoinAddress() diff --git a/src/main.cpp b/src/main.cpp index 81dd76fc4..5db708a8c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -902,7 +902,7 @@ bool CTransaction::FetchInputs(CTxDB& txdb, const map& mapTes { if (IsCoinBase()) return true; // Coinbase transactions have no inputs to fetch. - + for (int i = 0; i < vin.size(); i++) { COutPoint prevout = vin[i].prevout; diff --git a/src/script.cpp b/src/script.cpp index 5487c01fa..467cb2f0d 100644 --- a/src/script.cpp +++ b/src/script.cpp @@ -1025,7 +1025,7 @@ bool EvalScriptInner(vector >& stack, const CScript& scrip valtype& vchScript = stacktop(-1); CScript subscript(vchScript.begin(), vchScript.end()); popstack(stack); - + // Codeseparators not allowed; they don't make sense 'inside' an OP_EVAL, because // their purpose is to change which parts of the scriptPubKey script is copied // and signed by OP_CHECKSIG, but OP_EVAl'ed code is in the scriptSig, not the scriptPubKey. From 77f21f1583deb89bf3fffe80fe9b181fedb1dd60 Mon Sep 17 00:00:00 2001 From: Gavin Andresen Date: Tue, 20 Dec 2011 12:21:47 -0500 Subject: [PATCH 12/12] include util.h to get SecureString definition. --- src/crypter.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/crypter.h b/src/crypter.h index d7f8a39d8..e52ebc01c 100644 --- a/src/crypter.h +++ b/src/crypter.h @@ -4,6 +4,7 @@ #ifndef __CRYPTER_H__ #define __CRYPTER_H__ +#include "util.h" /* for SecureString */ #include "key.h" const unsigned int WALLET_CRYPTO_KEY_SIZE = 32;