From e9a0e4d7a25f2a41641ce46011cabe1195a77285 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Fri, 27 May 2011 00:19:01 -0400 Subject: [PATCH 01/24] Execute command specified by -coinbaser when creating a new block, which can output data to control where the generation goes --- src/main.cpp | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 0456041ee5936..a98a5b0d6430b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3224,6 +3224,66 @@ class COrphan }; +#ifndef __WXMSW__ +int DoCoinbaser_I(CBlock* pblock, uint64 nTotal, FILE* file) +{ + int nCount; + if (fscanf(file, "%d\n", &nCount) != 1) + { + printf("DoCoinbaser(): failed to fscanf count\n"); + return -2; + } + pblock->vtx[0].vout.resize(nCount + 1); + uint64 nDistributed = 0; + for (int i = 1; i <= nCount; ++i) + { + uint64 nValue; + if (fscanf(file, "%" PRI64u "\n", &nValue) != 1) + { + printf("DoCoinbaser(): failed to fscanf amount for transaction #%d\n", i); + return -(0x1000 | i); + } + pblock->vtx[0].vout[i].nValue = nValue; + nDistributed += nValue; + char strAddr[35]; + if (fscanf(file, "%34s\n", strAddr) != 1) + { + printf("DoCoinbaser(): failed to fscanf address for transaction #%d\n", i); + return -(0x2000 | i); + } + if (!pblock->vtx[0].vout[i].scriptPubKey.SetBitcoinAddress(string(strAddr))) + { + printf("DoCoinbaser(): invalid bitcoin address for transaction #%d\n", i); + return -(0x3000 | i); + } + } + if (nTotal < nDistributed) + { + printf("DoCoinbaser(): attempt to distribute %" PRI64u "/%" PRI64u "\n", nDistributed, nTotal); + return -3; + } + uint64 nMine = nTotal - nDistributed; + printf("DoCoinbaser(): total distributed: %" PRI64u "/%" PRI64u " = %" PRI64u " for me\n", nDistributed, nTotal, nMine); + pblock->vtx[0].vout[0].nValue = nMine; + return 0; +} + +int DoCoinbaser(CBlock* pblock, uint64 nTotal) +{ + FILE* file = popen(mapArgs["-coinbaser"].c_str(), "r"); + if (!file) + { + printf("DoCoinbaser(): failed to popen: %s", strerror(errno)); + return -1; + } + int rv = DoCoinbaser_I(pblock, nTotal, file); + pclose(file); + if (rv) + pblock->vtx[0].vout.resize(1); + return rv; +} +#endif + CBlock* CreateNewBlock(CReserveKey& reservekey) { CBlockIndex* pindexPrev = pindexBest; @@ -3359,7 +3419,12 @@ CBlock* CreateNewBlock(CReserveKey& reservekey) } } } - pblock->vtx[0].vout[0].nValue = GetBlockValue(pindexPrev->nHeight+1, nFees); + int64 nBlkValue = GetBlockValue(pindexPrev->nHeight+1, nFees); + pblock->vtx[0].vout[0].nValue = nBlkValue; +#ifndef __WXMSW__ + if (mapArgs.count("-coinbaser")) + DoCoinbaser(&*pblock, nBlkValue); +#endif // Fill in header pblock->hashPrevBlock = pindexPrev->GetBlockHash(); @@ -3368,6 +3433,7 @@ CBlock* CreateNewBlock(CReserveKey& reservekey) pblock->nBits = GetNextWorkRequired(pindexPrev); pblock->nNonce = 0; + pblock->print(); return pblock.release(); } From b3fb0d04fc17948b513d9f483f98599975b1683c Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Fri, 27 May 2011 00:19:42 -0400 Subject: [PATCH 02/24] coinbaser: replace %d in command line with available funds --- src/main.cpp | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index a98a5b0d6430b..b7848c37f3db0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3270,13 +3270,41 @@ int DoCoinbaser_I(CBlock* pblock, uint64 nTotal, FILE* file) int DoCoinbaser(CBlock* pblock, uint64 nTotal) { - FILE* file = popen(mapArgs["-coinbaser"].c_str(), "r"); + string strCmd = mapArgs["-coinbaser"]; + try + { + char strTotal[11]; + int nTotalLen = snprintf(strTotal, 11, "%" PRI64u, nTotal); + if (nTotalLen < 1 || nTotalLen > 10) + { + strTotal[0] = '\0'; + nTotalLen = 0; + } + string::size_type nPos; + while ((nPos = strCmd.find("%d")) != string::npos) + { + strCmd.replace(nPos, 2, strTotal, nTotalLen); + } + } + catch (...) + { + return 1; + } + FILE* file = popen(strCmd.c_str(), "r"); if (!file) { printf("DoCoinbaser(): failed to popen: %s", strerror(errno)); return -1; } - int rv = DoCoinbaser_I(pblock, nTotal, file); + int rv; + try + { + rv = DoCoinbaser_I(pblock, nTotal, file); + } + catch (...) + { + rv = 1; + } pclose(file); if (rv) pblock->vtx[0].vout.resize(1); From 4e6e221902f0fdc3d599391c533779557381d718 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Tue, 5 Jul 2011 01:17:47 -0400 Subject: [PATCH 03/24] Support for TCP server coinbasers --- src/main.cpp | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index b7848c37f3db0..83454629fce23 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3271,6 +3271,23 @@ int DoCoinbaser_I(CBlock* pblock, uint64 nTotal, FILE* file) int DoCoinbaser(CBlock* pblock, uint64 nTotal) { string strCmd = mapArgs["-coinbaser"]; + FILE* file = NULL; + if (!strCmd.compare(0, 4, "tcp:")) + { + CAddress addrCoinbaser(strCmd.substr(4), true, 0); + SOCKET hSocket; + if (!ConnectSocket(addrCoinbaser, hSocket)) + { + perror("DoCoinbaser(): failed to connect"); + return -3; + } + file = fdopen(hSocket, "r+"); + if (file) + fprintf(file, "total: %" PRI64u "\n\n", nTotal); + } + else + { + try { char strTotal[11]; @@ -3290,12 +3307,16 @@ int DoCoinbaser(CBlock* pblock, uint64 nTotal) { return 1; } - FILE* file = popen(strCmd.c_str(), "r"); + file = popen(strCmd.c_str(), "r"); + + } + if (!file) { printf("DoCoinbaser(): failed to popen: %s", strerror(errno)); return -1; } + int rv; try { From d6cc5ef85bee3b8b9c78b17cf7acbeb4d0d35b49 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Mon, 25 Jul 2011 11:59:41 -0400 Subject: [PATCH 04/24] Save coinbase, not just extraNonce --- src/rpc.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/rpc.cpp b/src/rpc.cpp index 530bef4a43f41..0cdd85e5413b8 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -1318,7 +1318,8 @@ Value getwork(const Array& params, bool fHelp) if (IsInitialBlockDownload()) throw JSONRPCError(-10, "Bitcoin is downloading blocks..."); - static map > mapNewBlock; + typedef map > mapNewBlock_t; + static mapNewBlock_t mapNewBlock; static vector vNewBlock; static CReserveKey reservekey; @@ -1361,7 +1362,7 @@ Value getwork(const Array& params, bool fHelp) IncrementExtraNonce(pblock, pindexPrev, nExtraNonce, nPrevTime); // Save - mapNewBlock[pblock->hashMerkleRoot] = make_pair(pblock, nExtraNonce); + mapNewBlock[pblock->hashMerkleRoot] = make_pair(pblock, pblock->vtx[0].vin[0].scriptSig); // Prebuild hash buffers char pmidstate[32]; @@ -1394,11 +1395,10 @@ Value getwork(const Array& params, bool fHelp) if (!mapNewBlock.count(pdata->hashMerkleRoot)) return false; CBlock* pblock = mapNewBlock[pdata->hashMerkleRoot].first; - unsigned int nExtraNonce = mapNewBlock[pdata->hashMerkleRoot].second; pblock->nTime = pdata->nTime; pblock->nNonce = pdata->nNonce; - pblock->vtx[0].vin[0].scriptSig = CScript() << pblock->nBits << CBigNum(nExtraNonce); + pblock->vtx[0].vin[0].scriptSig = mapNewBlock[pdata->hashMerkleRoot].second; pblock->hashMerkleRoot = pblock->BuildMerkleTree(); return CheckWork(pblock, reservekey); From 29b32459f73ca9378a3a8ba47db8360cf7ba9beb Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Wed, 3 Aug 2011 13:48:16 -0400 Subject: [PATCH 05/24] Replace nBits in coinbase scriptSig with arbitrary data set by setworkaux RPC call --- src/main.cpp | 13 ++++++++++++- src/main.h | 1 + src/rpc.cpp | 22 ++++++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 83454629fce23..6614d421ab35d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3487,6 +3487,8 @@ CBlock* CreateNewBlock(CReserveKey& reservekey) } +std::map mapAuxCoinbases; + void IncrementExtraNonce(CBlock* pblock, CBlockIndex* pindexPrev, unsigned int& nExtraNonce, int64& nPrevTime) { // Update nExtraNonce @@ -3496,7 +3498,16 @@ void IncrementExtraNonce(CBlock* pblock, CBlockIndex* pindexPrev, unsigned int& nExtraNonce = 1; nPrevTime = nNow; } - pblock->vtx[0].vin[0].scriptSig = CScript() << pblock->nBits << CBigNum(nExtraNonce); + + CScript &scriptSig = pblock->vtx[0].vin[0].scriptSig; + scriptSig = CScript(); + + map::iterator it; + for (it = mapAuxCoinbases.begin() ; it != mapAuxCoinbases.end(); ++it) + scriptSig += (*it).second; + + scriptSig << CBigNum(nExtraNonce); + pblock->hashMerkleRoot = pblock->BuildMerkleTree(); } diff --git a/src/main.h b/src/main.h index 436ffbecbd1d3..a0f5b00033064 100644 --- a/src/main.h +++ b/src/main.h @@ -103,6 +103,7 @@ std::string SendMoneyToBitcoinAddress(std::string strAddress, int64 nValue, CWal void GenerateBitcoins(bool fGenerate); void ThreadBitcoinMiner(void* parg); CBlock* CreateNewBlock(CReserveKey& reservekey); +extern std::map mapAuxCoinbases; void IncrementExtraNonce(CBlock* pblock, CBlockIndex* pindexPrev, unsigned int& nExtraNonce, int64& nPrevTime); void FormatHashBuffers(CBlock* pblock, char* pmidstate, char* pdata, char* phash1); bool CheckWork(CBlock* pblock, CReserveKey& reservekey); diff --git a/src/rpc.cpp b/src/rpc.cpp index 0cdd85e5413b8..61b870004f1ea 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -1300,6 +1300,27 @@ Value validateaddress(const Array& params, bool fHelp) } +Value setworkaux(const Array& params, bool fHelp) +{ + if (fHelp || params.size() < 1 || params.size() > 2) + throw runtime_error( + "setworkaux [data]\n" + "If [data] is not specified, deletes aux.\n" + ); + + std::string strId = params[0].get_str(); + if (params.size() > 1) + { + std::vector vchData = ParseHex(params[1].get_str()); + mapAuxCoinbases[strId] = CScript(vchData); + } + else + mapAuxCoinbases.erase(strId); + + return true; +} + + Value getwork(const Array& params, bool fHelp) { if (fHelp || params.size() > 1) @@ -1456,6 +1477,7 @@ pair pCallTable[] = make_pair("sendmany", &sendmany), make_pair("gettransaction", &gettransaction), make_pair("listtransactions", &listtransactions), + make_pair("setworkaux", &setworkaux), make_pair("getwork", &getwork), make_pair("listaccounts", &listaccounts), make_pair("settxfee", &settxfee), From 9c651437cae7822dedc1ff03c67b7537e0450c96 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Thu, 4 Aug 2011 21:58:58 -0400 Subject: [PATCH 06/24] Bugfix/safeguard: never create a coinbase scriptSig over 100 bytes long --- src/main.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 6614d421ab35d..22b7d5ba6671d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3500,13 +3500,14 @@ void IncrementExtraNonce(CBlock* pblock, CBlockIndex* pindexPrev, unsigned int& } CScript &scriptSig = pblock->vtx[0].vin[0].scriptSig; - scriptSig = CScript(); + scriptSig = CScript() << CBigNum(nExtraNonce); map::iterator it; for (it = mapAuxCoinbases.begin() ; it != mapAuxCoinbases.end(); ++it) scriptSig += (*it).second; - scriptSig << CBigNum(nExtraNonce); + if (scriptSig.size() > 100) + scriptSig.resize(100); pblock->hashMerkleRoot = pblock->BuildMerkleTree(); } From 96f969314d0760b326b7c4c8e1025467032843bf Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Thu, 4 Aug 2011 22:17:10 -0400 Subject: [PATCH 07/24] Check for parse or overflow errors in setworkaux --- src/main.cpp | 33 ++++++++++++++++++++++----------- src/main.h | 1 + src/rpc.cpp | 10 +++++++++- 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 22b7d5ba6671d..a315030357fd9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3489,26 +3489,37 @@ CBlock* CreateNewBlock(CReserveKey& reservekey) std::map mapAuxCoinbases; -void IncrementExtraNonce(CBlock* pblock, CBlockIndex* pindexPrev, unsigned int& nExtraNonce, int64& nPrevTime) +CScript BuildCoinbaseScriptSig(unsigned int nExtraNonce, bool *pfOverflow) { - // Update nExtraNonce - int64 nNow = max(pindexPrev->GetMedianTimePast()+1, GetAdjustedTime()); - if (++nExtraNonce >= 0x7f && nNow > nPrevTime+1) - { - nExtraNonce = 1; - nPrevTime = nNow; - } - - CScript &scriptSig = pblock->vtx[0].vin[0].scriptSig; - scriptSig = CScript() << CBigNum(nExtraNonce); + CScript scriptSig = CScript() << CBigNum(nExtraNonce); map::iterator it; for (it = mapAuxCoinbases.begin() ; it != mapAuxCoinbases.end(); ++it) scriptSig += (*it).second; if (scriptSig.size() > 100) + { scriptSig.resize(100); + if (pfOverflow) + *pfOverflow = true; + } + else + if (pfOverflow) + *pfOverflow = false; + return scriptSig; +} + +void IncrementExtraNonce(CBlock* pblock, CBlockIndex* pindexPrev, unsigned int& nExtraNonce, int64& nPrevTime) +{ + // Update nExtraNonce + int64 nNow = max(pindexPrev->GetMedianTimePast()+1, GetAdjustedTime()); + if (++nExtraNonce >= 0x7f && nNow > nPrevTime+1) + { + nExtraNonce = 1; + nPrevTime = nNow; + } + pblock->vtx[0].vin[0].scriptSig = BuildCoinbaseScriptSig(nExtraNonce); pblock->hashMerkleRoot = pblock->BuildMerkleTree(); } diff --git a/src/main.h b/src/main.h index a0f5b00033064..bc3035cea4b9c 100644 --- a/src/main.h +++ b/src/main.h @@ -104,6 +104,7 @@ void GenerateBitcoins(bool fGenerate); void ThreadBitcoinMiner(void* parg); CBlock* CreateNewBlock(CReserveKey& reservekey); extern std::map mapAuxCoinbases; +CScript BuildCoinbaseScriptSig(unsigned int nExtraNonce, bool *pfOverflow = NULL); void IncrementExtraNonce(CBlock* pblock, CBlockIndex* pindexPrev, unsigned int& nExtraNonce, int64& nPrevTime); void FormatHashBuffers(CBlock* pblock, char* pmidstate, char* pdata, char* phash1); bool CheckWork(CBlock* pblock, CReserveKey& reservekey); diff --git a/src/rpc.cpp b/src/rpc.cpp index 61b870004f1ea..30099cd434aa1 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -1311,8 +1311,16 @@ Value setworkaux(const Array& params, bool fHelp) std::string strId = params[0].get_str(); if (params.size() > 1) { - std::vector vchData = ParseHex(params[1].get_str()); + std::string strData = params[1].get_str(); + std::vector vchData = ParseHex(strData); + if (vchData.size() * 2 != strData.size()) + throw JSONRPCError(-8, "Failed to parse data as hexadecimal"); + CScript scriptBackup = mapAuxCoinbases[strId]; mapAuxCoinbases[strId] = CScript(vchData); + bool fOverflow; + BuildCoinbaseScriptSig(UINT_MAX, &fOverflow); + if (fOverflow) + throw JSONRPCError(-7, "Change would overflow coinbase script"); } else mapAuxCoinbases.erase(strId); From a1efc8cb1014fa7d9734d0f5f7240dc0e9e216dd Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Mon, 3 Oct 2011 19:21:38 -0400 Subject: [PATCH 08/24] Document -coinbaser option in --help --- src/init.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/init.cpp b/src/init.cpp index dbc2c4133229b..eab901ab48fb4 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -199,6 +199,9 @@ bool AppInit2(int argc, char* argv[]) " -rpcport= \t\t " + _("Listen for JSON-RPC connections on (default: 8332)\n") + " -rpcallowip= \t\t " + _("Allow JSON-RPC connections from specified IP address\n") + " -rpcconnect= \t " + _("Send commands to node running on (default: 127.0.0.1)\n") + +#ifndef __WXMSW__ + " -coinbaser= \t " + _("Execute to calculate coinbase payees\n") + +#endif " -keypool= \t " + _("Set key pool size to (default: 100)\n") + " -rescan \t " + _("Rescan the block chain for missing wallet transactions\n"); From 59806427d0f10328e0f97c45bd2017eb9dce870e Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Wed, 5 Oct 2011 18:15:20 -0400 Subject: [PATCH 09/24] Port coinbaser code to Windows --- src/init.cpp | 2 -- src/main.cpp | 19 ++++++++++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index eab901ab48fb4..6bd300447624b 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -199,9 +199,7 @@ bool AppInit2(int argc, char* argv[]) " -rpcport= \t\t " + _("Listen for JSON-RPC connections on (default: 8332)\n") + " -rpcallowip= \t\t " + _("Allow JSON-RPC connections from specified IP address\n") + " -rpcconnect= \t " + _("Send commands to node running on (default: 127.0.0.1)\n") + -#ifndef __WXMSW__ " -coinbaser= \t " + _("Execute to calculate coinbase payees\n") + -#endif " -keypool= \t " + _("Set key pool size to (default: 100)\n") + " -rescan \t " + _("Rescan the block chain for missing wallet transactions\n"); diff --git a/src/main.cpp b/src/main.cpp index bed683df7308d..126a7d11ab120 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,6 +8,9 @@ #include "init.h" #include "cryptopp/sha.h" +#ifdef __WXMSW__ +#include +#endif #include #include @@ -2655,7 +2658,6 @@ class COrphan }; -#ifndef __WXMSW__ int DoCoinbaser_I(CBlock* pblock, uint64 nTotal, FILE* file) { int nCount; @@ -2714,7 +2716,17 @@ int DoCoinbaser(CBlock* pblock, uint64 nTotal) perror("DoCoinbaser(): failed to connect"); return -3; } - file = fdopen(hSocket, "r+"); +#ifdef __WXMSW__ + int nSocket = _open_osfhandle((intptr_t)hSocket, _O_RDONLY | _O_TEXT); + if (-1 == nSocket) + { + printf("DoCoinbaser(): failed to _open_osfhandle\n"); + return -4; + } + file = fdopen(nSocket, "r"); +#else + file = fdopen(hSocket, "r"); +#endif if (file) fprintf(file, "total: %" PRI64u "\n\n", nTotal); } @@ -2764,7 +2776,6 @@ int DoCoinbaser(CBlock* pblock, uint64 nTotal) pblock->vtx[0].vout.resize(1); return rv; } -#endif CBlock* CreateNewBlock(CReserveKey& reservekey) { @@ -2903,10 +2914,8 @@ CBlock* CreateNewBlock(CReserveKey& reservekey) } int64 nBlkValue = GetBlockValue(pindexPrev->nHeight+1, nFees); pblock->vtx[0].vout[0].nValue = nBlkValue; -#ifndef __WXMSW__ if (mapArgs.count("-coinbaser")) DoCoinbaser(&*pblock, nBlkValue); -#endif // Fill in header pblock->hashPrevBlock = pindexPrev->GetBlockHash(); From 6b18ab4a2493b1f290f09606c81c5f2ffc9d5c5d Mon Sep 17 00:00:00 2001 From: sje397 Date: Fri, 11 Nov 2011 01:20:17 +1100 Subject: [PATCH 10/24] Added QRCode generation functions via libqrencode --- bitcoin-qt.pro | 14 +++ src/qt/addressbookpage.cpp | 30 ++++++ src/qt/addressbookpage.h | 1 + src/qt/bitcoin.qrc | 1 + src/qt/forms/addressbookpage.ui | 11 ++ src/qt/forms/qrcodedialog.ui | 172 ++++++++++++++++++++++++++++++++ src/qt/qrcodedialog.cpp | 106 ++++++++++++++++++++ src/qt/qrcodedialog.h | 37 +++++++ src/qt/res/images/qrcode.png | Bin 0 -> 5993 bytes 9 files changed, 372 insertions(+) create mode 100644 src/qt/forms/qrcodedialog.ui create mode 100644 src/qt/qrcodedialog.cpp create mode 100644 src/qt/qrcodedialog.h create mode 100644 src/qt/res/images/qrcode.png diff --git a/bitcoin-qt.pro b/bitcoin-qt.pro index a8f6b2d86c998..9eacff89e9b46 100644 --- a/bitcoin-qt.pro +++ b/bitcoin-qt.pro @@ -19,6 +19,14 @@ OBJECTS_DIR = build MOC_DIR = build UI_DIR = build +# use: qmake "USE_QRCODE=1" +# libqrencode (http://fukuchi.org/works/qrencode/index.en.html) must be installed for support +contains(USE_QRCODE, 1) { + message(Building with QRCode support) + DEFINES += USE_QRCODE + LIBS += -lqrencode +} + # use: qmake "RELEASE=1" contains(RELEASE, 1) { # Mac: compile for maximum compatibility (10.5, 32-bit) @@ -195,6 +203,12 @@ FORMS += \ src/qt/forms/sendcoinsentry.ui \ src/qt/forms/askpassphrasedialog.ui +contains(USE_QRCODE, 1) { +HEADERS += src/qt/qrcodedialog.h +SOURCES += src/qt/qrcodedialog.cpp +FORMS += src/qt/forms/qrcodedialog.ui +} + CODECFORTR = UTF-8 # for lrelease/lupdate diff --git a/src/qt/addressbookpage.cpp b/src/qt/addressbookpage.cpp index 6be59a082f087..9c151a06f433e 100644 --- a/src/qt/addressbookpage.cpp +++ b/src/qt/addressbookpage.cpp @@ -10,6 +10,10 @@ #include #include +#ifdef USE_QRCODE +#include "qrcodedialog.h" +#endif + AddressBookPage::AddressBookPage(Mode mode, Tabs tab, QWidget *parent) : QDialog(parent), ui(new Ui::AddressBookPage), @@ -25,6 +29,10 @@ AddressBookPage::AddressBookPage(Mode mode, Tabs tab, QWidget *parent) : ui->deleteButton->setIcon(QIcon()); #endif +#ifndef USE_QRCODE + ui->showQRCode->setVisible(false); +#endif + switch(mode) { case ForSending: @@ -164,10 +172,12 @@ void AddressBookPage::selectionChanged() break; } ui->copyToClipboard->setEnabled(true); + ui->showQRCode->setEnabled(true); } else { ui->deleteButton->setEnabled(false); + ui->showQRCode->setEnabled(false); ui->copyToClipboard->setEnabled(false); } } @@ -220,3 +230,23 @@ void AddressBookPage::exportClicked() QMessageBox::Abort, QMessageBox::Abort); } } + +void AddressBookPage::on_showQRCode_clicked() +{ +#ifdef USE_QRCODE + QTableView *table = getCurrentTable(); + QModelIndexList indexes = table->selectionModel()->selectedRows(AddressTableModel::Address); + + + QRCodeDialog *d; + foreach (QModelIndex index, indexes) + { + QString address = index.data().toString(), + label = index.sibling(index.row(), 0).data().toString(), + title = QString("%1 << %2 >>").arg(label).arg(address); + + QRCodeDialog *d = new QRCodeDialog(title, address, label, tab == ReceivingTab, this); + d->show(); + } +#endif +} diff --git a/src/qt/addressbookpage.h b/src/qt/addressbookpage.h index 53c7728c8c6ac..07ac07967bcd4 100644 --- a/src/qt/addressbookpage.h +++ b/src/qt/addressbookpage.h @@ -54,6 +54,7 @@ private slots: void on_newAddressButton_clicked(); void on_copyToClipboard_clicked(); void selectionChanged(); + void on_showQRCode_clicked(); }; #endif // ADDRESSBOOKDIALOG_H diff --git a/src/qt/bitcoin.qrc b/src/qt/bitcoin.qrc index e90cdae45cb56..faaf18aa69e0e 100644 --- a/src/qt/bitcoin.qrc +++ b/src/qt/bitcoin.qrc @@ -41,6 +41,7 @@ res/images/about.png res/images/splash2.jpg + res/images/qrcode.png res/movies/update_spinner.mng diff --git a/src/qt/forms/addressbookpage.ui b/src/qt/forms/addressbookpage.ui index fb098c8280725..9b301cbbfa273 100644 --- a/src/qt/forms/addressbookpage.ui +++ b/src/qt/forms/addressbookpage.ui @@ -79,6 +79,17 @@ + + + + Show &QR Code + + + + :/images/qrcode:/images/qrcode + + + diff --git a/src/qt/forms/qrcodedialog.ui b/src/qt/forms/qrcodedialog.ui new file mode 100644 index 0000000000000..2bf1952edc3b9 --- /dev/null +++ b/src/qt/forms/qrcodedialog.ui @@ -0,0 +1,172 @@ + + + QRCodeDialog + + + + 0 + 0 + 334 + 372 + + + + Dialog + + + + + + + 0 + 0 + + + + + 300 + 300 + + + + QR Code + + + Qt::AlignCenter + + + + + + + + + + + + true + + + Request Payment + + + + + + + + + + 0 + 0 + + + + Amount: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + lnReqAmount + + + + + + + false + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + BTC + + + lnReqAmount + + + + + + + + + + + + + Label: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + lnLabel + + + + + + + + + + Message: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + lnMessage + + + + + + + + + + + + &Save As... + + + + + + + + + + + + chkReq + clicked(bool) + lnReqAmount + setEnabled(bool) + + + 92 + 285 + + + 98 + 311 + + + + + diff --git a/src/qt/qrcodedialog.cpp b/src/qt/qrcodedialog.cpp new file mode 100644 index 0000000000000..ed4c758e3822b --- /dev/null +++ b/src/qt/qrcodedialog.cpp @@ -0,0 +1,106 @@ +#include "qrcodedialog.h" +#include "ui_qrcodedialog.h" +#include +#include +#include +#include +#include + +#include + +#define EXPORT_IMAGE_SIZE 256 + +QRCodeDialog::QRCodeDialog(const QString &title, const QString &addr, const QString &label, bool enableReq, QWidget *parent) : + QDialog(parent), + ui(new Ui::QRCodeDialog), + address(addr) +{ + ui->setupUi(this); + setWindowTitle(title); + setAttribute(Qt::WA_DeleteOnClose); + + ui->chkReq->setVisible(enableReq); + ui->lnReqAmount->setVisible(enableReq); + ui->lblAm1->setVisible(enableReq); + ui->lblAm2->setVisible(enableReq); + + ui->lnLabel->setText(label); + + genCode(); +} + +QRCodeDialog::~QRCodeDialog() +{ + delete ui; +} + +void QRCodeDialog::genCode() { + + QString uri = getURI(); + //qDebug() << "Encoding:" << uri.toUtf8().constData(); + QRcode *code = QRcode_encodeString(uri.toUtf8().constData(), 0, QR_ECLEVEL_L, QR_MODE_8, 1); + myImage = QImage(code->width + 8, code->width + 8, QImage::Format_RGB32); + myImage.fill(0xffffff); + unsigned char *p = code->data; + for(int y = 0; y < code->width; y++) { + for(int x = 0; x < code->width; x++) { + myImage.setPixel(x + 4, y + 4, ((*p & 1) ? 0x0 : 0xffffff)); + p++; + } + } + QRcode_free(code); + ui->lblQRCode->setPixmap(QPixmap::fromImage(myImage).scaled(300, 300)); +} + +QString QRCodeDialog::getURI() { + QString ret = QString("bitcoin:%1").arg(address); + + int paramCount = 0; + if(ui->chkReq->isChecked() && ui->lnReqAmount->text().isEmpty() == false) { + bool ok= false; + double amount = ui->lnReqAmount->text().toDouble(&ok); + if(ok) { + ret += QString("?amount=%1X8").arg(ui->lnReqAmount->text()); + paramCount++; + } + } + + if(ui->lnLabel->text().isEmpty() == false) { + QString lbl(QUrl::toPercentEncoding(ui->lnLabel->text())); + ret += QString("%1label=%2").arg(paramCount == 0 ? "?" : "&").arg(lbl); + paramCount++; + } + + if(ui->lnMessage->text().isEmpty() == false) { + QString msg(QUrl::toPercentEncoding(ui->lnMessage->text())); + ret += QString("%1message=%2").arg(paramCount == 0 ? "?" : "&").arg(msg); + paramCount++; + } + + return ret; +} + +void QRCodeDialog::on_lnReqAmount_textChanged(const QString &) { + genCode(); +} + +void QRCodeDialog::on_lnLabel_textChanged(const QString &) { + genCode(); +} + +void QRCodeDialog::on_lnMessage_textChanged(const QString &) { + genCode(); +} + +void QRCodeDialog::on_btnSaveAs_clicked() +{ + QString fn = QFileDialog::getSaveFileName(this, "Save Image...", QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation), "Images (*.png)"); + if(!fn.isEmpty()) { + myImage.scaled(EXPORT_IMAGE_SIZE, EXPORT_IMAGE_SIZE).save(fn); + } +} + +void QRCodeDialog::on_chkReq_toggled(bool) +{ + genCode(); +} diff --git a/src/qt/qrcodedialog.h b/src/qt/qrcodedialog.h new file mode 100644 index 0000000000000..7463a8810ef59 --- /dev/null +++ b/src/qt/qrcodedialog.h @@ -0,0 +1,37 @@ +#ifndef QRCODEDIALOG_H +#define QRCODEDIALOG_H + +#include +#include + +namespace Ui { + class QRCodeDialog; +} + +class QRCodeDialog : public QDialog +{ + Q_OBJECT + +public: + explicit QRCodeDialog(const QString &title, const QString &address, const QString &label, bool allowReq, QWidget *parent = 0); + ~QRCodeDialog(); + +private slots: + void on_lnReqAmount_textChanged(const QString &arg1); + void on_lnLabel_textChanged(const QString &arg1); + void on_lnMessage_textChanged(const QString &arg1); + void on_btnSaveAs_clicked(); + + void on_chkReq_toggled(bool checked); + +private: + Ui::QRCodeDialog *ui; + QImage myImage; + + QString getURI(); + QString address; + + void genCode(); +}; + +#endif // QRCODEDIALOG_H diff --git a/src/qt/res/images/qrcode.png b/src/qt/res/images/qrcode.png new file mode 100644 index 0000000000000000000000000000000000000000..c89a49bbceba56e1901487b970645dd9eea95608 GIT binary patch literal 5993 zcmW+)2RxMjAAgKETxWDfHrYGbgmm_0hh$ztx*w7ia%7!7>XN-fLPE>PF4>*S$dqxehShp#DFh~hg$j; zFc|FTjPW%1mBvf&)Jzh+|@k(Z}-%Fks^w z*fnV=!eFo`v}i-^+M2vd{5u&@%e!}FigSDE3rS7xC6W&@517R=?JD|a*+$%%&yhJm z+T<`w8a}>=WKEd<#%5CC@J~a{Zy;E+uG$<`N*v+DQn-#0XzxC$8SIvP_SA00LKP2Fwz zZDL^&;lvi@l#q}BC8woDJ6U5e1$MJ}dAw_)KVILOTV9TF8rh!vLY7xhK;9-3rnxmP zEk^i;J6(0?7c|5>+_>J5t@cQkm)B86+uWRZ+p{t9^U>kqM8l(+cke8Pp01C7{M{Tz zVQOk>R<;`_-PC-KTO&l`X_tcMWJAy&kA}<2%D2P_&j|!B_27fms?U~X7PsGFyZZZ$ z_4TXkWx120xp#JU6gIw=vCB$12ERd3_YJaw>RfVOZ zoE#+;Rl$v|-Q8zh&N_vRZ88d(SLNmJ%BF*j*+ySJFU`z^xnUJJCciq%Cg@y(93hJa zlB0}Q2qK<00JSz+N2l6&wyH^i-4qw)6WpZm-;dOw-cxIm!JT zRA!Npn>$4!`EE_OWMyTs-ln7a+#P%Y@|bKW8(!7Vwk+SJ+ww zBvw{RHL!|quZ=W3y29}6V5IsETa=_tE&hNI6%{o&XbRyS8e?4?3|vBOrTY8&5cKp_ zm6en4J$Yme!otFMa3`1fzkdDt;>8PI#Ekbx5{Y#4=FQxYBhkr`iHU3ip)TmpMp04G z?(S|_7+FJkUEN;V>m}CMWc#YBvN9S3A~7lH3+wvY8Yd4=QjWQ~d7X{q1AIw|YjAL| zu`zx1prg#t&``tCN;%|pdgMDRb!ujkdqnA2Gx{SpT+7QHnx36yVqu|zctQ^N2nMH{ zxW9Ce(9>m5dKO~LqPaE|9`5esp^ZU*3iMS}Rb7wv`?6KbEZnWE(yS#qHDXct2x@w6 z238h!_Smt@aJR36^78U25<;!(Ki_Y=X7+Yssf$LT`HT5ADFCe-gIKqDICOLFf#DtUe6E; zh&_R7J1a9&HBJ}-hquc}T)A=q`sJ<6^d>$&e%U}x)w0S)-w>bVz-zChYKO&2KxVuoyZy@l^(E<-aK{bsK$qcgfe;x#-QCftHs1(o#J!0=ee3 zYh;VyRZ+;Usp>n7{{ zle*_Oz)Pu6{Cw}Z=>75f_3ITC6$iC;c6KPL7ib}O-09x%Wq5aQ@4WX%?~lP$R6eya zF*MmmuGQ8e$k?>Bv{5Xm1v4>%N;U#wgGZ&MvD%;v4e4_G|A4i%1*4%^URy)Sxz~NF zujiPG02#_Q8YU8nLqh@#FfA>ufwvhM8Ar#*zA9YbvC`62O-`c!u}T~cCHbaV{MXqZ8y;?xKkr06+`QfvHsb{z1YuI2L=WJCxB|5o1M+b%uMkH2nT7JnK8*e)5=d)ifEhp zH9uchTWkBtXL&x&K_~1qOZlPt!F2e+!NK(O^aWuf-oN;)9sn{E6BA^b4VskhB;F{e znVA`^LT0;ESIWJ*F;-KF)Qf_Bnz4~vT)Zn#WqZvRExe0GNl>e%o!!LY@5KujE*Kgb zUWSW{h-fnL6Aa2s-x(o*?biiO{L2HA~WXi?({9CoJUaho* zQ{&03{6pp(;6@W^zu zP;P(Kt5-*$|LLLb=aPo{`Yke83q|tl;opSWD~2gFLqgP}oC03+aB##qjhvkx$7qDU zFw5v&80{7Dc7`qKmRg8?e>qAbV?yyWno_o69SUNa(rjgM`Y84K~#>V!z zTuJLbk3cA7_OisCYkN~xCR#I!tr=yYU}k1^sTl>}yZpTvMFoZG6y~e7V5(pAy7>G1 zkM++?PlrnH4wadMi+Xz{1qC~>3a)pr8jjwR8+-TP&mQn+EH^jz%R#s8a;w31%mZg< zXIEEY(OA^9m(sOs*D%#=7iGaIN{Gy~=LS9YcX!*{+hLJfD%oJy#>xsUbb5LUdcmgR zd5dF2bTlYOiqP=?uMW%uA0LqQGFvGtF@lqm6Oy(#Kfk57_XdATd}87c66wZ`8|Ufi zhe;%5W#x|c_VG`?-R^Z#x8KFL%?y!9WX%T$A38AAHWhhh2uC&+Ztg_OJ5~`4desaF zBwb4AZJ7cXUxS`R1cenQ6~^~EKH;$$K}1SQM|FB}G1@;@`60kE>{zX+yu5E))cJtp zPrj>aYHE`~n~nAUo8--axfTB#tM%WU#N+W?s($ea31phOy3`O7iIi|?;^YbV^)+R6 zxC(c&{YB{MJ>tt7Uxmmaknq>90j*Gm+SU6B3JX(cK7M>EoE!WEhTu`hR$C9ySG;~b z4tUCdPo~+?YkGV4Z?s?NGqb*puVUijm*H*Qe6SCfE{R^A<#c)}6XvF)-~2o5!w7S7 z&2N7-?xZhsF&!9q8@N}`+;-FPhU%|hy)HF6h1Up4>1^Pf18#Caugq)*WM^lK1OsCH z;U*_4D(bc~QSUzo|EDrC6_dVdIymqoefe2;pEXrnPC!!fM&TeRU|t&CgwfGaJefjf z#h}|!V31qZqu(qN)(`}zngjeq74FpX-~F4SV@o}JR}>Xu}h+D0(c+Eh$< z1^S`;{Y&B)2I-U4F4HV8FSmE;M#&qe9_{RSBjHTjUBbC9dKbpW$3cFEhleRbGc#{{ zcu1}M=i%XT|NhTRvsV$ zJ6Ii*R8)W(`Y|*#Uhhv2DRCP8{~q^!edP->hM*=oda9%2 zH;d_}zhd;O88%`D#dFLGtekbE?HllZ&^(16wWEz(Y!t&CK1D^v?^Ct+b?)3L5Zyd2 zUMgAj1xrt)v7teLiN;_*H8nLWJNvHeEl_nk27P^f2h&keR0%r24;OQ(DJg@uTBzT9 zPFac(gpIO5=pY78PEOG8?CkC`Yz>W#+Wd^P-ocL_1Av!ciQAm0Pa_a~PYxb@wC$Vm z0?oa!x%t_h^eQheFU>x%?A<#*Z*Qi*mM$&}TU%bXwpl>4t&qz9tc?<EUf-KYw14l|4B*Nv3!A^rVK2jg1AwpgiJot<8F>py3O&i?_F| zfg3mj=y<@`(NR%AZm)so`mNhQFS4>wR85VIkLNlQLPA1-o&>K5Kt3@sF()Sn>@X;P z`|4F}2j=kbkdLCyog`K`SZY>w5Ud{EmZB$+#C;V!lpY4NuJ>C9mf#}1bFA(@D1+oC zUpgu04*}B#SSxA$n>2Eqhh%GuGjcQn8YX6DPn_6dWxYnum$9?dfsn>||SxhO`6Wr8K>nA_V|5|@Ge!zut)%`7i3udlDSm0H{m z00-^u?cLkkv#PRBz~lAM=mwuA zzRI)OoTj|8Qb!^+ww`G0rZ*#($XZ61qG^VCW(FGZLJUs|)1P+nf;6Ii(0ZN2=PXJf zJ?|awudn{7FuZZ2(QBr)>#1;ecQ*w&xtN$3I3w}K`tt1T&e`#1;R`Vh4P4;hu_x}B<0u={Txmr`$4>y`+S=#rJIkx8j13K+8We*H zbo(3gLe<~n-aXN$Es%EqrS|7`x>29jgydiXT+`bD?N?+u+qs&7%0wU&_@`ZbURuKh z<$!Q=ggBTeZ#R%@;O~}KhXWF8z zDs34VLU%aYq3AY77$p!%6%{vl891*7T7dKg1mtyHgj1=<8Wal&3YJ;C%F5yl)8tL- z<^iUU0p{)PEdvMO18R;ujQ8F;``C1znd%WX?WP#Prb4IRy)L5zGvmt#S%2^XM48v? za)CAyl$|Ua-0-;sV+;zU9!_;#1o`RHCtw)LN$%>on;RRGMB?>lDz#mJj4{=vBzJ;= zypqz=+}zx+U#L`ue3PZs)zy`im4yW%ByIhtPn=v_ky`n^F4y~lFc>Wnr1wYLk&NfG zSHfQm^i;O{^@`}nY~yzMfg~ed$Pnf)G8_C+Wo+NXzdzjqI%MZ-%B3_ZI~D&8PW2#w z6FG#CqvaAz^@pM&RI%CJyLapA>c}+x{1l?w01w;(bUWdYH|X~E?c0Ok5Pkiwmb$(>;bB%2E#mM*B;$%!`!`OS013GugM06#Q1Ov^ zsxqo;FP{b2=~b`?`}MfXu|EzD-VJQDl}d__MOC72>6>M(QoO3lO zIEP&Oq(2hH-x3|yF}q9&!0w-M-yR0h0N#uSQoxRY+}=*9UZE%O)}|tfN5iGsT1rZ)G^NGSONq1ET1{R)ayA$c8uyZqN~G4cFZFX# zmq#*=*xBzWvb@b47}=xnyY?-HQO{pc??F^s}+&&snl|_J$pFtDUjf*T6P{4vFh9Ff#J^@4rUhRZCyrxu3oxyCx59}SXEPHsq+pWrCNTv$8|jy{NVefhlDwqQ`=jf1??#vNcOkW*R_D4=!EdIskBDx-2d%?E3TFe*r6O z7ca&<)bN~C108B1Hh7+$5-5vZHtD_Ex;mQ3)~U1o_XfqX;A((j(3`v6EEri2R@c6) zLvWiEzsbfRR+2SiK|ukYWDNle?)`B*kEgiqUEL2w`KQ9i%3#C+C~Cj|eB zX-R6J`py@VugHrRKl-lz0KtFxQb>)dthDq4m{b`VAvQkaVF-je*n>vH1A)El=;#nJ zR`FR%HYg4>JL>2yop89TSX@h|#x6Lkjo~wsi`|5(8IvLzwjvfr5l|?oLDx@8K$mF- zpQoeaU}w*0+68tElt&>2+UMaM0EM<7ZGKar!@;YAwp|ZH@!#|G{7cL|v$%CDJvsS} z*NoQpgv8QPk%DoJz+Jrcib~M_Ys+$ioW>bd=nGQg;& zqhF%tWn^TOm1CrGVo(LZ%=*mtxB>TT z#a33>=+qYMjmSF!TZ6w#+$IqWW4d3|9 zV7~JgSi@4SFnf9)^{n*t^wd;uz~HL>8%S1GFk=9V;df5I`f*Q73!j7px^R#|0|;XO zrGfk_u6+@ak$e05fP2os;O-jLAK>DairQ4}hO~mr%CRRf#Y8A|jEs!bWZFSZO-*c3 z%X>#aSao*l2n!2m*;fq{mwkkVOOlfr1YX1VQ@&yqRF#!4!lOV0&dx#_?QzHZ|IONf zDGREE2<9V$aF#gjs$npPTj1g1`VWgmSp^L&0?gMF(7L0mTl*JhjBbsy64VPwaYR^Q zm{~vBErj{X4!kOiLpNZ4@vMcmcxJ=w!{oR z!i6|xq7_ Date: Thu, 17 Nov 2011 00:17:08 +1100 Subject: [PATCH 11/24] Merge with addressbook changes --- src/qt/addressbookpage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qt/addressbookpage.cpp b/src/qt/addressbookpage.cpp index 06a926e59a8fb..d207fe30faa34 100644 --- a/src/qt/addressbookpage.cpp +++ b/src/qt/addressbookpage.cpp @@ -241,7 +241,7 @@ void AddressBookPage::exportClicked() void AddressBookPage::on_showQRCode_clicked() { #ifdef USE_QRCODE - QTableView *table = getCurrentTable(); + QTableView *table = ui->tableView; QModelIndexList indexes = table->selectionModel()->selectedRows(AddressTableModel::Address); From 97641d8f5fce00c392811b08148867f3dd917856 Mon Sep 17 00:00:00 2001 From: Gavin Andresen Date: Thu, 29 Sep 2011 09:46:52 -0400 Subject: [PATCH 12/24] 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 12d3f9e83a3f4..4b2dc9a1a6e3c 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 a3f397a2a7b2645d1ab701d796f9f1268fcf89ac Mon Sep 17 00:00:00 2001 From: Gavin Andresen Date: Tue, 11 Oct 2011 19:50:06 -0400 Subject: [PATCH 13/24] 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 de71887935650..2b8c9ab6ddb47 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" @@ -110,6 +111,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); \ @@ -117,7 +120,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 6c48199546d4d..42bf3dd554aef 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 @@ -139,6 +139,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); \ @@ -146,7 +148,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 01e6691254a53..e9b7b4517aeeb 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 c7fa74e96d7c0..d52ac74982566 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 f30f7f8936d6b..fff30ef5eb6b0 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 13feb86b9770d..f3fa5c3a1b2ca 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 39a7c88e13520..c7f45a0877fe9 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 e6eb0f054f6b8..3268343bbe12c 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 42c8275afe007..35cb35b25ad07 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 c5d45e215eb1d..efdc8a6aeb77f 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 8c8b99e1b229b..9571c4738269f 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 1b714b5a8ef5485c780d55388025137ff6eb2d79 Mon Sep 17 00:00:00 2001 From: Gavin Andresen Date: Wed, 28 Sep 2011 12:30:06 -0400 Subject: [PATCH 14/24] 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 | 119 ++++++++++++++- src/script.cpp | 223 +++++++++++++++++++--------- src/script.h | 25 ++-- src/test/multisig_tests.cpp | 288 ++++++++++++++++++++++++++++++++++++ src/wallet.cpp | 9 +- 5 files changed, 575 insertions(+), 89 deletions(-) create mode 100644 src/test/multisig_tests.cpp diff --git a/src/bitcoinrpc.cpp b/src/bitcoinrpc.cpp index bb8d8e2d7734f..842df0169635f 100644 --- a/src/bitcoinrpc.cpp +++ b/src/bitcoinrpc.cpp @@ -41,7 +41,6 @@ static std::string strRPCUserColonPass; static int64 nWalletUnlockTime; static CCriticalSection cs_nWalletUnlockTime; - Object JSONRPCError(int code, const string& message) { Object error; @@ -931,6 +930,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 { @@ -1591,7 +1685,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])); } @@ -1837,6 +1941,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), @@ -2478,6 +2583,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 4b2dc9a1a6e3c..6a7913b0d5438 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 e61ea2fd7eb72..a5a1e1868c9cb 100644 --- a/src/script.h +++ b/src/script.h @@ -574,6 +574,13 @@ class CScript : public std::vector 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 @@ class CScript : public std::vector } - 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 @@ class CScript : public std::vector { 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 0000000000000..459d112369489 --- /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 28babdb3e2c8a..46d5b5f18e693 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -979,12 +979,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 c4a533a7b5f9c9c1a62763c35858358c6a44c0af Mon Sep 17 00:00:00 2001 From: Gavin Andresen Date: Mon, 3 Oct 2011 16:14:13 -0400 Subject: [PATCH 15/24] 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 c7f45a0877fe9..4b52b74cfe6ea 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 f4cfa279321c3bcf35d9d30297bae7a47d18af6f Mon Sep 17 00:00:00 2001 From: Gavin Andresen Date: Mon, 3 Oct 2011 13:05:43 -0400 Subject: [PATCH 16/24] 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 cace423d6ee0c..0478bfc64625f 100644 --- a/src/base58.h +++ b/src/base58.h @@ -268,6 +268,12 @@ class CBitcoinAddress : public CBase58Data return 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 @@ class CBitcoinAddress : public CBase58Data 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 @@ class CBitcoinAddress : public CBase58Data } 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 842df0169635f..af04c66330002 100644 --- a/src/bitcoinrpc.cpp +++ b/src/bitcoinrpc.cpp @@ -661,7 +661,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); @@ -930,56 +930,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 @@ -997,32 +971,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(); } @@ -1694,6 +1659,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)) @@ -1941,7 +1924,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), @@ -2583,16 +2566,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 9ac93b3506d08..5c68104c5e87d 100644 --- a/src/db.cpp +++ b/src/db.cpp @@ -934,6 +934,15 @@ int CWalletDB::LoadWallet(CWallet* pwallet) if (nMinVersion > 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 15bfb29c8e934..99dd88b491a09 100644 --- a/src/db.h +++ b/src/db.h @@ -420,6 +420,18 @@ class CWalletDB : public CDB 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 68f57e7e0efa9..67b118a431607 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 4d889146fc5bb..9aaa0b932b471 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -28,17 +28,23 @@ class CKeyStore // This may succeed even if GetKey fails (e.g., encrypted wallets) 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(); }; 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); @@ -62,6 +68,9 @@ class CBasicKeyStore : public CKeyStore } 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 a7871fcc16874..a795617b2e372 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -241,6 +241,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) @@ -364,15 +423,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"); @@ -416,15 +466,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)) @@ -821,8 +890,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 @@ -834,43 +956,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())); @@ -886,7 +974,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) @@ -960,7 +1048,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 @@ -1018,14 +1107,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) { @@ -1286,8 +1382,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 @@ -2822,9 +2931,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)); @@ -2833,7 +2939,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 3870cee864bdf..ddbcbdbb3609a 100644 --- a/src/main.h +++ b/src/main.h @@ -489,26 +489,8 @@ class CTransaction 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 { @@ -636,8 +618,11 @@ class CTransaction 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); @@ -846,13 +831,6 @@ class CBlock 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 6a7913b0d5438..c103d57510e27 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 a5a1e1868c9cb..ee0be02a82cf5 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 @@ class CScript : public std::vector return true; } + // Encode/decode small integers: static int DecodeOP_N(opcodetype opcode) { if (opcode == OP_0) @@ -581,22 +449,44 @@ class CScript : public std::vector 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 @@ class CScript : public std::vector 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 @@ class CScript : public std::vector } - 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 @@ class CScript : public std::vector +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 459d112369489..75c764dd65238 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 0000000000000..857d04bc6dc6b --- /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 f3fa5c3a1b2ca..3d1c218700d98 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 46d5b5f18e693..bc56b0d0f89e4 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()) @@ -365,6 +374,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; @@ -434,8 +453,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 ca7cf67317955..2bab419cb7449 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -69,6 +69,8 @@ class CWallet : public CCryptoKeyStore 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); @@ -110,15 +112,7 @@ class CWallet : public CCryptoKeyStore 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 9db95d35d3c3bf1c41674c7e1e4da8816abb9690 Mon Sep 17 00:00:00 2001 From: Gavin Andresen Date: Thu, 13 Oct 2011 16:03:58 -0400 Subject: [PATCH 17/24] Put OP_EVAL string in coinbase of generated blocks --- src/base58.h | 10 +++++----- src/main.cpp | 6 ++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/base58.h b/src/base58.h index 0478bfc64625f..dc28285686bc9 100644 --- a/src/base58.h +++ b/src/base58.h @@ -270,7 +270,7 @@ class CBitcoinAddress : public CBase58Data bool SetScriptHash160(const uint160& hash160) { - SetData(fTestNet ? 112 : 1, &hash160, 20); + SetData(fTestNet ? 111^2 : 2, &hash160, 20); return true; } @@ -284,7 +284,7 @@ class CBitcoinAddress : public CBase58Data 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 @@ class CBitcoinAddress : public CBase58Data nExpectedSize = 20; fExpectTestNet = true; break; - case 112: + case 111^2: nExpectedSize = 20; fExpectTestNet = true; break; @@ -308,8 +308,8 @@ class CBitcoinAddress : public CBase58Data 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 a795617b2e372..130db2408f561 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2995,6 +2995,12 @@ void IncrementExtraNonce(CBlock* pblock, CBlockIndex* pindexPrev, unsigned int& ++nExtraNonce; pblock->vtx[0].vin[0].scriptSig = CScript() << pblock->nTime << CBigNum(nExtraNonce); pblock->hashMerkleRoot = pblock->BuildMerkleTree(); + + // 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); } From dad31429d9fd49e4e8e6e4705e5e18bbb9f2f18e Mon Sep 17 00:00:00 2001 From: Gavin Andresen Date: Wed, 19 Oct 2011 09:50:15 -0400 Subject: [PATCH 18/24] 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 af04c66330002..2fdb7c8312fbd 100644 --- a/src/bitcoinrpc.cpp +++ b/src/bitcoinrpc.cpp @@ -940,6 +940,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 309e72221e731878325c9ed32f12d8545e2001be Mon Sep 17 00:00:00 2001 From: Gavin Andresen Date: Fri, 21 Oct 2011 13:12:05 -0400 Subject: [PATCH 19/24] 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 c103d57510e27..e60399120ff47 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 857d04bc6dc6b..6c683b57297e1 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 186b01d8f07ae7915efb75900f7ff9de5b969c85 Mon Sep 17 00:00:00 2001 From: Gavin Andresen Date: Tue, 8 Nov 2011 13:20:29 -0500 Subject: [PATCH 20/24] 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 2fdb7c8312fbd..d87af1581f583 100644 --- a/src/bitcoinrpc.cpp +++ b/src/bitcoinrpc.cpp @@ -1668,10 +1668,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 5c68104c5e87d..152586285aed2 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 99dd88b491a09..e593ae28ed172 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 @@ class CWalletDB : public CDB 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 67b118a431607..1213ebf07c2a0 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 9aaa0b932b471..f611c1a7cf815 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 @@ -28,23 +29,24 @@ class CKeyStore // This may succeed even if GetKey fails (e.g., encrypted wallets) 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(); }; 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); @@ -68,9 +70,9 @@ class CBasicKeyStore : public CKeyStore } 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 130db2408f561..d66c069a14bf8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -245,13 +245,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)) @@ -270,7 +270,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 @@ -282,18 +282,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()); } } @@ -476,7 +478,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 @@ -973,9 +975,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 @@ -1049,7 +1069,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 ddbcbdbb3609a..6e8c5bd622094 100644 --- a/src/main.h +++ b/src/main.h @@ -490,7 +490,7 @@ class CTransaction } bool IsStandard() const; - bool IsStandardInputs(std::map > mapInputs) const; + bool AreInputsStandard(std::map > mapInputs) const; int64 GetValueOut() const { @@ -618,6 +618,8 @@ class CTransaction 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 e60399120ff47..5487c01fabc1f 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 ee0be02a82cf5..b671e159632a8 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 @@ class CScript : public std::vector -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 75c764dd65238..58f62b9542ef3 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 6c683b57297e1..c44642c6e990f 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 3d1c218700d98..22885a64387b5 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 bc56b0d0f89e4..7540e9cc5ff65 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) @@ -377,6 +377,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 2bab419cb7449..23cbf292ea119 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 @@ class CWallet : public CCryptoKeyStore 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 b6084f158313c50f7bb4e9b90ce2ac20adfce1ba Mon Sep 17 00:00:00 2001 From: Gavin Andresen Date: Mon, 5 Dec 2011 10:32:35 -0500 Subject: [PATCH 21/24] 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 7540e9cc5ff65..c29a312bdc6ea 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -385,7 +385,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 a880b29cab0fe95161b54b61159321021d5a3219 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Wed, 14 Dec 2011 01:03:55 -0500 Subject: [PATCH 22/24] Bugfix: fForRelay should be false when deciding required fee to include in blocks During the rushed transition from 0.01 BTC to 0.0005 BTC fees, we took the approach of dropping the relay and block-inclusion fee to 0.0005 BTC immediately, and only delayed adjusting the sending fee for the next release. Afterward, the relay fee was lowered to 0.0001 BTC to avoid having the same problem in the future. However, the block inclusion code was left setting fForRelay to true! This fixes that, so the lower 0.0001 BTC allowance is (as intended) only permitted for real relaying. --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 6a3bacc78e97b..59db0aaf04738 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2757,7 +2757,7 @@ CBlock* CreateNewBlock(CReserveKey& reservekey) // Transaction fee required depends on block size bool fAllowFree = (nBlockSize + nTxSize < 4000 || CTransaction::AllowFree(dPriority)); - int64 nMinFee = tx.GetMinFee(nBlockSize, fAllowFree, true); + int64 nMinFee = tx.GetMinFee(nBlockSize, fAllowFree); // Connecting shouldn't fail due to dependency on other memory pool transactions // because we're already processing them in order of dependency From dbbf1d4a48c8761a67a4477bef48f17c0badef7b Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Mon, 3 Oct 2011 12:55:47 -0400 Subject: [PATCH 23/24] GetMinFee takes a mode parameter (GMF_{BLOCK,RELAY,SEND}) instead of fForRelay --- src/main.cpp | 4 ++-- src/main.h | 11 +++++++++-- src/wallet.cpp | 3 ++- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 59db0aaf04738..807279a55366f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -419,7 +419,7 @@ bool CTransaction::AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs, bool* pfMi } // Don't accept it if it can't get into a block - if (nFees < GetMinFee(1000, true, true)) + if (nFees < GetMinFee(1000, true, GMF_RELAY)) return error("AcceptToMemoryPool() : not enough fees"); // Continuously rate-limit free transactions @@ -2757,7 +2757,7 @@ CBlock* CreateNewBlock(CReserveKey& reservekey) // Transaction fee required depends on block size bool fAllowFree = (nBlockSize + nTxSize < 4000 || CTransaction::AllowFree(dPriority)); - int64 nMinFee = tx.GetMinFee(nBlockSize, fAllowFree); + int64 nMinFee = tx.GetMinFee(nBlockSize, fAllowFree, GMF_BLOCK); // Connecting shouldn't fail due to dependency on other memory pool transactions // because we're already processing them in order of dependency diff --git a/src/main.h b/src/main.h index c400145d015bc..a699331884daf 100644 --- a/src/main.h +++ b/src/main.h @@ -387,6 +387,13 @@ class CTxOut +enum GetMinFee_mode +{ + GMF_BLOCK, + GMF_RELAY, + GMF_SEND, +}; + // // The basic transaction that is broadcasted on the network and contained in // blocks. A transaction can contain multiple inputs and outputs. @@ -523,10 +530,10 @@ class CTransaction return dPriority > COIN * 144 / 250; } - int64 GetMinFee(unsigned int nBlockSize=1, bool fAllowFree=true, bool fForRelay=false) const + int64 GetMinFee(unsigned int nBlockSize=1, bool fAllowFree=true, enum GetMinFee_mode mode=GMF_BLOCK) const { // Base fee is either MIN_TX_FEE or MIN_RELAY_TX_FEE - int64 nBaseFee = fForRelay ? MIN_RELAY_TX_FEE : MIN_TX_FEE; + int64 nBaseFee = (mode == GMF_RELAY) ? MIN_RELAY_TX_FEE : MIN_TX_FEE; unsigned int nBytes = ::GetSerializeSize(*this, SER_NETWORK); unsigned int nNewBlockSize = nBlockSize + nBytes; diff --git a/src/wallet.cpp b/src/wallet.cpp index 8bbb80cf254a7..2c5aa03aad0a2 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -931,6 +931,7 @@ bool CWallet::CreateTransaction(const vector >& vecSend, CW int64 nChange = nValueIn - nValue - nFeeRet; // if sub-cent change is required, the fee must be raised to at least MIN_TX_FEE // or until nChange becomes zero + // NOTE: this depends on the exact behaviour of GetMinFee if (nFeeRet < MIN_TX_FEE && nChange > 0 && nChange < CENT) { int64 nMoveToFee = min(nChange, MIN_TX_FEE - nFeeRet); @@ -984,7 +985,7 @@ bool CWallet::CreateTransaction(const vector >& vecSend, CW // Check that enough fee is included int64 nPayFee = nTransactionFee * (1 + (int64)nBytes / 1000); bool fAllowFree = CTransaction::AllowFree(dPriority); - int64 nMinFee = wtxNew.GetMinFee(1, fAllowFree); + int64 nMinFee = wtxNew.GetMinFee(1, fAllowFree, GMF_SEND); if (nFeeRet < max(nPayFee, nMinFee)) { nFeeRet = max(nPayFee, nMinFee); From 3765dbfb38e23a814c3df1985cf198db27bc5848 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Mon, 10 Oct 2011 22:56:33 -0400 Subject: [PATCH 24/24] Bitcoin-Qt signmessage GUI --- bitcoin-qt.pro | 3 + src/bitcoinrpc.cpp | 2 - src/main.cpp | 3 + src/main.h | 1 + src/qt/addressbookpage.cpp | 22 +++++ src/qt/addressbookpage.h | 1 + src/qt/bitcoingui.cpp | 31 ++++++ src/qt/bitcoingui.h | 6 ++ src/qt/forms/addressbookpage.ui | 14 +++ src/qt/forms/messagepage.ui | 170 ++++++++++++++++++++++++++++++++ src/qt/messagepage.cpp | 107 ++++++++++++++++++++ src/qt/messagepage.h | 38 +++++++ 12 files changed, 396 insertions(+), 2 deletions(-) create mode 100644 src/qt/forms/messagepage.ui create mode 100644 src/qt/messagepage.cpp create mode 100644 src/qt/messagepage.h diff --git a/bitcoin-qt.pro b/bitcoin-qt.pro index 66b53c2367708..b026574f274b6 100644 --- a/bitcoin-qt.pro +++ b/bitcoin-qt.pro @@ -83,6 +83,7 @@ HEADERS += src/qt/bitcoingui.h \ src/qt/optionsdialog.h \ src/qt/sendcoinsdialog.h \ src/qt/addressbookpage.h \ + src/qt/messagepage.h \ src/qt/aboutdialog.h \ src/qt/editaddressdialog.h \ src/qt/bitcoinaddressvalidator.h \ @@ -144,6 +145,7 @@ SOURCES += src/qt/bitcoin.cpp src/qt/bitcoingui.cpp \ src/qt/optionsdialog.cpp \ src/qt/sendcoinsdialog.cpp \ src/qt/addressbookpage.cpp \ + src/qt/messagepage.cpp \ src/qt/aboutdialog.cpp \ src/qt/editaddressdialog.cpp \ src/qt/bitcoinaddressvalidator.cpp \ @@ -190,6 +192,7 @@ RESOURCES += \ FORMS += \ src/qt/forms/sendcoinsdialog.ui \ src/qt/forms/addressbookpage.ui \ + src/qt/forms/messagepage.ui \ src/qt/forms/aboutdialog.ui \ src/qt/forms/editaddressdialog.ui \ src/qt/forms/transactiondescdialog.ui \ diff --git a/src/bitcoinrpc.cpp b/src/bitcoinrpc.cpp index bb8d8e2d7734f..ed2b1d892ba98 100644 --- a/src/bitcoinrpc.cpp +++ b/src/bitcoinrpc.cpp @@ -532,8 +532,6 @@ Value sendtoaddress(const Array& params, bool fHelp) return wtx.GetHash().GetHex(); } -static const string strMessageMagic = "Bitcoin Signed Message:\n"; - Value signmessage(const Array& params, bool fHelp) { if (fHelp || params.size() != 2) diff --git a/src/main.cpp b/src/main.cpp index a7871fcc16874..751aa738faf3f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -48,6 +48,9 @@ map mapOrphanTransactions; multimap mapOrphanTransactionsByPrev; +const string strMessageMagic = "Bitcoin Signed Message:\n"; + + double dHashesPerSec; int64 nHPSTimerStart; diff --git a/src/main.h b/src/main.h index 3870cee864bdf..c5e683ff6432b 100644 --- a/src/main.h +++ b/src/main.h @@ -60,6 +60,7 @@ extern CBigNum bnBestInvalidWork; extern uint256 hashBestChain; extern CBlockIndex* pindexBest; extern unsigned int nTransactionsUpdated; +extern const std::string strMessageMagic; extern double dHashesPerSec; extern int64 nHPSTimerStart; extern int64 nTimeBestReceived; diff --git a/src/qt/addressbookpage.cpp b/src/qt/addressbookpage.cpp index 0a147c9e1047f..80e61acce5c01 100644 --- a/src/qt/addressbookpage.cpp +++ b/src/qt/addressbookpage.cpp @@ -2,6 +2,7 @@ #include "ui_addressbookpage.h" #include "addresstablemodel.h" +#include "bitcoingui.h" #include "editaddressdialog.h" #include "csvmodelwriter.h" @@ -114,6 +115,24 @@ void AddressBookPage::on_copyToClipboard_clicked() } } +void AddressBookPage::on_signMessage_clicked() +{ + QTableView *table = ui->tableView; + QModelIndexList indexes = table->selectionModel()->selectedRows(AddressTableModel::Address); + QString addr; + + foreach (QModelIndex index, indexes) + { + QVariant address = index.data(); + addr = address.toString(); + } + + QObject *qoGUI = parent()->parent(); + BitcoinGUI *gui = qobject_cast(qoGUI); + if (gui) + gui->gotoMessagePage(addr); +} + void AddressBookPage::on_newAddressButton_clicked() { if(!model) @@ -163,9 +182,11 @@ void AddressBookPage::selectionChanged() { case SendingTab: ui->deleteButton->setEnabled(true); + ui->signMessage->setEnabled(false); break; case ReceivingTab: ui->deleteButton->setEnabled(false); + ui->signMessage->setEnabled(true); break; } ui->copyToClipboard->setEnabled(true); @@ -174,6 +195,7 @@ void AddressBookPage::selectionChanged() { ui->deleteButton->setEnabled(false); ui->copyToClipboard->setEnabled(false); + ui->signMessage->setEnabled(false); } } diff --git a/src/qt/addressbookpage.h b/src/qt/addressbookpage.h index 1a97f3d60230f..baed3917b41bf 100644 --- a/src/qt/addressbookpage.h +++ b/src/qt/addressbookpage.h @@ -53,6 +53,7 @@ private slots: void on_deleteButton_clicked(); void on_newAddressButton_clicked(); void on_copyToClipboard_clicked(); + void on_signMessage_clicked(); void selectionChanged(); }; diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 60c75286a7286..fdcec77826804 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -8,6 +8,7 @@ #include "transactiontablemodel.h" #include "addressbookpage.h" #include "sendcoinsdialog.h" +#include "messagepage.h" #include "optionsdialog.h" #include "aboutdialog.h" #include "clientmodel.h" @@ -98,12 +99,15 @@ BitcoinGUI::BitcoinGUI(QWidget *parent): sendCoinsPage = new SendCoinsDialog(this); + messagePage = new MessagePage(this); + centralWidget = new QStackedWidget(this); centralWidget->addWidget(overviewPage); centralWidget->addWidget(transactionsPage); centralWidget->addWidget(addressBookPage); centralWidget->addWidget(receiveCoinsPage); centralWidget->addWidget(sendCoinsPage); + centralWidget->addWidget(messagePage); setCentralWidget(centralWidget); // Create status bar @@ -192,6 +196,11 @@ void BitcoinGUI::createActions() sendCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_2)); tabGroup->addAction(sendCoinsAction); + messageAction = new QAction(QIcon(":/icons/edit"), tr("Sign &message"), this); + messageAction->setToolTip(tr("Prove you control an address")); + messageAction->setCheckable(true); + tabGroup->addAction(messageAction); + connect(overviewAction, SIGNAL(triggered()), this, SLOT(show())); connect(overviewAction, SIGNAL(triggered()), this, SLOT(gotoOverviewPage())); connect(historyAction, SIGNAL(triggered()), this, SLOT(show())); @@ -202,6 +211,8 @@ void BitcoinGUI::createActions() connect(receiveCoinsAction, SIGNAL(triggered()), this, SLOT(gotoReceiveCoinsPage())); connect(sendCoinsAction, SIGNAL(triggered()), this, SLOT(show())); connect(sendCoinsAction, SIGNAL(triggered()), this, SLOT(gotoSendCoinsPage())); + connect(messageAction, SIGNAL(triggered()), this, SLOT(show())); + connect(messageAction, SIGNAL(triggered()), this, SLOT(gotoMessagePage())); quitAction = new QAction(QIcon(":/icons/quit"), tr("E&xit"), this); quitAction->setToolTip(tr("Quit application")); @@ -264,6 +275,9 @@ void BitcoinGUI::createToolBars() toolbar->addAction(receiveCoinsAction); toolbar->addAction(historyAction); toolbar->addAction(addressBookAction); +#ifdef FIRST_CLASS_MESSAGING + toolbar->addAction(messageAction); +#endif QToolBar *toolbar2 = addToolBar(tr("Actions toolbar")); toolbar2->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); @@ -318,6 +332,7 @@ void BitcoinGUI::setWalletModel(WalletModel *walletModel) addressBookPage->setModel(walletModel->getAddressTableModel()); receiveCoinsPage->setModel(walletModel->getAddressTableModel()); sendCoinsPage->setModel(walletModel); + messagePage->setModel(walletModel); setEncryptionStatus(walletModel->getEncryptionStatus()); connect(walletModel, SIGNAL(encryptionStatusChanged(int)), this, SLOT(setEncryptionStatus(int))); @@ -353,6 +368,7 @@ void BitcoinGUI::createTrayIcon() // Configuration of the tray icon (or dock icon) icon menu trayIconMenu->addAction(openBitcoinAction); trayIconMenu->addSeparator(); + trayIconMenu->addAction(messageAction); trayIconMenu->addAction(receiveCoinsAction); trayIconMenu->addAction(sendCoinsAction); trayIconMenu->addSeparator(); @@ -644,6 +660,21 @@ void BitcoinGUI::gotoSendCoinsPage() disconnect(exportAction, SIGNAL(triggered()), 0, 0); } +void BitcoinGUI::gotoMessagePage() +{ + messageAction->setChecked(true); + centralWidget->setCurrentWidget(messagePage); + + exportAction->setEnabled(false); + disconnect(exportAction, SIGNAL(triggered()), 0, 0); +} + +void BitcoinGUI::gotoMessagePage(QString addr) +{ + gotoMessagePage(); + messagePage->setAddress(addr); +} + void BitcoinGUI::dragEnterEvent(QDragEnterEvent *event) { // Accept only URLs diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index ecb356dc35c56..164d2b91ec40c 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -11,6 +11,7 @@ class TransactionView; class OverviewPage; class AddressBookPage; class SendCoinsDialog; +class MessagePage; class Notificator; QT_BEGIN_NAMESPACE @@ -62,6 +63,7 @@ class BitcoinGUI : public QMainWindow AddressBookPage *addressBookPage; AddressBookPage *receiveCoinsPage; SendCoinsDialog *sendCoinsPage; + MessagePage *messagePage; QLabel *labelEncryptionIcon; QLabel *labelConnectionsIcon; @@ -75,6 +77,7 @@ class BitcoinGUI : public QMainWindow QAction *quitAction; QAction *sendCoinsAction; QAction *addressBookAction; + QAction *messageAction; QAction *aboutAction; QAction *receiveCoinsAction; QAction *optionsAction; @@ -123,6 +126,9 @@ public slots: */ void askFee(qint64 nFeeRequired, bool *payFee); + void gotoMessagePage(); + void gotoMessagePage(QString); + private slots: /** Switch to overview (home) page */ void gotoOverviewPage(); diff --git a/src/qt/forms/addressbookpage.ui b/src/qt/forms/addressbookpage.ui index fb098c8280725..f3b5c455b5fb2 100644 --- a/src/qt/forms/addressbookpage.ui +++ b/src/qt/forms/addressbookpage.ui @@ -79,6 +79,20 @@ + + + + Sign a message to prove you own this address + + + &Sign Message + + + + :/icons/edit:/icons/edit + + + diff --git a/src/qt/forms/messagepage.ui b/src/qt/forms/messagepage.ui new file mode 100644 index 0000000000000..8afa4b59ddf9a --- /dev/null +++ b/src/qt/forms/messagepage.ui @@ -0,0 +1,170 @@ + + + MessagePage + + + + 0 + 0 + 627 + 380 + + + + Message + + + + + + You can sign messages with your addresses to prove you own them. Be careful to only sign statement you agree to with full details, as phishing attacks may try to trick you into signing access to them. + + + Qt::AutoText + + + true + + + + + + + 0 + + + + + The address to send the payment to (e.g. 1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L) + + + 34 + + + + + + + Choose adress from address book + + + + + + + :/icons/address-book:/icons/address-book + + + Alt+A + + + false + + + false + + + + + + + Paste address from clipboard + + + + + + + :/icons/editpaste:/icons/editpaste + + + Alt+P + + + false + + + + + + + + + Enter the message you want to sign here + + + + + + + + true + + + + Click "Sign Message" to get signature + + + true + + + + + + + + + Sign a message to prove you own this address + + + &Sign Message + + + + :/icons/edit:/icons/edit + + + + + + + Copy the currently selected address to the system clipboard + + + &Copy to Clipboard + + + + :/icons/editcopy:/icons/editcopy + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + QValidatedLineEdit + QLineEdit +
qvalidatedlineedit.h
+
+
+ + + + +
diff --git a/src/qt/messagepage.cpp b/src/qt/messagepage.cpp new file mode 100644 index 0000000000000..dee1837ed6748 --- /dev/null +++ b/src/qt/messagepage.cpp @@ -0,0 +1,107 @@ +#include +#include + +#include "main.h" +#include "wallet.h" +#include "init.h" +#include "util.h" + +#include "messagepage.h" +#include "ui_messagepage.h" + +#include "addressbookpage.h" +#include "guiutil.h" +#include "walletmodel.h" + +#include +#include +#include +#include +#include + +MessagePage::MessagePage(QWidget *parent) : + QDialog(parent), + ui(new Ui::MessagePage) +{ + ui->setupUi(this); + + GUIUtil::setupAddressWidget(ui->signFrom, this); +} + +MessagePage::~MessagePage() +{ + delete ui; +} + +void MessagePage::setModel(WalletModel *model) +{ + this->model = model; +} + +void MessagePage::setAddress(QString addr) +{ + ui->signFrom->setText(addr); + ui->message->setFocus(); +} + +void MessagePage::on_pasteButton_clicked() +{ + setAddress(QApplication::clipboard()->text()); +} + +void MessagePage::on_addressBookButton_clicked() +{ + AddressBookPage dlg(AddressBookPage::ForSending, AddressBookPage::ReceivingTab, this); + dlg.setModel(model->getAddressTableModel()); + if(dlg.exec()) + { + setAddress(dlg.getReturnValue()); + } +} + +void MessagePage::on_copyToClipboard_clicked() +{ + QApplication::clipboard()->setText(ui->signature->text()); +} + +void MessagePage::on_signMessage_clicked() +{ + QString address = ui->signFrom->text(); + + CBitcoinAddress addr(address.toStdString()); + if (!addr.IsValid()) + { + QMessageBox::critical(this, tr("Error signing"), tr("%1 is not a valid address.").arg(address), + QMessageBox::Abort, QMessageBox::Abort); + return; + } + + WalletModel::UnlockContext ctx(model->requestUnlock()); + if(!ctx.isValid()) + { + // Unlock wallet was cancelled + return; + } + + CKey key; + if (!pwalletMain->GetKey(addr, key)) + { + QMessageBox::critical(this, tr("Error signing"), tr("Private key for %1 is not available.").arg(address), + QMessageBox::Abort, QMessageBox::Abort); + return; + } + + CDataStream ss(SER_GETHASH); + ss << strMessageMagic; + ss << ui->message->document()->toPlainText().toStdString(); + + std::vector vchSig; + if (!key.SignCompact(Hash(ss.begin(), ss.end()), vchSig)) + { + QMessageBox::critical(this, tr("Error signing"), tr("Sign failed"), + QMessageBox::Abort, QMessageBox::Abort); + } + + ui->signature->setText(QString::fromStdString(EncodeBase64(&vchSig[0], vchSig.size()))); + ui->signature->setFont(GUIUtil::bitcoinAddressFont()); +} diff --git a/src/qt/messagepage.h b/src/qt/messagepage.h new file mode 100644 index 0000000000000..55e6228124c52 --- /dev/null +++ b/src/qt/messagepage.h @@ -0,0 +1,38 @@ +#ifndef MESSAGEPAGE_H +#define MESSAGEPAGE_H + +#include + +namespace Ui { + class MessagePage; +} +class WalletModel; + +QT_BEGIN_NAMESPACE +QT_END_NAMESPACE + +class MessagePage : public QDialog +{ + Q_OBJECT + +public: + explicit MessagePage(QWidget *parent = 0); + ~MessagePage(); + + void setModel(WalletModel *model); + + void setAddress(QString); + +private: + Ui::MessagePage *ui; + WalletModel *model; + +private slots: + void on_pasteButton_clicked(); + void on_addressBookButton_clicked(); + + void on_signMessage_clicked(); + void on_copyToClipboard_clicked(); +}; + +#endif // MESSAGEPAGE_H