From a20202d841675596c9380c9de0b14662d98552cd Mon Sep 17 00:00:00 2001 From: rejectedpromise Date: Tue, 7 Nov 2017 05:12:27 -0700 Subject: [PATCH] [QT] Multisignature GUI --- src/Makefile.qt.include | 4 + src/coincontrol.h | 13 +- src/keystore.cpp | 26 + src/keystore.h | 13 + src/qt/bitcoingui.cpp | 35 + src/qt/bitcoingui.h | 8 +- src/qt/coincontroldialog.cpp | 74 ++- src/qt/coincontroldialog.h | 6 +- src/qt/forms/coincontroldialog.ui | 7 +- src/qt/forms/multisigdialog.ui | 838 +++++++++++++++++++++++ src/qt/multisigdialog.cpp | 1024 +++++++++++++++++++++++++++++ src/qt/multisigdialog.h | 71 ++ src/qt/walletframe.cpp | 7 + src/qt/walletframe.h | 3 +- src/qt/walletmodel.cpp | 15 + src/qt/walletmodel.h | 5 + src/qt/walletview.cpp | 8 + src/qt/walletview.h | 3 +- src/wallet.cpp | 57 +- src/wallet.h | 9 + src/wallet_ismine.cpp | 30 +- src/wallet_ismine.h | 7 +- src/walletdb.cpp | 23 + src/walletdb.h | 3 + 24 files changed, 2259 insertions(+), 30 deletions(-) create mode 100644 src/qt/forms/multisigdialog.ui create mode 100644 src/qt/multisigdialog.cpp create mode 100644 src/qt/multisigdialog.h diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index b428a7d5c841..7d9181413760 100755 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -41,6 +41,7 @@ QT_FORMS_UI = \ qt/forms/intro.ui \ qt/forms/masternodelist.ui \ qt/forms/multisenddialog.ui \ + qt/forms/multisigdialog.ui\ qt/forms/openuridialog.ui \ qt/forms/optionsdialog.ui \ qt/forms/overviewpage.ui \ @@ -76,6 +77,7 @@ QT_MOC_CPP = \ qt/moc_macnotificationhandler.cpp \ qt/moc_masternodelist.cpp \ qt/moc_multisenddialog.cpp \ + qt/moc_multisigdialog.cpp\ qt/moc_notificator.cpp \ qt/moc_openuridialog.cpp \ qt/moc_optionsdialog.cpp \ @@ -149,6 +151,7 @@ BITCOIN_QT_H = \ qt/macnotificationhandler.h \ qt/masternodelist.h \ qt/multisenddialog.h \ + qt/multisigdialog.h\ qt/networkstyle.h \ qt/notificator.h \ qt/openuridialog.h \ @@ -282,6 +285,7 @@ BITCOIN_QT_CPP += \ qt/editaddressdialog.cpp \ qt/masternodelist.cpp \ qt/multisenddialog.cpp \ + qt/multisigdialog.cpp\ qt/openuridialog.cpp \ qt/overviewpage.cpp \ qt/paymentrequestplus.cpp \ diff --git a/src/coincontrol.h b/src/coincontrol.h index 835b775b8a40..0e9ffdfca8b5 100644 --- a/src/coincontrol.h +++ b/src/coincontrol.h @@ -38,7 +38,7 @@ class CCoinControl useSwiftTX = false; useObfuScation = false; fAllowOtherInputs = false; - fAllowWatchOnly = false; + fAllowWatchOnly = true; nMinimumTotalFee = 0; fSplitBlock = false; nSplitBlock = 1; @@ -75,6 +75,17 @@ class CCoinControl vOutpoints.assign(setSelected.begin(), setSelected.end()); } + unsigned int QuantitySelected() + { + return setSelected.size(); + } + + void SetSelection(std::set setSelected) + { + this->setSelected.clear(); + this->setSelected = setSelected; + } + private: std::set setSelected; }; diff --git a/src/keystore.cpp b/src/keystore.cpp index 7257ed254318..3eddb352bde5 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -86,3 +86,29 @@ bool CBasicKeyStore::HaveWatchOnly() const LOCK(cs_KeyStore); return (!setWatchOnly.empty()); } + +bool CBasicKeyStore::AddMultiSig(const CScript& dest) +{ + LOCK(cs_KeyStore); + setMultiSig.insert(dest); + return true; +} + +bool CBasicKeyStore::RemoveMultiSig(const CScript& dest) +{ + LOCK(cs_KeyStore); + setMultiSig.erase(dest); + return true; +} + +bool CBasicKeyStore::HaveMultiSig(const CScript& dest) const +{ + LOCK(cs_KeyStore); + return setMultiSig.count(dest) > 0; +} + +bool CBasicKeyStore::HaveMultiSig() const +{ + LOCK(cs_KeyStore); + return (!setMultiSig.empty()); +} diff --git a/src/keystore.h b/src/keystore.h index ec12865ad0cc..ca7a35632184 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -45,11 +45,18 @@ class CKeyStore virtual bool RemoveWatchOnly(const CScript& dest) = 0; virtual bool HaveWatchOnly(const CScript& dest) const = 0; virtual bool HaveWatchOnly() const = 0; + + //! Support for MultiSig addresses + virtual bool AddMultiSig(const CScript& dest) = 0; + virtual bool RemoveMultiSig(const CScript& dest) = 0; + virtual bool HaveMultiSig(const CScript& dest) const = 0; + virtual bool HaveMultiSig() const = 0; }; typedef std::map KeyMap; typedef std::map ScriptMap; typedef std::set WatchOnlySet; +typedef std::set MultiSigScriptSet; /** Basic key store, that keeps keys in an address->secret map */ class CBasicKeyStore : public CKeyStore @@ -58,6 +65,7 @@ class CBasicKeyStore : public CKeyStore KeyMap mapKeys; ScriptMap mapScripts; WatchOnlySet setWatchOnly; + MultiSigScriptSet setMultiSig; public: bool AddKeyPubKey(const CKey& key, const CPubKey& pubkey); @@ -102,6 +110,11 @@ class CBasicKeyStore : public CKeyStore virtual bool RemoveWatchOnly(const CScript& dest); virtual bool HaveWatchOnly(const CScript& dest) const; virtual bool HaveWatchOnly() const; + + virtual bool AddMultiSig(const CScript& dest); + virtual bool RemoveMultiSig(const CScript& dest); + virtual bool HaveMultiSig(const CScript& dest) const; + virtual bool HaveMultiSig() const; }; typedef std::vector > CKeyingMaterial; diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 0740421cef9b..c3f7522e9ae2 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -87,6 +87,9 @@ BitcoinGUI::BitcoinGUI(const NetworkStyle* networkStyle, QWidget* parent) : QMai signMessageAction(0), verifyMessageAction(0), bip38ToolAction(0), + multisigCreateAction(0), + multisigSpendAction(0), + multisigSignAction(0), aboutAction(0), receiveCoinsAction(0), privacyAction(0), @@ -433,6 +436,13 @@ void BitcoinGUI::createActions(const NetworkStyle* networkStyle) usedReceivingAddressesAction = new QAction(QIcon(":/icons/address-book"), tr("&Receiving addresses..."), this); usedReceivingAddressesAction->setStatusTip(tr("Show the list of used receiving addresses and labels")); + multisigCreateAction = new QAction(QIcon(":/icons/address-book"), tr("&Multisignature creation..."), this); + multisigCreateAction->setStatusTip(tr("Create a new multisignature address and add it to this wallet")); + multisigSpendAction = new QAction(QIcon(":/icons/send"), tr("&Multisignature spending..."), this); + multisigSpendAction->setStatusTip(tr("Spend from a multisignature address")); + multisigSignAction = new QAction(QIcon(":/icons/editpaste"), tr("&Multisignature signing..."), this); + multisigSignAction->setStatusTip(tr("Sign with a multisignature address")); + openAction = new QAction(QApplication::style()->standardIcon(QStyle::SP_FileIcon), tr("Open &URI..."), this); openAction->setStatusTip(tr("Open a PIVX: URI or payment request")); openBlockExplorerAction = new QAction(QIcon(":/icons/explorer"), tr("&Blockchain explorer"), this); @@ -462,6 +472,9 @@ void BitcoinGUI::createActions(const NetworkStyle* networkStyle) connect(usedReceivingAddressesAction, SIGNAL(triggered()), walletFrame, SLOT(usedReceivingAddresses())); connect(openAction, SIGNAL(triggered()), this, SLOT(openClicked())); connect(multiSendAction, SIGNAL(triggered()), this, SLOT(gotoMultiSendDialog())); + connect(multisigCreateAction, SIGNAL(triggered()), this, SLOT(gotoMultisigCreate())); + connect(multisigSpendAction, SIGNAL(triggered()), this, SLOT(gotoMultisigSpend())); + connect(multisigSignAction, SIGNAL(triggered()), this, SLOT(gotoMultisigSign())); } #endif // ENABLE_WALLET } @@ -487,6 +500,10 @@ void BitcoinGUI::createMenuBar() file->addAction(usedSendingAddressesAction); file->addAction(usedReceivingAddressesAction); file->addSeparator(); + file->addAction(multisigCreateAction); + file->addAction(multisigSpendAction); + file->addAction(multisigSignAction); + file->addSeparator(); } file->addAction(quitAction); @@ -634,6 +651,9 @@ void BitcoinGUI::setWalletActionsEnabled(bool enabled) changePassphraseAction->setEnabled(enabled); signMessageAction->setEnabled(enabled); verifyMessageAction->setEnabled(enabled); + multisigCreateAction->setEnabled(enabled); + multisigSpendAction->setEnabled(enabled); + multisigSignAction->setEnabled(enabled); bip38ToolAction->setEnabled(enabled); usedSendingAddressesAction->setEnabled(enabled); usedReceivingAddressesAction->setEnabled(enabled); @@ -795,6 +815,21 @@ void BitcoinGUI::gotoVerifyMessageTab(QString addr) if (walletFrame) walletFrame->gotoVerifyMessageTab(addr); } +void BitcoinGUI::gotoMultisigCreate() +{ + if(walletFrame) walletFrame->gotoMultisigDialog(0); +} + +void BitcoinGUI::gotoMultisigSpend() +{ + if(walletFrame) walletFrame->gotoMultisigDialog(1); +} + +void BitcoinGUI::gotoMultisigSign() +{ + if(walletFrame) walletFrame->gotoMultisigDialog(2); +} + void BitcoinGUI::gotoBip38Tool() { if (walletFrame) walletFrame->gotoBip38Tool(); diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index e8049b52b720..96a2c90bb673 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -101,6 +101,9 @@ class BitcoinGUI : public QMainWindow QAction* signMessageAction; QAction* verifyMessageAction; QAction* bip38ToolAction; + QAction* multisigCreateAction; + QAction* multisigSpendAction; + QAction* multisigSignAction; QAction* aboutAction; QAction* receiveCoinsAction; QAction* privacyAction; @@ -215,7 +218,10 @@ private slots: void gotoVerifyMessageTab(QString addr = ""); /** Show MultiSend Dialog */ void gotoMultiSendDialog(); - + /** Show MultiSig Dialog */ + void gotoMultisigCreate(); + void gotoMultisigSpend(); + void gotoMultisigSign(); /** Show BIP 38 tool - default to Encryption tab */ void gotoBip38Tool(); diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index 8beccbea2369..0c7864949f55 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -18,6 +18,7 @@ #include "main.h" #include "obfuscation.h" #include "wallet.h" +#include "multisigdialog.h" #include // for 'map_list_of()' @@ -37,11 +38,12 @@ QList CoinControlDialog::payAmounts; int CoinControlDialog::nSplitBlockDummy; CCoinControl* CoinControlDialog::coinControl = new CCoinControl(); -CoinControlDialog::CoinControlDialog(QWidget* parent) : QDialog(parent), +CoinControlDialog::CoinControlDialog(QWidget* parent, bool fMultisigEnabled) : QDialog(parent), ui(new Ui::CoinControlDialog), model(0) { ui->setupUi(this); + this->fMultisigEnabled = fMultisigEnabled; /* Open CSS when configured */ this->setStyleSheet(GUIUtil::loadStyleSheet()); @@ -171,6 +173,7 @@ void CoinControlDialog::setModel(WalletModel* model) updateView(); updateLabelLocked(); CoinControlDialog::updateLabels(model, this); + updateDialogLabels(); } } @@ -208,6 +211,7 @@ void CoinControlDialog::buttonSelectAllClicked() if (state == Qt::Unchecked) coinControl->UnSelectAll(); // just to be sure CoinControlDialog::updateLabels(model, this); + updateDialogLabels(); } // Toggle lock state @@ -219,6 +223,10 @@ void CoinControlDialog::buttonToggleLockClicked() ui->treeWidget->setEnabled(false); for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++) { item = ui->treeWidget->topLevelItem(i); + + if (item->text(COLUMN_TYPE) == "MultiSig") + continue; + COutPoint outpt(uint256(item->text(COLUMN_TXHASH).toStdString()), item->text(COLUMN_VOUT_INDEX).toUInt()); if (model->isLockedCoin(uint256(item->text(COLUMN_TXHASH).toStdString()), item->text(COLUMN_VOUT_INDEX).toUInt())) { model->unlockCoin(outpt); @@ -233,6 +241,7 @@ void CoinControlDialog::buttonToggleLockClicked() } ui->treeWidget->setEnabled(true); CoinControlDialog::updateLabels(model, this); + updateDialogLabels(); } else { QMessageBox msgBox; msgBox.setObjectName("lockMessageBox"); @@ -431,8 +440,10 @@ void CoinControlDialog::viewItemChanged(QTreeWidgetItem* item, int column) coinControl->Select(outpt); // selection changed -> update labels - if (ui->treeWidget->isEnabled()) // do not update on every click for (un)select all + if (ui->treeWidget->isEnabled()){ // do not update on every click for (un)select all CoinControlDialog::updateLabels(model, this); + updateDialogLabels(); + } } // todo: this is a temporary qt5 fix: when clicking a parent node in tree mode, the parent node // including all childs are partially selected. But the parent node should be fully selected @@ -486,6 +497,42 @@ void CoinControlDialog::updateLabelLocked() ui->labelLocked->setVisible(false); } +void CoinControlDialog::updateDialogLabels() +{ + + if (this->parentWidget() == nullptr) { + CoinControlDialog::updateLabels(model, this); + return; + } + + vector vCoinControl; + vector vOutputs; + coinControl->ListSelected(vCoinControl); + model->getOutputs(vCoinControl, vOutputs); + + CAmount nAmount = 0; + unsigned int nQuantity = 0; + for (const COutput& out : vOutputs) { + // unselect already spent, very unlikely scenario, this could happen + // when selected are spent elsewhere, like rpc or another computer + uint256 txhash = out.tx->GetHash(); + COutPoint outpt(txhash, out.i); + if(model->isSpent(outpt)) { + coinControl->UnSelect(outpt); + continue; + } + + // Quantity + nQuantity++; + + // Amount + nAmount += out.tx->vout[out.i].nValue; + } + MultisigDialog* multisigDialog = (MultisigDialog*)this->parentWidget(); + + multisigDialog->updateCoinControl(nAmount, nQuantity); +} + void CoinControlDialog::updateLabels(WalletModel* model, QDialog* dialog) { if (!model) @@ -700,7 +747,7 @@ void CoinControlDialog::updateView() int nDisplayUnit = model->getOptionsModel()->getDisplayUnit(); double mempoolEstimatePriority = mempool.estimatePriority(nTxConfirmTarget); - map > mapCoins; + map> mapCoins; model->listCoins(mapCoins); BOOST_FOREACH (PAIRTYPE(QString, vector) coins, mapCoins) { @@ -731,7 +778,12 @@ void CoinControlDialog::updateView() double dPrioritySum = 0; int nChildren = 0; int nInputSum = 0; - BOOST_FOREACH (const COutput& out, coins.second) { + for(const COutput& out: coins.second) { + isminetype mine = pwalletMain->IsMine(out.tx->vout[out.i]); + bool fMultiSigUTXO = (mine & ISMINE_MULTISIG); + // when multisig is enabled, it will only display outputs from multisig addresses + if (fMultisigEnabled && !fMultiSigUTXO) + continue; int nInputSize = 0; nSum += out.tx->vout[out.i].nValue; nChildren++; @@ -744,6 +796,20 @@ void CoinControlDialog::updateView() itemOutput->setFlags(flgCheckbox); itemOutput->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); + //MultiSig + if (fMultiSigUTXO) { + itemOutput->setText(COLUMN_TYPE, "MultiSig"); + + if (!fMultisigEnabled) { + COutPoint outpt(out.tx->GetHash(), out.i); + coinControl->UnSelect(outpt); // just to be sure + itemOutput->setDisabled(true); + itemOutput->setIcon(COLUMN_CHECKBOX, QIcon(":/icons/lock_closed")); + } + } else { + itemOutput->setText(COLUMN_TYPE, "Personal"); + } + // address CTxDestination outputAddress; QString sAddress = ""; diff --git a/src/qt/coincontroldialog.h b/src/qt/coincontroldialog.h index f0bdb7714a28..163aa8cf9088 100644 --- a/src/qt/coincontroldialog.h +++ b/src/qt/coincontroldialog.h @@ -18,6 +18,7 @@ class WalletModel; +class MultisigDialog; class CCoinControl; class CTxMemPool; @@ -31,10 +32,11 @@ class CoinControlDialog : public QDialog Q_OBJECT public: - explicit CoinControlDialog(QWidget* parent = 0); + explicit CoinControlDialog(QWidget* parent = nullptr, bool fMultisigEnabled = false); ~CoinControlDialog(); void setModel(WalletModel* model); + void updateDialogLabels(); // static because also called from sendcoinsdialog static void updateLabels(WalletModel*, QDialog*); @@ -49,6 +51,7 @@ class CoinControlDialog : public QDialog WalletModel* model; int sortColumn; Qt::SortOrder sortOrder; + bool fMultisigEnabled; QMenu* contextMenu; QTreeWidgetItem* contextMenuItem; @@ -65,6 +68,7 @@ class CoinControlDialog : public QDialog COLUMN_AMOUNT, COLUMN_LABEL, COLUMN_ADDRESS, + COLUMN_TYPE, COLUMN_DATE, COLUMN_CONFIRMATIONS, COLUMN_PRIORITY, diff --git a/src/qt/forms/coincontroldialog.ui b/src/qt/forms/coincontroldialog.ui index 18ccd406c48e..c3805ec8acc2 100644 --- a/src/qt/forms/coincontroldialog.ui +++ b/src/qt/forms/coincontroldialog.ui @@ -447,7 +447,7 @@ false - 12 + 13 true @@ -477,6 +477,11 @@ + Type + + + + Date diff --git a/src/qt/forms/multisigdialog.ui b/src/qt/forms/multisigdialog.ui new file mode 100644 index 000000000000..8fdf9d589962 --- /dev/null +++ b/src/qt/forms/multisigdialog.ui @@ -0,0 +1,838 @@ + + + MultisigDialog + + + + 0 + 0 + 801 + 504 + + + + + 0 + 0 + + + + Multisignature Address Interactions + + + + + + QScrollArea{border: 1px solid #5b4c7c;} +QFrame{background-color:#f2f0f0;} +QLabel{background-color:#ffffff;} +QFrame > QLabel{background-color:#f2f0f0;} + + + 0 + + + + + 0 + 0 + + + + Create MultiSignature &Address + + + + + + + + + 0 + 0 + + + + How many people must sign to verify a transaction + + + 1 + + + 16 + + + 1 + + + + + + + Enter the minimum number of signatures required to sign transactions + + + + + + + + + + + Address Label: + + + + + + + + + + + + + + Add another address that could sign to verify a transaction from the multisig address. + + + &Add Address / Key + + + + :/icons/add:/icons/add + + + false + + + + + + + + 0 + 0 + + + + Local addresses or public keys that can sign: + + + + + + + Qt::Horizontal + + + + 40 + 10 + + + + + + + + + + + 0 + 0 + + + + true + + + + + 0 + 0 + 759 + 165 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + 0 + 0 + + + + Create a new multisig address + + + -3 + + + C&reate + + + + :/icons/filesave:/icons/filesave + + + false + + + + + + + Status: + + + + + + + + 0 + 0 + + + + + 16777215 + 75 + + + + true + + + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + + 0 + 0 + + + + Use below to quickly import an address by its redeem. Don't forget to add a label before clicking import! +Keep in mind, the wallet will rescan the blockchain to find transactions containing the new address. +Please be patient after clicking import. + + + false + + + true + + + + + + + + + + 0 + 0 + + + + &Import Redeem + + + + :/icons/receiving_addresses:/icons/receiving_addresses + + + + + + + + 0 + 0 + + + + + + + + + + + &Create MultiSignature Tx + + + + + + + + + + + + + 0 + 0 + + + + Inputs: + + + + + + + + 0 + 0 + + + + Coin Control + + + + + + + 10 + + + + + Quantity Selected: + + + + + + + 0 + + + + + + + Amount: + + + + + + + 0 + + + + + + + + + + 0 + 0 + + + + Add an input to fund the outputs + + + Add a Raw Input + + + + :/css/default:/css/default + + + + + + + + + + 0 + 0 + + + + true + + + + + 0 + 0 + 98 + 28 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + Address / Amount: + + + + + + + + 0 + 0 + + + + Add destinations to send PIV to + + + Add &Destination + + + + :/icons/add:/icons/add + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + 0 + 0 + + + + true + + + + + 0 + 0 + 98 + 28 + + + + + + + + + + Qt::Vertical + + + + 20 + 20 + + + + + + + + + + + + + + + 0 + 0 + + + + + 150 + 0 + + + + Create a transaction object using the given inputs to the given outputs + + + Cr&eate + + + + :/icons/export:/icons/export + + + false + + + true + + + + + + + + 0 + 0 + + + + Status: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + true + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Noto Sans'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> + + + + + + + + + + + + + + + + + txScrollArea:{ + border: 1px solid #5b4c7c; +} +keyScrollArea:{ + border: 1px solid #5b4c7c; +} +txScrollArea:{ + border: 1px solid #5b4c7c; +} + + + &Sign MultiSignature Tx + + + + + + + + Transaction Hex: + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + + + + + 150 + 0 + + + + Sign the transaction from this wallet or from provided private keys + + + S&ign + + + + :/icons/edit:/icons/edit + + + false + + + true + + + + + + + false + + + + 0 + 0 + + + + <html><head/><body><p>DISABLED until transaction has been signed enough times.</p></body></html> + + + false + + + Co&mmit + + + + :/icons/send:/icons/send + + + true + + + + + + + + + + 0 + 0 + + + + Add private keys to sign the transaction with + + + Add Private &Key + + + + :/icons/add:/icons/add + + + + + + + Sign with only private keys (Not Recommened) + + + + + + + + + + + + + Status: + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Qt::ScrollBarAsNeeded + + + true + + + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + + 0 + 0 + + + + true + + + + + 0 + 0 + 98 + 28 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + diff --git a/src/qt/multisigdialog.cpp b/src/qt/multisigdialog.cpp new file mode 100644 index 000000000000..635ce6f23ec6 --- /dev/null +++ b/src/qt/multisigdialog.cpp @@ -0,0 +1,1024 @@ +// Copyright (c) 2017 The PIVX developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "multisigdialog.h" +#include "forms/ui_multisigdialog.h" + +#include "askpassphrasedialog.h" +#include "primitives/transaction.h" +#include "addressbookpage.h" +#include "utilstrencodings.h" +#include "core_io.h" +#include "script/script.h" +#include "base58.h" +#include "coins.h" +#include "keystore.h" +#include "init.h" +#include "wallet.h" +#include "script/sign.h" +#include "script/interpreter.h" +#include "utilmoneystr.h" +#include "guiutil.h" +#include "qvalidatedlineedit.h" +#include "bitcoinamountfield.h" + +#include +#include +#include +#include +#include +#include + + +MultisigDialog::MultisigDialog(QWidget* parent) : QDialog(parent), + ui(new Ui::MultisigDialog), + model(0) +{ + ui->setupUi(this); + multisigTx = CMutableTransaction(); + + //flag to show keyScrollArea on first priv key added + isFirstPrivKey = true; + isFirstRawTx = true; + ui->keyScrollArea->hide(); + ui->txInputsScrollArea->hide(); + + connect(ui->commitButton, SIGNAL(clicked()), this, SLOT(commitMultisigTx())); + + //populate lists with initial objects + on_addAddressButton_clicked(); + on_addAddressButton_clicked(); + on_addDestinationButton_clicked(); + + this->setStyleSheet(GUIUtil::loadStyleSheet()); +} + +MultisigDialog::~MultisigDialog() +{ + delete ui; +} + +void MultisigDialog::setModel(WalletModel *model) +{ + this->model = model; +} + +void MultisigDialog::showTab(int index) +{ + ui->multisigTabWidget->setCurrentIndex(index); + this->show(); +} + +void MultisigDialog::updateCoinControl(CAmount nAmount, unsigned int nQuantity) +{ + ui->labelAmount_int->setText(QString::fromStdString(FormatMoney(nAmount))); + ui->labelQuantity_int->setText(QString::number(nQuantity)); +} + +/** +* Private Slots +*/ +//slot for pasting addresses +void MultisigDialog::pasteText() +{ + QWidget* pasteButton = qobject_cast(sender()); + if(!pasteButton)return; + + QFrame* addressFrame = qobject_cast(pasteButton->parentWidget()); + if(!addressFrame)return; + + QValidatedLineEdit* vle = addressFrame->findChild("address"); + if(!vle)return; + + vle->setText(QApplication::clipboard()->text()); +} + +//slot for deleting QFrames with the delete buttons +void MultisigDialog::deleteFrame() +{ + QWidget *buttonWidget = qobject_cast(sender()); + if(!buttonWidget)return; + + //if deleting last raw input/priv key, hide scroll area + if(buttonWidget->objectName() == "inputDeleteButton" && ui->inputsList->count() == 1){ + isFirstRawTx = true; + ui->txInputsScrollArea->hide(); + }else if(buttonWidget->objectName() == "keyDeleteButton" && ui->keyList->count() == 1){ + isFirstPrivKey = true; + ui->keyScrollArea->hide(); + } + + QFrame* frame = qobject_cast(buttonWidget->parentWidget()); + if(!frame)return; + + delete frame; +} + +//slot to open address book dialog +void MultisigDialog::addressBookButtonReceiving() +{ + QWidget* addressButton = qobject_cast(sender()); + if(!addressButton)return; + + QFrame* addressFrame = qobject_cast(addressButton->parentWidget()); + if(!addressFrame)return; + + QValidatedLineEdit* vle = addressFrame->findChild("address"); + if(!vle)return; + + if (model && model->getAddressTableModel()) { + AddressBookPage dlg(AddressBookPage::ForSelection, AddressBookPage::ReceivingTab, this); + dlg.setModel(model->getAddressTableModel()); + if (dlg.exec()) { + vle->setText(dlg.getReturnValue()); + } + } +} + +//create address +void MultisigDialog::on_addMultisigButton_clicked() +{ + if(!model) + return; + + int m = ui->enterMSpinbox->value(); + + vector keys; + + for (int i = 0; i < ui->addressList->count(); i++) { + QWidget* address = qobject_cast(ui->addressList->itemAt(i)->widget()); + QValidatedLineEdit* vle = address->findChild("address"); + + if(!vle->text().isEmpty()){ + keys.push_back(vle->text().toStdString()); + } + } + + addMultisig(m, keys); +} + +void MultisigDialog::on_importAddressButton_clicked(){ + if(!model) + return; + + string sRedeem = ui->importRedeem->text().toStdString(); + + if(sRedeem.empty()){ + ui->addMultisigStatus->setStyleSheet("QLabel { color: red; }"); + ui->addMultisigStatus->setText("Import box empty!"); + return; + } + + vector vRedeem; + size_t pos = 0; + + //search redeem input delimited by space + while ((pos = sRedeem.find(" ")) != std::string::npos) { + vRedeem.push_back(sRedeem.substr(0, pos)); + sRedeem.erase(0, pos + 1); + } + + vector keys(vRedeem.begin()+1, vRedeem.end()-1); + + addMultisig(stoi(vRedeem[0]), keys); + + // rescan to find txs associated with imported address + pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true); + pwalletMain->ReacceptWalletTransactions(); +} + +bool MultisigDialog::addMultisig(int m, vector keys){ + try{ + string error; + CScript redeem; + + if(!createRedeemScript(m, keys, redeem, error)){ + throw runtime_error(error.data()); + } + + if (::IsMine(*pwalletMain, redeem) == ISMINE_SPENDABLE){ + throw runtime_error("The wallet already contains this script"); + } + + if(!pwalletMain->AddCScript(redeem)){ + throw runtime_error("Failure: address invalid or already exists"); + } + + CScriptID innerID(redeem); + string label = ui->multisigAddressLabel->text().toStdString(); + pwalletMain->SetAddressBook(innerID, label, "receive"); + if (!pwalletMain->AddMultiSig(redeem)){ + throw runtime_error("Failure: unable to add address as watch only"); + } + + ui->addMultisigStatus->setStyleSheet("QLabel { color: black; }"); + ui->addMultisigStatus->setText("Multisignature address " + + QString::fromStdString(CBitcoinAddress(innerID).ToString()) + + " has been added to the wallet.\nSend the redeem below for other owners to import:\n" + + QString::fromStdString(redeem.ToString())); + }catch(const runtime_error& e) { + ui->addMultisigStatus->setStyleSheet("QLabel { color: red; }"); + ui->addMultisigStatus->setText(tr(e.what())); + return false; + } + return true; +} + + +//spend +void MultisigDialog::on_createButton_clicked() +{ + if(!model) + return; + + vector vUserIn; + vector vUserOut; + try{ + //Add inputs from Coin Control if any are selected + if (CoinControlDialog::coinControl->HasSelected()) { + vector vSelected; + CoinControlDialog::coinControl->ListSelected(vSelected); + for (auto outpoint : vSelected) + vUserIn.emplace_back(CTxIn(outpoint)); + }else{//check for raw inputs + for(int i = 0; i < ui->inputsList->count(); i++){ + QWidget* input = qobject_cast(ui->inputsList->itemAt(i)->widget()); + QLineEdit* txIdLine = input->findChild("txInputId"); + if(txIdLine->text().isEmpty()){ + ui->createButtonStatus->setStyleSheet("QLabel { color: red; }"); + ui->createButtonStatus->setText(tr("Invalid Tx Hash.")); + return; + } + + QSpinBox* txVoutLine = input->findChild("txInputVout"); + int nOutput = txVoutLine->value(); + if(nOutput < 0){ + ui->createButtonStatus->setStyleSheet("QLabel { color: red; }"); + ui->createButtonStatus->setText(tr("Vout position must be positive.")); + return; + } + + uint256 txid = uint256S(txIdLine->text().toStdString()); + CTxIn in(COutPoint(txid, nOutput)); + vUserIn.emplace_back(in); + } + } + + //validate destinations + bool validInput = true; + for(int i = 0; i < ui->destinationsList->count(); i++){ + QWidget* dest = qobject_cast(ui->destinationsList->itemAt(i)->widget()); + QValidatedLineEdit* addr = dest->findChild("destinationAddress"); + BitcoinAmountField* amt = dest->findChild("destinationAmount"); + CBitcoinAddress address; + + bool validDest = true; + + if(!model->validateAddress(addr->text())){ + addr->setValid(false); + validDest = false; + }else{ + address = CBitcoinAddress(addr->text().toStdString()); + } + + if(!amt->validate()){ + amt->setValid(false); + validDest = false; + } + + if(!validDest){ + validInput = false; + continue; + } + + CScript scriptPubKey = GetScriptForDestination(address.Get()); + CTxOut out(amt->value(), scriptPubKey); + vUserOut.push_back(out); + } + + + //if all user data valid create a multisig tx + if(validInput){ + //clear member variable + multisigTx = CMutableTransaction(); + + string error; + string fee; + if(!createMultisigTransaction(vUserIn, vUserOut, fee, error)){ + throw runtime_error(error); + } + + //display status string + ui->createButtonStatus->setStyleSheet("QTextEdit{ color: black }"); + + QString status(strprintf("Transaction has successfully created with a fee of %s.\n" + "The transaction has been automatically imported to the sign tab.\n" + "Please continue on to sign the tx from this wallet, to access the hex to send to other owners.", fee).c_str()); + + ui->createButtonStatus->setText(status); + ui->transactionHex->setText(QString::fromStdString(EncodeHexTx(multisigTx))); + + } + }catch(const runtime_error& e){ + ui->createButtonStatus->setStyleSheet("QTextEdit{ color: red }"); + ui->createButtonStatus->setText(tr(e.what())); + } +} + +bool MultisigDialog::createMultisigTransaction(vector vUserIn, vector vUserOut, string& feeStringRet, string& errorRet) +{ + try{ + //attempt to access the given inputs + CCoinsViewCache view = getInputsCoinsViewCache(vUserIn); + + //retrieve total input val and change dest + CAmount totalIn = 0; + vector vInputVals; + CScript changePubKey; + bool fFirst = true; + + for(CTxIn in : vUserIn){ + const CCoins* coins = view.AccessCoins(in.prevout.hash); + if(!coins->IsAvailable(in.prevout.n) || coins == NULL){ + continue; + } + CTxOut prevout = coins->vout[in.prevout.n]; + CScript privKey = prevout.scriptPubKey; + + vInputVals.push_back(prevout.nValue); + totalIn += prevout.nValue; + + if(!fFirst){ + if(privKey != changePubKey){ + throw runtime_error("Address mismatch! Inputs must originate from the same multisignature address."); + } + }else{ + fFirst = false; + changePubKey = privKey; + } + } + + CAmount totalOut = 0; + + //retrieve total output val + for(CTxOut out : vUserOut){ + totalOut += out.nValue; + } + + if(totalIn < totalOut){ + throw runtime_error("Not enough PIV provided as input to complete transaction (including fee)."); + } + + //calculate change amount + CAmount changeAmount = totalIn - totalOut; + CTxOut change(changeAmount, changePubKey); + + //generate random position for change + unsigned int changeIndex = rand() % (vUserOut.size() + 1); + + //insert change into random position + if(changeIndex < vUserOut.size()){ + vUserOut.insert(vUserOut.begin() + changeIndex, change); + }else{ + vUserOut.emplace_back(change); + } + + //populate tx + CMutableTransaction tx; + tx.vin = vUserIn; + tx.vout = vUserOut; + + const CCoins* coins = view.AccessCoins(tx.vin[0].prevout.hash); + + if(coins == NULL || !coins->IsAvailable(tx.vin[0].prevout.n)){ + throw runtime_error("Coins unavailable (unconfirmed/spent)"); + } + + CScript prevPubKey = coins->vout[tx.vin[0].prevout.n].scriptPubKey; + + //get payment destination + CTxDestination address; + if(!ExtractDestination(prevPubKey, address)){ + throw runtime_error("Could not find address for destination."); + } + + CScriptID hash = boost::get(address); + CScript redeemScript; + + if (!pwalletMain->GetCScript(hash, redeemScript)){ + throw runtime_error("could not redeem"); + } + txnouttype type; + vector addresses; + int nReq; + if(!ExtractDestinations(redeemScript, type, addresses, nReq)){ + throw runtime_error("Could not extract destinations from redeem script."); + } + + for(CTxIn& in : tx.vin){ + in.scriptSig.clear(); + //scale estimate to account for multisig scriptSig + for(unsigned int i = 0; i < 50*(nReq+addresses.size()); i++){ + in.scriptSig << INT64_MAX; + } + } + + //calculate fee + unsigned int nBytes = tx.GetSerializeSize(SER_NETWORK, PROTOCOL_VERSION); + CAmount fee = ::minRelayTxFee.GetFee(nBytes); + + if(tx.vout.at(changeIndex).nValue > fee){ + tx.vout.at(changeIndex).nValue -= fee; + feeStringRet = strprintf("%d",((double)fee)/COIN).c_str(); + }else{ + throw runtime_error("Not enough PIV provided to cover fee"); + } + + //clear junk from script sigs + for(CTxIn& in : tx.vin){ + in.scriptSig.clear(); + } + multisigTx = tx; + }catch(const runtime_error& e){ + errorRet = e.what(); + return false; + } + return true; +} + +//sign +void MultisigDialog::on_signButton_clicked() +{ + if(!model) + return; + try{ + //parse tx hex + CTransaction txRead; + if(!DecodeHexTx(txRead, ui->transactionHex->text().toStdString())){ + throw runtime_error("Failed to decode transaction hex!"); + } + + CMutableTransaction tx(txRead); + + //check if transaction is already fully verified + if(isFullyVerified(tx)){ + this->multisigTx = tx; + ui->commitButton->setEnabled(true); + ui->signButtonStatus->setText("This transaction is ready to commit. \nThe commit button in now enabled."); + return; + } + + string errorOut = string(); + bool fComplete = signMultisigTx(tx, errorOut, ui->keyList); + + if(!errorOut.empty()){ + throw runtime_error(errorOut.data()); + }else{ + this->multisigTx = tx; + } + + ui->signButtonStatus->setStyleSheet("QTextEdit{ color: black }"); + ui->signButtonStatus->setText(buildMultisigTxStatusString(fComplete, tx)); + + }catch(const runtime_error& e){ + ui->signButtonStatus->setStyleSheet("QTextEdit{ color: red }"); + ui->signButtonStatus->setText(tr(e.what())); + } +} + +/*** + *private helper functions + */ +QString MultisigDialog::buildMultisigTxStatusString(bool fComplete, const CMutableTransaction& tx) +{ + string sTxHex = EncodeHexTx(tx); + + if(fComplete){ + ui->commitButton->setEnabled(true); + string sTxId = tx.GetHash().GetHex(); + string sTxComplete = "Complete: true!\n" + "The commit button has now been enabled for you to finalize the transaction.\n" + "Once the commit button is clicked, the transaction will be published and coins transferred " + "to their destinations.\nWARNING: THE ACTIONS OF THE COMMIT BUTTON ARE FINAL AND CANNOT BE REVERSED."; + + return QString(strprintf("%s\nTx Id:\n%s\nTx Hex:\n%s",sTxComplete, sTxId, sTxHex).c_str()); + } else { + string sTxIncomplete = "Complete: false.\n" + "You may now send the hex below to another owner to sign.\n" + "Keep in mind the transaction must be passed from one owner to the next for signing.\n" + "Ensure all owners have imported the redeem before trying to sign. (besides creator)"; + + return QString(strprintf("%s\nTx Hex: %s", sTxIncomplete, sTxHex).c_str()); + } +} + + +CCoinsViewCache MultisigDialog::getInputsCoinsViewCache(const vector& vin) +{ + CCoinsView viewDummy; + CCoinsViewCache view(&viewDummy); + { + LOCK(mempool.cs); + CCoinsViewCache& viewChain = *pcoinsTip; + CCoinsViewMemPool viewMempool(&viewChain, mempool); + view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view + + for(const CTxIn& txin : vin) { + const uint256& prevHash = txin.prevout.hash; + view.AccessCoins(prevHash); // this is certainly allowed to fail + } + + view.SetBackend(viewDummy); // switch back to avoid locking mempool for too long + } + + return view; +} + + +bool MultisigDialog::signMultisigTx(CMutableTransaction& tx, string& errorOut, QVBoxLayout* keyList) +{ + //will be set false if all inputs are not fully signed(valid) + bool fComplete = true; + + //if keyslist is not default value AND has items in list then true + bool fGivenKeys = (keyList != nullptr) && (keyList->count() > 0); + + try{ + + //copy of vin for reference before vin is mutated + vector oldVin(tx.vin); + CBasicKeyStore privKeystore; + + //if keys were given, attempt to collect redeem and scriptpubkey + if(fGivenKeys){ + for(int i = 0; i < keyList->count(); i++){ + QWidget* keyFrame = qobject_cast(keyList->itemAt(i)->widget()); + QLineEdit* key = keyFrame->findChild("key"); + CBitcoinSecret vchSecret; + if (!vchSecret.SetString(key->text().toStdString())) + throw runtime_error("Invalid private key"); + CKey cKey = vchSecret.GetKey(); + if (!cKey.IsValid()) + throw runtime_error("Private key outside allowed range"); + privKeystore.AddKey(cKey); + } + + for(CTxIn& txin : tx.vin){ + //get inputs + CTransaction txVin; + uint256 hashBlock; + if (!GetTransaction(txin.prevout.hash, txVin, hashBlock, true)) + throw runtime_error("txin could not be found"); + + if (hashBlock == 0) + throw runtime_error("txin is unconfirmed"); + + //get pubkey from input + CScript prevPubKey = txVin.vout[txin.prevout.n].scriptPubKey; + + //get payment destination + CTxDestination address; + if(!ExtractDestination(prevPubKey, address)){ + throw runtime_error("Could not find address for destination."); + } + + //get redeem script related to destination + CScriptID hash = boost::get(address); + CScript redeemScript; + + if (!pwalletMain->GetCScript(hash, redeemScript)){ + errorOut = "could not redeem"; + } + privKeystore.AddCScript(redeemScript); + } + }else{ + if (model->getEncryptionStatus() == model->Locked) { + if (!model->requestUnlock(true).isValid()) { + // Unlock wallet was cancelled + throw runtime_error("Error: Your wallet is locked. Please enter the wallet passphrase first."); + } + } + } + + //choose between local wallet and provided + const CKeyStore& keystore = fGivenKeys ? privKeystore : *pwalletMain; + + //attempt to sign each input from local wallet + int nIn = 0; + for(CTxIn& txin : tx.vin){ + //get inputs + CTransaction txVin; + uint256 hashBlock; + if (!GetTransaction(txin.prevout.hash, txVin, hashBlock, true)) + throw runtime_error("txin could not be found"); + + if (hashBlock == 0) + throw runtime_error("txin is unconfirmed"); + + txin.scriptSig.clear(); + CScript prevPubKey = txVin.vout[txin.prevout.n].scriptPubKey; + + //sign what we can + SignSignature(keystore, prevPubKey, tx, nIn); + + //merge in any previous signatures + txin.scriptSig = CombineSignatures(prevPubKey, tx, nIn, txin.scriptSig, oldVin[nIn].scriptSig); + + if (!VerifyScript(txin.scriptSig, prevPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, MutableTransactionSignatureChecker(&tx, nIn))){ + fComplete = false; + } + nIn++; + } + + ui->signButtonStatus->setText(buildMultisigTxStatusString(fComplete, tx)); + + }catch(const runtime_error& e){ + errorOut = string(e.what()); + fComplete = false; + } + return fComplete; +} + +// quick check for an already fully signed tx +bool MultisigDialog::isFullyVerified(CMutableTransaction& tx){ + try{ + int nIn = 0; + for(CTxIn& txin : tx.vin){ + CTransaction txVin; + uint256 hashBlock; + if (!GetTransaction(txin.prevout.hash, txVin, hashBlock, true)){ + throw runtime_error("txin could not be found"); + } + if (hashBlock == 0){ + throw runtime_error("txin is unconfirmed"); + } + + //get pubkey from this input as output in last tx + CScript prevPubKey = txVin.vout[txin.prevout.n].scriptPubKey; + + if (!VerifyScript(txin.scriptSig, prevPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, MutableTransactionSignatureChecker(&tx, nIn))){ + return false; + } + + nIn++; + } + }catch(const runtime_error& e){ + return false; + } + + return true; +} + +void MultisigDialog::commitMultisigTx() +{ + CMutableTransaction tx(multisigTx); + try{ +#ifdef ENABLE_WALLET + CWalletTx wtx(pwalletMain, tx); + CReserveKey keyChange(pwalletMain); + if (!pwalletMain->CommitTransaction(wtx, keyChange)) + throw runtime_error(string("Transaction rejected - Failed to commit")); +#else + uint256 hashTx = tx.GetHash(); + CCoinsViewCache& view = *pcoinsTip; + const CCoins* existingCoins = view.AccessCoins(hashTx); + bool fOverrideFees = false; + bool fHaveMempool = mempool.exists(hashTx); + bool fHaveChain = existingCoins && existingCoins->nHeight < 1000000000; + + if (!fHaveMempool && !fHaveChain) { + // push to local node and sync with wallets + CValidationState state; + if (!AcceptToMemoryPool(mempool, state, tx, false, NULL, !fOverrideFees)) { + if (state.IsInvalid()) + throw runtime_error(strprintf("Transaction rejected - %i: %s", state.GetRejectCode(), state.GetRejectReason())); + else + throw runtime_error(string("Transaction rejected - ") + state.GetRejectReason()); + } + } else if (fHaveChain) { + throw runtime_error("transaction already in block chain"); + } + RelayTransaction(tx); +#endif + //disable commit if successfully committed + ui->commitButton->setEnabled(false); + ui->signButtonStatus->setText(strprintf("Transaction has been successfully published with transaction ID:\n %s", tx.GetHash().GetHex()).c_str()); + }catch(const runtime_error& e){ + ui->signButtonStatus->setText(e.what()); + } +} + +bool MultisigDialog::createRedeemScript(int m, vector vKeys, CScript& redeemRet, string& errorRet) +{ + try{ + int n = vKeys.size(); + //gather pub keys + if (n < 1) + throw runtime_error("a Multisignature address must require at least one key to redeem"); + if (n < m) + throw runtime_error( + strprintf("not enough keys supplied " + "(got %d keys, but need at least %d to redeem)", + m, n)); + if (n > 15) + throw runtime_error("Number of addresses involved in the Multisignature address creation > 15\nReduce the number"); + + vector pubkeys; + pubkeys.resize(n); + + int i = 0; + for(vector::iterator it = vKeys.begin(); it != vKeys.end(); ++it) { + string keyString = *it; + #ifdef ENABLE_WALLET + // Case 1: PIVX address and we have full public key: + CBitcoinAddress address(keyString); + if (pwalletMain && address.IsValid()) { + CKeyID keyID; + if (!address.GetKeyID(keyID)) { + throw runtime_error( + strprintf("%s does not refer to a key", keyString)); + } + CPubKey vchPubKey; + if (!pwalletMain->GetPubKey(keyID, vchPubKey)) + throw runtime_error( + strprintf("no full public key for address %s", keyString)); + if (!vchPubKey.IsFullyValid()){ + string sKey = keyString.empty()?"(empty)":keyString; + throw runtime_error(" Invalid public key: " + sKey ); + } + pubkeys[i++] = vchPubKey; + } + + //case 2: hex pub key + else + #endif + if (IsHex(keyString)) { + CPubKey vchPubKey(ParseHex(keyString)); + if (!vchPubKey.IsFullyValid()){ + throw runtime_error(" Invalid public key: " + keyString); + } + pubkeys[i++] = vchPubKey; + } else { + throw runtime_error(" Invalid public key: " + keyString); + } + } + //populate redeem script + //OP_N for required signatures + redeemRet << redeemRet.EncodeOP_N(m); + //public keys + for(CPubKey& key : pubkeys){ + vector vKey= ToByteVector(key); + redeemRet << vKey; + } + //OP_N for total pubkeys + redeemRet << redeemRet.EncodeOP_N(pubkeys.size()); + redeemRet << OP_CHECKMULTISIG; + return true; + }catch(const runtime_error& e){ + errorRet = string(e.what()); + return false; + } +} + +/*** + * Begin QFrame object creation methods + */ +//creates an address object on the create tab +void MultisigDialog::on_addAddressButton_clicked() +{ + //max addresses 15 + if(ui->addressList->count() > 14){ + ui->addMultisigStatus->setStyleSheet("QLabel { color: red; }"); + ui->addMultisigStatus->setText(tr("Maximum possible addresses reached. (16)")); + return; + } + + QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + sizePolicy.setHorizontalStretch(0); + sizePolicy.setVerticalStretch(0); + QFrame* addressFrame = new QFrame(); + sizePolicy.setHeightForWidth(addressFrame->sizePolicy().hasHeightForWidth()); + addressFrame->setSizePolicy(sizePolicy); + addressFrame->setFrameShape(QFrame::StyledPanel); + addressFrame->setFrameShadow(QFrame::Raised); + addressFrame->setObjectName(QStringLiteral("addressFrame")); + + QVBoxLayout* frameLayout = new QVBoxLayout(addressFrame); + frameLayout->setSpacing(1); + frameLayout->setObjectName(QStringLiteral("frameLayout")); + frameLayout->setContentsMargins(6, 6, 6, 6); + + QHBoxLayout* addressLayout = new QHBoxLayout(); + addressLayout->setSpacing(0); + addressLayout->setObjectName(QStringLiteral("addressLayout")); + + QLabel* addressLabel = new QLabel(addressFrame); + addressLabel->setObjectName(QStringLiteral("addressLabel")); + addressLabel->setText(QApplication::translate("MultisigDialog", strprintf("Address / Key %i:", ui->addressList->count()+1).c_str() , 0)); + addressLayout->addWidget(addressLabel); + + QValidatedLineEdit* address = new QValidatedLineEdit(addressFrame); + address->setObjectName(QStringLiteral("address")); + addressLayout->addWidget(address); + + QPushButton* addressBookButton = new QPushButton(addressFrame); + addressBookButton->setObjectName(QStringLiteral("addressBookButton")); + QIcon icon3; + icon3.addFile(QStringLiteral(":/icons/address-book"), QSize(), QIcon::Normal, QIcon::Off); + addressBookButton->setIcon(icon3); + addressBookButton->setAutoDefault(false); + connect(addressBookButton, SIGNAL(clicked()), this, SLOT(addressBookButtonReceiving())); + + addressLayout->addWidget(addressBookButton); + + QPushButton* addressPasteButton = new QPushButton(addressFrame); + addressPasteButton->setObjectName(QStringLiteral("addressPasteButton")); + QIcon icon4; + icon4.addFile(QStringLiteral(":/icons/editpaste"), QSize(), QIcon::Normal, QIcon::Off); + addressPasteButton->setIcon(icon4); + addressPasteButton->setAutoDefault(false); + connect(addressPasteButton, SIGNAL(clicked()), this, SLOT(pasteText())); + + addressLayout->addWidget(addressPasteButton); + + QPushButton* addressDeleteButton = new QPushButton(addressFrame); + addressDeleteButton->setObjectName(QStringLiteral("addressDeleteButton")); + QIcon icon5; + icon5.addFile(QStringLiteral(":/icons/remove"), QSize(), QIcon::Normal, QIcon::Off); + addressDeleteButton->setIcon(icon5); + addressDeleteButton->setAutoDefault(false); + connect(addressDeleteButton, SIGNAL(clicked()), this, SLOT(deleteFrame())); + + addressLayout->addWidget(addressDeleteButton); + frameLayout->addLayout(addressLayout); + + ui->addressList->addWidget(addressFrame); +} + +void MultisigDialog::on_pushButtonCoinControl_clicked() +{ + CoinControlDialog coinControlDialog(this, true); + coinControlDialog.setModel(model); + coinControlDialog.exec(); +} + +void MultisigDialog::on_addInputButton_clicked() +{ + if(isFirstRawTx){ + isFirstRawTx = false; + ui->txInputsScrollArea->show(); + } + QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + sizePolicy.setHorizontalStretch(0); + sizePolicy.setVerticalStretch(0); + + QFrame* txInputFrame = new QFrame(ui->txInputsWidget); + sizePolicy.setHeightForWidth(txInputFrame->sizePolicy().hasHeightForWidth()); + txInputFrame->setFrameShape(QFrame::StyledPanel); + txInputFrame->setFrameShadow(QFrame::Raised); + txInputFrame->setObjectName(QStringLiteral("txInputFrame")); + + QVBoxLayout* frameLayout = new QVBoxLayout(txInputFrame); + frameLayout->setSpacing(1); + frameLayout->setObjectName(QStringLiteral("txInputFrameLayout")); + frameLayout->setContentsMargins(6, 6, 6, 6); + + QHBoxLayout* txInputLayout = new QHBoxLayout(); + txInputLayout->setObjectName(QStringLiteral("txInputLayout")); + + QLabel* txInputIdLabel = new QLabel(txInputFrame); + txInputIdLabel->setObjectName(QStringLiteral("txInputIdLabel")); + txInputIdLabel->setText(QApplication::translate("MultisigDialog", strprintf("%i. Tx Hash: ", ui->inputsList->count()+1).c_str(), 0)); + txInputLayout->addWidget(txInputIdLabel); + + QLineEdit* txInputId = new QLineEdit(txInputFrame); + txInputId->setObjectName(QStringLiteral("txInputId")); + + txInputLayout->addWidget(txInputId); + + QSpacerItem* horizontalSpacer = new QSpacerItem(10, 20, QSizePolicy::Fixed, QSizePolicy::Minimum); + txInputLayout->addItem(horizontalSpacer); + + QLabel* txInputVoutLabel = new QLabel(txInputFrame); + txInputVoutLabel->setObjectName(QStringLiteral("txInputVoutLabel")); + txInputVoutLabel->setText(QApplication::translate("MultisigDialog", "Vout Position: ", 0)); + + txInputLayout->addWidget(txInputVoutLabel); + + QSpinBox* txInputVout = new QSpinBox(txInputFrame); + txInputVout->setObjectName("txInputVout"); + sizePolicy.setHeightForWidth(txInputVout->sizePolicy().hasHeightForWidth()); + txInputVout->setSizePolicy(sizePolicy); + txInputLayout->addWidget(txInputVout); + + QPushButton* inputDeleteButton = new QPushButton(txInputFrame); + inputDeleteButton->setObjectName(QStringLiteral("inputDeleteButton")); + QIcon icon; + icon.addFile(QStringLiteral(":/icons/remove"), QSize(), QIcon::Normal, QIcon::Off); + inputDeleteButton->setIcon(icon); + inputDeleteButton->setAutoDefault(false); + connect(inputDeleteButton, SIGNAL(clicked()), this, SLOT(deleteFrame())); + txInputLayout->addWidget(inputDeleteButton); + + frameLayout->addLayout(txInputLayout); + + ui->inputsList->addWidget(txInputFrame); +} + +void MultisigDialog::on_addDestinationButton_clicked() +{ + QFrame* destinationFrame = new QFrame(ui->destinationsScrollAreaContents); + destinationFrame->setObjectName(QStringLiteral("destinationFrame")); + destinationFrame->setFrameShape(QFrame::StyledPanel); + destinationFrame->setFrameShadow(QFrame::Raised); + + QVBoxLayout* frameLayout = new QVBoxLayout(destinationFrame); + frameLayout->setObjectName(QStringLiteral("destinationFrameLayout")); + QHBoxLayout* destinationLayout = new QHBoxLayout(); + destinationLayout->setSpacing(0); + destinationLayout->setObjectName(QStringLiteral("destinationLayout")); + QLabel* destinationAddressLabel = new QLabel(destinationFrame); + destinationAddressLabel->setObjectName(QStringLiteral("destinationAddressLabel")); + + destinationLayout->addWidget(destinationAddressLabel); + + QValidatedLineEdit* destinationAddress = new QValidatedLineEdit(destinationFrame); + destinationAddress->setObjectName(QStringLiteral("destinationAddress")); + + destinationLayout->addWidget(destinationAddress); + + QSpacerItem* horizontalSpacer = new QSpacerItem(10, 20, QSizePolicy::Fixed, QSizePolicy::Minimum); + destinationLayout->addItem(horizontalSpacer); + + QLabel* destinationAmountLabel = new QLabel(destinationFrame); + destinationAmountLabel->setObjectName(QStringLiteral("destinationAmountLabel")); + + destinationLayout->addWidget(destinationAmountLabel); + + BitcoinAmountField* destinationAmount = new BitcoinAmountField(destinationFrame); + destinationAmount->setObjectName(QStringLiteral("destinationAmount")); + + destinationAddressLabel->setText(QApplication::translate("MultisigDialog", strprintf("%i. Address: ", ui->destinationsList->count()+1).c_str(), 0)); + destinationAmountLabel->setText(QApplication::translate("MultisigDialog", "Amount: ", 0)); + + destinationLayout->addWidget(destinationAmount); + + QPushButton* destinationDeleteButton = new QPushButton(destinationFrame); + destinationDeleteButton->setObjectName(QStringLiteral("destinationDeleteButton")); + QIcon icon; + icon.addFile(QStringLiteral(":/icons/remove"), QSize(), QIcon::Normal, QIcon::Off); + destinationDeleteButton->setIcon(icon); + destinationDeleteButton->setAutoDefault(false); + connect(destinationDeleteButton, SIGNAL(clicked()), this, SLOT(deleteFrame())); + destinationLayout->addWidget(destinationDeleteButton); + + frameLayout->addLayout(destinationLayout); + + ui->destinationsList->addWidget(destinationFrame); +} + +void MultisigDialog::on_addPrivKeyButton_clicked() +{ + if(isFirstPrivKey){//on first click the scroll area must show + isFirstPrivKey = false; + ui->keyScrollArea->show(); + } + + if(ui->keyList->count() > 14){ + ui->signButtonStatus->setStyleSheet("QTextEdit{ color: red }"); + ui->signButtonStatus->setText(tr("Maximum (15)")); + return; + } + + QFrame* keyFrame = new QFrame(ui->keyScrollAreaContents); + + keyFrame->setObjectName(QStringLiteral("keyFrame")); + keyFrame->setFrameShape(QFrame::StyledPanel); + keyFrame->setFrameShadow(QFrame::Raised); + + QHBoxLayout* keyLayout = new QHBoxLayout(keyFrame); + keyLayout->setObjectName(QStringLiteral("keyLayout")); + + QLabel* keyLabel = new QLabel(keyFrame); + keyLabel->setObjectName(QStringLiteral("keyLabel")); + keyLabel->setText(QApplication::translate("MultisigDialog", strprintf("Key %i: ", (ui->keyList->count()+1)).c_str(), 0)); + keyLayout->addWidget(keyLabel); + + QLineEdit* key = new QLineEdit(keyFrame); + key->setObjectName(QStringLiteral("key")); + key->setEchoMode(QLineEdit::Password); + keyLayout->addWidget(key); + + QPushButton* keyDeleteButton = new QPushButton(keyFrame); + keyDeleteButton->setObjectName(QStringLiteral("keyDeleteButton")); + QIcon icon; + icon.addFile(QStringLiteral(":/icons/remove"), QSize(), QIcon::Normal, QIcon::Off); + keyDeleteButton->setIcon(icon); + keyDeleteButton->setAutoDefault(false); + connect(keyDeleteButton, SIGNAL(clicked()), this, SLOT(deleteFrame())); + keyLayout->addWidget(keyDeleteButton); + + ui->keyList->addWidget(keyFrame); +} + diff --git a/src/qt/multisigdialog.h b/src/qt/multisigdialog.h new file mode 100644 index 000000000000..152f2c2752b9 --- /dev/null +++ b/src/qt/multisigdialog.h @@ -0,0 +1,71 @@ +// Copyright (c) 2017 The PIVX developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_QT_MULTISIGDIALOG_H +#define BITCOIN_QT_MULTISIGDIALOG_H + +#include +#include +#include +#include +#include "script/script.h" +#include "primitives/transaction.h" +#include "coins.h" +#include "coincontrol.h" +#include "walletmodel.h" +#include "coincontroldialog.h" + +namespace Ui +{ +class MultisigDialog; +} + +class MultisigDialog : public QDialog +{ + Q_OBJECT + +public: + explicit MultisigDialog(QWidget* parent); + ~MultisigDialog(); + void setModel(WalletModel* model); + void updateCoinControl(CAmount nAmount, unsigned int nQuantity); + +public slots: + void showTab(int index); + +private: + Ui::MultisigDialog* ui; + WalletModel* model; + CCoinControl* coinControl; + bool isFirstPrivKey; + bool isFirstRawTx; + CMutableTransaction multisigTx; + + QFrame* createAddress(int labelNumber); + QFrame* createInput(int labelNumber); + CCoinsViewCache getInputsCoinsViewCache(const std::vector& vin); + QString buildMultisigTxStatusString(bool fComplete, const CMutableTransaction& tx); + bool createRedeemScript(int m, std::vector keys, CScript& redeemRet, std::string& errorRet); + bool createMultisigTransaction(std::vector vUserIn, std::vector vUserOut, string& feeStringRet, string& errorRet); + bool signMultisigTx(CMutableTransaction& txToSign, std::string& errorMessageRet, QVBoxLayout* keyList = nullptr); + bool addMultisig(int m, std::vector keys); + bool isFullyVerified(CMutableTransaction& txToVerify); + +private slots: + void deleteFrame(); + void pasteText(); + void commitMultisigTx(); + void addressBookButtonReceiving(); + void on_addAddressButton_clicked(); + void on_addMultisigButton_clicked(); + void on_addDestinationButton_clicked(); + void on_createButton_clicked(); + void on_addInputButton_clicked(); + void on_addPrivKeyButton_clicked(); + void on_signButton_clicked(); + void on_pushButtonCoinControl_clicked(); + void on_importAddressButton_clicked(); +}; + +#endif // BITCOIN_QT_MULTISIGDIALOG_H diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp index d392d0816fd9..b40860655c33 100644 --- a/src/qt/walletframe.cpp +++ b/src/qt/walletframe.cpp @@ -182,6 +182,13 @@ void WalletFrame::gotoMultiSendDialog() walletView->gotoMultiSendDialog(); } +void WalletFrame::gotoMultisigDialog(int index) +{ + WalletView* walletView = currentWalletView(); + if(walletView){ + walletView->gotoMultisigDialog(index); + } +} void WalletFrame::encryptWallet(bool status) { diff --git a/src/qt/walletframe.h b/src/qt/walletframe.h index 174f4cf20bb9..b5bfe6c54cd6 100644 --- a/src/qt/walletframe.h +++ b/src/qt/walletframe.h @@ -70,7 +70,8 @@ public slots: void gotoVerifyMessageTab(QString addr = ""); /** Show MultiSend Dialog **/ void gotoMultiSendDialog(); - + /** show a multisig tab **/ + void gotoMultisigDialog(int index); /** Show BIP 38 tool - default to Encryption tab */ void gotoBip38Tool(); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index f6dacd38748c..bafe97ba9ab1 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -37,6 +37,7 @@ WalletModel::WalletModel(CWallet* wallet, OptionsModel* optionsModel, QObject* p cachedNumBlocks(0) { fHaveWatchOnly = wallet->HaveWatchOnly(); + fHaveMultiSig = wallet->HaveMultiSig(); fForceCheckBalanceChanged = false; addressTableModel = new AddressTableModel(wallet, this); @@ -229,6 +230,12 @@ void WalletModel::updateWatchOnlyFlag(bool fHaveWatchonly) emit notifyWatchonlyChanged(fHaveWatchonly); } +void WalletModel::updateMultiSigFlag(bool fHaveMultiSig) +{ + this->fHaveMultiSig = fHaveMultiSig; + emit notifyMultiSigChanged(fHaveMultiSig); +} + bool WalletModel::validateAddress(const QString& address) { CBitcoinAddress addressParsed(address.toStdString()); @@ -537,6 +544,12 @@ static void NotifyWatchonlyChanged(WalletModel* walletmodel, bool fHaveWatchonly Q_ARG(bool, fHaveWatchonly)); } +static void NotifyMultiSigChanged(WalletModel* walletmodel, bool fHaveMultiSig) +{ + QMetaObject::invokeMethod(walletmodel, "updateMultiSigFlag", Qt::QueuedConnection, + Q_ARG(bool, fHaveMultiSig)); +} + static void NotifyZerocoinChanged(WalletModel* walletmodel, CWallet* wallet, const std::string& hexString, const std::string& isUsed, ChangeType status) { @@ -557,6 +570,7 @@ void WalletModel::subscribeToCoreSignals() wallet->NotifyTransactionChanged.connect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3)); wallet->ShowProgress.connect(boost::bind(ShowProgress, this, _1, _2)); wallet->NotifyWatchonlyChanged.connect(boost::bind(NotifyWatchonlyChanged, this, _1)); + wallet->NotifyMultiSigChanged.connect(boost::bind(NotifyMultiSigChanged, this, _1)); wallet->NotifyZerocoinChanged.connect(boost::bind(NotifyZerocoinChanged, this, _1, _2, _3, _4)); } @@ -568,6 +582,7 @@ void WalletModel::unsubscribeFromCoreSignals() wallet->NotifyTransactionChanged.disconnect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3)); wallet->ShowProgress.disconnect(boost::bind(ShowProgress, this, _1, _2)); wallet->NotifyWatchonlyChanged.disconnect(boost::bind(NotifyWatchonlyChanged, this, _1)); + wallet->NotifyMultiSigChanged.disconnect(boost::bind(NotifyMultiSigChanged, this, _1)); wallet->NotifyZerocoinChanged.disconnect(boost::bind(NotifyZerocoinChanged, this, _1, _2, _3, _4)); } diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 4b55654f3b57..00f67d0ffb24 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -218,6 +218,7 @@ class WalletModel : public QObject private: CWallet* wallet; bool fHaveWatchOnly; + bool fHaveMultiSig; bool fForceCheckBalanceChanged; // Wallet has an options model for wallet-specific options @@ -275,6 +276,8 @@ class WalletModel : public QObject // Watch-only address added void notifyWatchonlyChanged(bool fHaveWatchonly); + // MultiSig address added + void notifyMultiSigChanged(bool fHaveMultiSig); public slots: /* Wallet status might have changed */ void updateStatus(); @@ -286,6 +289,8 @@ public slots: void updateAddressBook(const QString &pubCoin, const QString &isUsed, int status); /* Watch-only added */ void updateWatchOnlyFlag(bool fHaveWatchonly); + /* MultiSig added */ + void updateMultiSigFlag(bool fHaveMultiSig); /* Current, immature or unconfirmed balance might have changed - emit 'balanceChanged' if so */ void pollBalanceChanged(); }; diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index b6a34a436d91..0834b02cb1c4 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -13,6 +13,7 @@ #include "guiutil.h" #include "masternodeconfig.h" #include "multisenddialog.h" +#include "multisigdialog.h" #include "optionsmodel.h" #include "overviewpage.h" #include "receivecoinsdialog.h" @@ -277,6 +278,13 @@ void WalletView::gotoMultiSendDialog() multiSendDialog->show(); } +void WalletView::gotoMultisigDialog(int index) +{ + MultisigDialog* multisig = new MultisigDialog(this); + multisig->setModel(walletModel); + multisig->showTab(index); +} + bool WalletView::handlePaymentRequest(const SendCoinsRecipient& recipient) { return sendCoinsPage->handlePaymentRequest(recipient); diff --git a/src/qt/walletview.h b/src/qt/walletview.h index f0548882471b..1c18747efabf 100644 --- a/src/qt/walletview.h +++ b/src/qt/walletview.h @@ -95,7 +95,8 @@ public slots: void gotoVerifyMessageTab(QString addr = ""); /** Show MultiSend Dialog */ void gotoMultiSendDialog(); - + /** Show a multisig tab **/ + void gotoMultisigDialog(int index); /** Show BIP 38 tool - default to Encryption tab */ void gotoBip38Tool(); diff --git a/src/wallet.cpp b/src/wallet.cpp index 570fb5657310..791876795309 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -215,6 +215,36 @@ bool CWallet::LoadWatchOnly(const CScript& dest) return CCryptoKeyStore::AddWatchOnly(dest); } +bool CWallet::AddMultiSig(const CScript& dest) +{ + if (!CCryptoKeyStore::AddMultiSig(dest)) + return false; + nTimeFirstKey = 1; // No birthday information + NotifyMultiSigChanged(true); + if (!fFileBacked) + return true; + return CWalletDB(strWalletFile).WriteMultiSig(dest); +} + +bool CWallet::RemoveMultiSig(const CScript& dest) +{ + AssertLockHeld(cs_wallet); + if (!CCryptoKeyStore::RemoveMultiSig(dest)) + return false; + if (!HaveMultiSig()) + NotifyMultiSigChanged(false); + if (fFileBacked) + if (!CWalletDB(strWalletFile).EraseMultiSig(dest)) + return false; + + return true; +} + +bool CWallet::LoadMultiSig(const CScript& dest) +{ + return CCryptoKeyStore::AddMultiSig(dest); +} + bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool anonymizeOnly) { SecureString strWalletPassphraseFinal; @@ -1613,13 +1643,26 @@ void CWallet::AvailableCoins(vector& vCoins, bool fOnlyConfirmed, const } isminetype mine = IsMine(pcoin->vout[i]); - if (!(IsSpent(wtxid, i)) && mine != ISMINE_NO && - (!IsLockedCoin((*it).first, i) || nCoinType == ONLY_10000) && - (pcoin->vout[i].nValue > 0 || fIncludeZeroValue) && - (!coinControl || !coinControl->HasSelected() || coinControl->fAllowOtherInputs || coinControl->IsSelected((*it).first, i))) - vCoins.push_back(COutput(pcoin, i, nDepth, - ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || - (coinControl && coinControl->fAllowWatchOnly && (mine & ISMINE_WATCH_SOLVABLE) != ISMINE_NO))); + if (IsSpent(wtxid, i)) + continue; + if (mine == ISMINE_NO) + continue; + if (mine == ISMINE_WATCH_ONLY) + continue; + + if (IsLockedCoin((*it).first, i) && nCoinType != ONLY_10000) + continue; + if (pcoin->vout[i].nValue <= 0 && !fIncludeZeroValue) + continue; + if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected((*it).first, i)) + continue; + + bool fIsSpendable = false; + if ((mine & ISMINE_SPENDABLE) != ISMINE_NO) + fIsSpendable = true; + if ((mine & ISMINE_MULTISIG) != ISMINE_NO) + fIsSpendable = true; + vCoins.emplace_back(COutput(pcoin, i, nDepth, fIsSpendable)); } } } diff --git a/src/wallet.h b/src/wallet.h index 1dff84cfe4dd..c1e9fba2b2da 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -406,6 +406,12 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface //! Adds a watch-only address to the store, without saving it to disk (used by LoadWallet) bool LoadWatchOnly(const CScript& dest); + //! Adds a MultiSig address to the store, and saves it to disk. + bool AddMultiSig(const CScript& dest); + bool RemoveMultiSig(const CScript& dest); + //! Adds a MultiSig address to the store, without saving it to disk (used by LoadWallet) + bool LoadMultiSig(const CScript& dest); + bool Unlock(const SecureString& strWalletPassphrase, bool anonimizeOnly = false); bool ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase); bool EncryptWallet(const SecureString& strWalletPassphrase); @@ -628,6 +634,9 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface /** Watch-only address added */ boost::signals2::signal NotifyWatchonlyChanged; + + /** MultiSig address added */ + boost::signals2::signal NotifyMultiSigChanged; }; diff --git a/src/wallet_ismine.cpp b/src/wallet_ismine.cpp index b72902932a88..226d612f52cc 100644 --- a/src/wallet_ismine.cpp +++ b/src/wallet_ismine.cpp @@ -10,6 +10,7 @@ #include "keystore.h" #include "script/script.h" #include "script/standard.h" +#include "util.h" #include @@ -22,7 +23,7 @@ unsigned int HaveKeys(const vector& pubkeys, const CKeyStore& keystore) unsigned int nResult = 0; BOOST_FOREACH (const valtype& pubkey, pubkeys) { CKeyID keyID = CPubKey(pubkey).GetID(); - if (keystore.HaveKey(keyID)) + if(keystore.HaveKey(keyID)) ++nResult; } return nResult; @@ -36,11 +37,19 @@ isminetype IsMine(const CKeyStore& keystore, const CTxDestination& dest) isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey) { + if(keystore.HaveWatchOnly(scriptPubKey)) + return ISMINE_WATCH_ONLY; + if(keystore.HaveMultiSig(scriptPubKey)) + return ISMINE_MULTISIG; + vector vSolutions; txnouttype whichType; - if (!Solver(scriptPubKey, whichType, vSolutions)) { - if (keystore.HaveWatchOnly(scriptPubKey)) + if(!Solver(scriptPubKey, whichType, vSolutions)) { + if(keystore.HaveWatchOnly(scriptPubKey)) return ISMINE_WATCH_ONLY; + if(keystore.HaveMultiSig(scriptPubKey)) + return ISMINE_MULTISIG; + return ISMINE_NO; } @@ -52,20 +61,20 @@ isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey) case TX_ZEROCOINMINT: case TX_PUBKEY: keyID = CPubKey(vSolutions[0]).GetID(); - if (keystore.HaveKey(keyID)) + if(keystore.HaveKey(keyID)) return ISMINE_SPENDABLE; break; case TX_PUBKEYHASH: keyID = CKeyID(uint160(vSolutions[0])); - if (keystore.HaveKey(keyID)) + if(keystore.HaveKey(keyID)) return ISMINE_SPENDABLE; break; case TX_SCRIPTHASH: { CScriptID scriptID = CScriptID(uint160(vSolutions[0])); CScript subscript; - if (keystore.GetCScript(scriptID, subscript)) { + if(keystore.GetCScript(scriptID, subscript)) { isminetype ret = IsMine(keystore, subscript); - if (ret == ISMINE_SPENDABLE) + if(ret != ISMINE_NO) return ret; } break; @@ -77,13 +86,16 @@ isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey) // them) enable spend-out-from-under-you attacks, especially // in shared-wallet situations. vector keys(vSolutions.begin() + 1, vSolutions.begin() + vSolutions.size() - 1); - if (HaveKeys(keys, keystore) == keys.size()) + if(HaveKeys(keys, keystore) == keys.size()) return ISMINE_SPENDABLE; break; } } - if (keystore.HaveWatchOnly(scriptPubKey)) + if(keystore.HaveWatchOnly(scriptPubKey)) return ISMINE_WATCH_ONLY; + if(keystore.HaveMultiSig(scriptPubKey)) + return ISMINE_MULTISIG; + return ISMINE_NO; } diff --git a/src/wallet_ismine.h b/src/wallet_ismine.h index b2e5a622472d..32e7b2dce733 100644 --- a/src/wallet_ismine.h +++ b/src/wallet_ismine.h @@ -16,11 +16,10 @@ class CScript; enum isminetype { ISMINE_NO = 0, //! Indicates that we dont know how to create a scriptSig that would solve this if we were given the appropriate private keys - ISMINE_WATCH_UNSOLVABLE = 1, + ISMINE_WATCH_ONLY = 1, //! Indicates that we know how to create a scriptSig that would solve this if we were given the appropriate private keys - ISMINE_WATCH_SOLVABLE = 2, - ISMINE_WATCH_ONLY = ISMINE_WATCH_SOLVABLE | ISMINE_WATCH_UNSOLVABLE, - ISMINE_SPENDABLE = 4, + ISMINE_MULTISIG = 2, + ISMINE_SPENDABLE = 4, ISMINE_ALL = ISMINE_WATCH_ONLY | ISMINE_SPENDABLE }; /** used for bitflags of isminetype */ diff --git a/src/walletdb.cpp b/src/walletdb.cpp index 8b297ef12df3..028f480d95b7 100644 --- a/src/walletdb.cpp +++ b/src/walletdb.cpp @@ -129,6 +129,18 @@ bool CWalletDB::EraseWatchOnly(const CScript& dest) return Erase(std::make_pair(std::string("watchs"), dest)); } +bool CWalletDB::WriteMultiSig(const CScript& dest) +{ + nWalletDBUpdated++; + return Write(std::make_pair(std::string("multisig"), dest), '1'); +} + +bool CWalletDB::EraseMultiSig(const CScript& dest) +{ + nWalletDBUpdated++; + return Erase(std::make_pair(std::string("multisig"), dest)); +} + bool CWalletDB::WriteBestBlock(const CBlockLocator& locator) { nWalletDBUpdated++; @@ -474,6 +486,17 @@ bool ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, CW // Watch-only addresses have no birthday information for now, // so set the wallet birthday to the beginning of time. pwallet->nTimeFirstKey = 1; + } else if (strType == "multisig") { + CScript script; + ssKey >> script; + char fYes; + ssValue >> fYes; + if (fYes == '1') + pwallet->LoadMultiSig(script); + + // MultiSig addresses have no birthday information for now, + // so set the wallet birthday to the beginning of time. + pwallet->nTimeFirstKey = 1; } else if (strType == "key" || strType == "wkey") { CPubKey vchPubKey; ssKey >> vchPubKey; diff --git a/src/walletdb.h b/src/walletdb.h index 545830e9ecfe..f5b67eed6eb0 100644 --- a/src/walletdb.h +++ b/src/walletdb.h @@ -104,6 +104,9 @@ class CWalletDB : public CDB bool WriteWatchOnly(const CScript& script); bool EraseWatchOnly(const CScript& script); + bool WriteMultiSig(const CScript& script); + bool EraseMultiSig(const CScript& script); + bool WriteBestBlock(const CBlockLocator& locator); bool ReadBestBlock(CBlockLocator& locator);