multisig dialog
[novacoin.git] / src / qt / multisigdialog.cpp
diff --git a/src/qt/multisigdialog.cpp b/src/qt/multisigdialog.cpp
new file mode 100644 (file)
index 0000000..9d9418c
--- /dev/null
@@ -0,0 +1,602 @@
+#include <QClipboard>
+#include <QDialog>
+#include <QMessageBox>
+#include <QScrollBar>
+#include <vector>
+
+#include "addresstablemodel.h"
+#include "base58.h"
+#include "key.h"
+#include "main.h"
+#include "multisigaddressentry.h"
+#include "multisiginputentry.h"
+#include "multisigdialog.h"
+#include "ui_multisigdialog.h"
+#include "script.h"
+#include "sendcoinsentry.h"
+#include "util.h"
+#include "wallet.h"
+#include "walletmodel.h"
+
+#ifdef USE_LEVELDB
+#include "txdb-leveldb.h"
+#else
+#include "txdb-bdb.h"
+#endif
+
+MultisigDialog::MultisigDialog(QWidget *parent) : QDialog(parent), ui(new Ui::MultisigDialog), model(0)
+{
+    ui->setupUi(this);
+
+#ifdef Q_WS_MAC // Icons on push buttons are very uncommon on Mac
+    ui->addPubKeyButton->setIcon(QIcon());
+    ui->clearButton->setIcon(QIcon());
+    ui->addInputButton->setIcon(QIcon());
+    ui->addOutputButton->setIcon(QIcon());
+    ui->signTransactionButton->setIcon(QIcon());
+    ui->sendTransactionButton->setIcon(QIcon());
+#endif
+
+    addPubKey();
+    addPubKey();
+
+    connect(ui->addPubKeyButton, SIGNAL(clicked()), this, SLOT(addPubKey()));
+    connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear()));
+
+    addInput();
+    addOutput();
+    updateAmounts();
+
+    connect(ui->addInputButton, SIGNAL(clicked()), this, SLOT(addInput()));
+    connect(ui->addOutputButton, SIGNAL(clicked()), this, SLOT(addOutput()));
+
+    ui->signTransactionButton->setEnabled(false);
+    ui->sendTransactionButton->setEnabled(false);
+}
+
+MultisigDialog::~MultisigDialog()
+{
+    delete ui;
+}
+
+void MultisigDialog::setModel(WalletModel *model)
+{
+    this->model = model;
+
+    for(int i = 0; i < ui->pubkeyEntries->count(); i++)
+    {
+        MultisigAddressEntry *entry = qobject_cast<MultisigAddressEntry *>(ui->pubkeyEntries->itemAt(i)->widget());
+        if(entry)
+            entry->setModel(model);
+    }
+
+
+    for(int i = 0; i < ui->inputs->count(); i++)
+    {
+        MultisigInputEntry *entry = qobject_cast<MultisigInputEntry *>(ui->inputs->itemAt(i)->widget());
+        if(entry)
+            entry->setModel(model);
+    }
+
+
+    for(int i = 0; i < ui->outputs->count(); i++)
+    {
+        SendCoinsEntry *entry = qobject_cast<SendCoinsEntry *>(ui->outputs->itemAt(i)->widget());
+        if(entry)
+            entry->setModel(model);
+    }
+}
+
+void MultisigDialog::updateRemoveEnabled()
+{
+    bool enabled = (ui->pubkeyEntries->count() > 2);
+
+    for(int i = 0; i < ui->pubkeyEntries->count(); i++)
+    {
+        MultisigAddressEntry *entry = qobject_cast<MultisigAddressEntry *>(ui->pubkeyEntries->itemAt(i)->widget());
+        if(entry)
+            entry->setRemoveEnabled(enabled);
+    }
+
+    QString maxSigsStr;
+    maxSigsStr.setNum(ui->pubkeyEntries->count());
+    ui->maxSignaturesLabel->setText(QString("/ ") + maxSigsStr);
+
+
+    enabled = (ui->inputs->count() > 1);
+    for(int i = 0; i < ui->inputs->count(); i++)
+    {
+        MultisigInputEntry *entry = qobject_cast<MultisigInputEntry *>(ui->inputs->itemAt(i)->widget());
+        if(entry)
+            entry->setRemoveEnabled(enabled);
+    }
+
+
+    enabled = (ui->outputs->count() > 1);
+    for(int i = 0; i < ui->outputs->count(); i++)
+    {
+        SendCoinsEntry *entry = qobject_cast<SendCoinsEntry *>(ui->outputs->itemAt(i)->widget());
+        if(entry)
+            entry->setRemoveEnabled(enabled);
+    }
+}
+
+void MultisigDialog::on_createAddressButton_clicked()
+{
+    ui->multisigAddress->clear();
+    ui->redeemScript->clear();
+
+    if(!model)
+        return;
+
+    std::vector<CKey> pubkeys;
+    pubkeys.resize(ui->pubkeyEntries->count());
+    unsigned int required = ui->requiredSignatures->text().toUInt();
+
+    for(int i = 0; i < ui->pubkeyEntries->count(); i++)
+    {
+        MultisigAddressEntry *entry = qobject_cast<MultisigAddressEntry *>(ui->pubkeyEntries->itemAt(i)->widget());
+        if(!entry->validate())
+            return;
+        QString str = entry->getPubkey();
+        CPubKey vchPubKey(ParseHex(str.toStdString().c_str()));
+        if(!vchPubKey.IsValid())
+            return;
+        pubkeys[i].SetPubKey(vchPubKey);
+    }
+
+    if((required == 0) || (required > pubkeys.size()))
+        return;
+
+    CScript script;
+    script.SetMultisig(required, pubkeys);
+    CScriptID scriptID = script.GetID();
+    CBitcoinAddress address(scriptID);
+
+    ui->multisigAddress->setText(address.ToString().c_str());
+    ui->redeemScript->setText(HexStr(script.begin(), script.end()).c_str());
+}
+
+void MultisigDialog::on_copyMultisigAddressButton_clicked()
+{
+    QApplication::clipboard()->setText(ui->multisigAddress->text());
+}
+
+void MultisigDialog::on_copyRedeemScriptButton_clicked()
+{
+    QApplication::clipboard()->setText(ui->redeemScript->text());
+}
+
+void MultisigDialog::on_saveRedeemScriptButton_clicked()
+{
+    if(!model)
+        return;
+
+    CWallet *wallet = model->getWallet();
+    std::string redeemScript = ui->redeemScript->text().toStdString();
+    std::vector<unsigned char> scriptData(ParseHex(redeemScript));
+    CScript script(scriptData.begin(), scriptData.end());
+    CScriptID scriptID = script.GetID();
+
+    LOCK(wallet->cs_wallet);
+    if(!wallet->HaveCScript(scriptID))
+        wallet->AddCScript(script);
+}
+
+void MultisigDialog::on_saveMultisigAddressButton_clicked()
+{
+    if(!model)
+        return;
+
+    CWallet *wallet = model->getWallet();
+    std::string redeemScript = ui->redeemScript->text().toStdString();
+    std::string address = ui->multisigAddress->text().toStdString();
+    std::string label("multisig");
+
+    if(!model->validateAddress(QString(address.c_str())))
+        return;
+
+    std::vector<unsigned char> scriptData(ParseHex(redeemScript));
+    CScript script(scriptData.begin(), scriptData.end());
+    CScriptID scriptID = script.GetID();
+
+    LOCK(wallet->cs_wallet);
+    if(!wallet->HaveCScript(scriptID))
+        wallet->AddCScript(script);
+    if(!wallet->mapAddressBook.count(CBitcoinAddress(address).Get()))
+        wallet->SetAddressBookName(CBitcoinAddress(address).Get(), label);
+}
+
+void MultisigDialog::clear()
+{
+    while(ui->pubkeyEntries->count())
+        delete ui->pubkeyEntries->takeAt(0)->widget();
+
+    addPubKey();
+    addPubKey();
+    updateRemoveEnabled();
+}
+
+MultisigAddressEntry * MultisigDialog::addPubKey()
+{
+    MultisigAddressEntry *entry = new MultisigAddressEntry(this);
+
+    entry->setModel(model);
+    ui->pubkeyEntries->addWidget(entry);
+    connect(entry, SIGNAL(removeEntry(MultisigAddressEntry *)), this, SLOT(removeEntry(MultisigAddressEntry *)));
+    updateRemoveEnabled();
+    entry->clear();
+    ui->scrollAreaWidgetContents->resize(ui->scrollAreaWidgetContents->sizeHint());
+    QScrollBar *bar = ui->scrollArea->verticalScrollBar();
+    if(bar)
+        bar->setSliderPosition(bar->maximum());
+
+    return entry;
+}
+
+void MultisigDialog::removeEntry(MultisigAddressEntry *entry)
+{
+    delete entry;
+    updateRemoveEnabled();
+}
+
+void MultisigDialog::on_createTransactionButton_clicked()
+{
+    CTransaction transaction;
+
+    // Get inputs
+    for(int i = 0; i < ui->inputs->count(); i++)
+    {
+        MultisigInputEntry *entry = qobject_cast<MultisigInputEntry *>(ui->inputs->itemAt(i)->widget());
+        if(entry)
+        {
+            if(entry->validate())
+            {
+                CTxIn input = entry->getInput();
+                transaction.vin.push_back(input);
+            }
+            else
+                return;
+        }
+    }
+
+    // Get outputs
+    for(int i = 0; i < ui->outputs->count(); i++)
+    {
+        SendCoinsEntry *entry = qobject_cast<SendCoinsEntry *>(ui->outputs->itemAt(i)->widget());
+
+        if(entry)
+        {
+            if(entry->validate())
+            {
+                SendCoinsRecipient recipient = entry->getValue();
+                CBitcoinAddress address(recipient.address.toStdString());
+                CScript scriptPubKey;
+                scriptPubKey.SetDestination(address.Get());
+                int64 amount = recipient.amount;
+                CTxOut output(amount, scriptPubKey);
+                transaction.vout.push_back(output);
+            }
+            else
+                return;
+        }
+    }
+
+    CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
+    ss << transaction;
+    ui->transaction->setText(HexStr(ss.begin(), ss.end()).c_str());
+}
+
+void MultisigDialog::on_transaction_textChanged()
+{
+    while(ui->inputs->count())
+        delete ui->inputs->takeAt(0)->widget();
+    while(ui->outputs->count())
+        delete ui->outputs->takeAt(0)->widget();
+
+    if(ui->transaction->text().size() > 0)
+        ui->signTransactionButton->setEnabled(true);
+    else
+        ui->signTransactionButton->setEnabled(false);
+
+    // Decode the raw transaction
+    std::vector<unsigned char> txData(ParseHex(ui->transaction->text().toStdString()));
+    CDataStream ss(txData, SER_NETWORK, PROTOCOL_VERSION);
+    CTransaction tx;
+    try
+    {
+        ss >> tx;
+    }
+    catch(std::exception &e)
+    {
+        return;
+    }
+
+    // Fill input list
+    int index = -1;
+    BOOST_FOREACH(const CTxIn& txin, tx.vin)
+    {
+        uint256 prevoutHash = txin.prevout.hash;
+        addInput();
+        index++;
+        MultisigInputEntry *entry = qobject_cast<MultisigInputEntry *>(ui->inputs->itemAt(index)->widget());
+        if(entry)
+        {
+            entry->setTransactionId(QString(prevoutHash.GetHex().c_str()));
+            entry->setTransactionOutputIndex(txin.prevout.n);
+        }
+    }
+
+    // Fill output list
+    index = -1;
+    BOOST_FOREACH(const CTxOut& txout, tx.vout)
+    {
+        CScript scriptPubKey = txout.scriptPubKey;
+        CTxDestination addr;
+        ExtractDestination(scriptPubKey, addr);
+        CBitcoinAddress address(addr);
+        SendCoinsRecipient recipient;
+        recipient.address = QString(address.ToString().c_str());
+        recipient.amount = txout.nValue;
+        addOutput();
+        index++;
+        SendCoinsEntry *entry = qobject_cast<SendCoinsEntry *>(ui->outputs->itemAt(index)->widget());
+        if(entry)
+        {
+            entry->setValue(recipient);
+        }
+    }
+
+    updateRemoveEnabled();
+}
+
+void MultisigDialog::on_copyTransactionButton_clicked()
+{
+    QApplication::clipboard()->setText(ui->transaction->text());
+}
+
+void MultisigDialog::on_pasteTransactionButton_clicked()
+{
+    ui->transaction->setText(QApplication::clipboard()->text());
+}
+
+void MultisigDialog::on_signTransactionButton_clicked()
+{
+    ui->signedTransaction->clear();
+
+    if(!model)
+        return;
+
+    CWallet *wallet = model->getWallet();
+
+    // Decode the raw transaction
+    std::vector<unsigned char> txData(ParseHex(ui->transaction->text().toStdString()));
+    CDataStream ss(txData, SER_NETWORK, PROTOCOL_VERSION);
+    CTransaction tx;
+    try
+    {
+        ss >> tx;
+    }
+    catch(std::exception &e)
+    {
+        return;
+    }
+    CTransaction mergedTx(tx);
+
+    // Fetch previous transactions (inputs)
+    std::map<COutPoint, CScript> mapPrevOut;
+    for(int i = 0; i < mergedTx.vin.size(); i++)
+    {
+        CTransaction tempTx;
+        MapPrevTx mapPrevTx;
+        CTxDB txdb("r");
+        std::map<uint256, CTxIndex> unused;
+        bool fInvalid;
+
+        tempTx.vin.push_back(mergedTx.vin[i]);
+        tempTx.FetchInputs(txdb, unused, false, false, mapPrevTx, fInvalid);
+
+        BOOST_FOREACH(const CTxIn& txin, tempTx.vin)
+        {
+            const uint256& prevHash = txin.prevout.hash;
+            if(mapPrevTx.count(prevHash) && mapPrevTx[prevHash].second.vout.size() > txin.prevout.n)
+                mapPrevOut[txin.prevout] = mapPrevTx[prevHash].second.vout[txin.prevout.n].scriptPubKey;
+        }
+    }
+
+    // Add the redeem scripts to the wallet keystore
+    for(int i = 0; i < ui->inputs->count(); i++)
+    {
+        MultisigInputEntry *entry = qobject_cast<MultisigInputEntry *>(ui->inputs->itemAt(i)->widget());
+        if(entry)
+        {
+            QString redeemScriptStr = entry->getRedeemScript();
+            if(redeemScriptStr.size() > 0)
+            {
+                std::vector<unsigned char> scriptData(ParseHex(redeemScriptStr.toStdString()));
+                CScript redeemScript(scriptData.begin(), scriptData.end());
+                wallet->AddCScript(redeemScript);
+            }
+        }
+    }
+
+    WalletModel::UnlockContext ctx(model->requestUnlock());
+    if(!ctx.isValid())
+        return;
+
+    // Sign what we can
+    bool fComplete = true;
+    for(int i = 0; i < mergedTx.vin.size(); i++)
+    {
+        CTxIn& txin = mergedTx.vin[i];
+        if(mapPrevOut.count(txin.prevout) == 0)
+        {
+            fComplete = false;
+            continue;
+        }
+        const CScript& prevPubKey = mapPrevOut[txin.prevout];
+
+        txin.scriptSig.clear();
+        SignSignature(*wallet, prevPubKey, mergedTx, i, SIGHASH_ALL);
+        txin.scriptSig = CombineSignatures(prevPubKey, mergedTx, i, txin.scriptSig, tx.vin[i].scriptSig);
+        if(!VerifyScript(txin.scriptSig, prevPubKey, mergedTx, i, true, 0))
+        {
+            fComplete = false;
+        }
+    }
+
+    CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
+    ssTx << mergedTx;
+    ui->signedTransaction->setText(HexStr(ssTx.begin(), ssTx.end()).c_str());
+
+    if(fComplete)
+    {
+        ui->statusLabel->setText(tr("Transaction signature is complete"));
+        ui->sendTransactionButton->setEnabled(true);
+    }
+    else
+    {
+        ui->statusLabel->setText(tr("Transaction is NOT completely signed"));
+        ui->sendTransactionButton->setEnabled(false);
+    }
+}
+
+void MultisigDialog::on_copySignedTransactionButton_clicked()
+{
+    QApplication::clipboard()->setText(ui->signedTransaction->text());
+}
+
+void MultisigDialog::on_sendTransactionButton_clicked()
+{
+    int64 transactionSize = ui->signedTransaction->text().size() / 2;
+    if(transactionSize == 0)
+        return;
+
+    // Check the fee
+    int64 fee = (int64 ) (ui->fee->text().toDouble() * COIN);
+    int64 minFee = MIN_TX_FEE * (1 + (int64) transactionSize / 1000);
+    if(fee < minFee)
+    {
+        QMessageBox::StandardButton ret = QMessageBox::question(this, tr("Confirm send transaction"), tr("The fee of the transaction (%1 NVC) is smaller than the expected fee (%2 NVC). Do you want to send the transaction anyway?").arg((double) fee / COIN).arg((double) minFee / COIN), QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel);
+        if(ret != QMessageBox::Yes)
+            return;
+    }
+    else if(fee > minFee)
+    {
+        QMessageBox::StandardButton ret = QMessageBox::question(this, tr("Confirm send transaction"), tr("The fee of the transaction (%1 NVC) is bigger than the expected fee (%2 NVC). Do you want to send the transaction anyway?").arg((double) fee / COIN).arg((double) minFee / COIN), QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel);
+        if(ret != QMessageBox::Yes)
+            return;
+    }
+
+    // Decode the raw transaction
+    std::vector<unsigned char> txData(ParseHex(ui->signedTransaction->text().toStdString()));
+    CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION);
+    CTransaction tx;
+    try
+    {
+        ssData >> tx;
+    }
+    catch(std::exception &e)
+    {
+        return;
+    }
+    uint256 txHash = tx.GetHash();
+
+    // Check if the transaction is already in the blockchain
+    CTransaction existingTx;
+    uint256 blockHash = 0;
+    if(GetTransaction(txHash, existingTx, blockHash))
+    {
+        if(blockHash != 0)
+            return;
+    }
+
+    // Send the transaction to the local node
+    CTxDB txdb("r");
+    if(!tx.AcceptToMemoryPool(txdb, false))
+    return;
+    SyncWithWallets(tx, NULL, true);
+    //(CInv(MSG_TX, txHash), tx);
+    RelayTransaction(tx, txHash);
+}
+
+MultisigInputEntry * MultisigDialog::addInput()
+{
+    MultisigInputEntry *entry = new MultisigInputEntry(this);
+
+    entry->setModel(model);
+    ui->inputs->addWidget(entry);
+    connect(entry, SIGNAL(removeEntry(MultisigInputEntry *)), this, SLOT(removeEntry(MultisigInputEntry *)));
+    connect(entry, SIGNAL(updateAmount()), this, SLOT(updateAmounts()));
+    updateRemoveEnabled();
+    entry->clear();
+    ui->scrollAreaWidgetContents_2->resize(ui->scrollAreaWidgetContents_2->sizeHint());
+    QScrollBar *bar = ui->scrollArea_2->verticalScrollBar();
+    if(bar)
+        bar->setSliderPosition(bar->maximum());
+
+    return entry;
+}
+
+void MultisigDialog::removeEntry(MultisigInputEntry *entry)
+{
+    delete entry;
+    updateRemoveEnabled();
+}
+
+SendCoinsEntry * MultisigDialog::addOutput()
+{
+    SendCoinsEntry *entry = new SendCoinsEntry(this);
+
+    entry->setModel(model);
+    ui->outputs->addWidget(entry);
+    connect(entry, SIGNAL(removeEntry(SendCoinsEntry *)), this, SLOT(removeEntry(SendCoinsEntry *)));
+    connect(entry, SIGNAL(payAmountChanged()), this, SLOT(updateAmounts()));
+    updateRemoveEnabled();
+    entry->clear();
+    ui->scrollAreaWidgetContents_3->resize(ui->scrollAreaWidgetContents_3->sizeHint());
+    QScrollBar *bar = ui->scrollArea_3->verticalScrollBar();
+    if(bar)
+        bar->setSliderPosition(bar->maximum());
+
+    return entry;
+}
+
+void MultisigDialog::removeEntry(SendCoinsEntry *entry)
+{
+    delete entry;
+    updateRemoveEnabled();
+}
+
+void MultisigDialog::updateAmounts()
+{
+    // Update inputs amount
+    int64 inputsAmount = 0;
+    for(int i = 0; i < ui->inputs->count(); i++)
+    {
+        MultisigInputEntry *entry = qobject_cast<MultisigInputEntry *>(ui->inputs->itemAt(i)->widget());
+        if(entry)
+            inputsAmount += entry->getAmount();
+    }
+    QString inputsAmountStr;
+    inputsAmountStr.sprintf("%.6f", (double) inputsAmount / COIN);
+    ui->inputsAmount->setText(inputsAmountStr);
+
+    // Update outputs amount
+    int64 outputsAmount = 0;
+    for(int i = 0; i < ui->outputs->count(); i++)
+    {
+        SendCoinsEntry *entry = qobject_cast<SendCoinsEntry *>(ui->outputs->itemAt(i)->widget());
+        if(entry)
+            outputsAmount += entry->getValue().amount;
+    }
+    QString outputsAmountStr;
+    outputsAmountStr.sprintf("%.6f", (double) outputsAmount / COIN);
+    ui->outputsAmount->setText(outputsAmountStr);
+
+    // Update Fee amount
+    int64 fee = inputsAmount - outputsAmount;
+    QString feeStr;
+    feeStr.sprintf("%.6f", (double) fee / COIN);
+    ui->fee->setText(feeStr);
+}
\ No newline at end of file