RedeemScriptSizeWarn
[novacoin.git] / src / qt / multisigdialog.cpp
1 #include <QClipboard>
2 #include <QDialog>
3 #include <QMessageBox>
4 #include <QScrollBar>
5 #include <vector>
6
7 #include "addresstablemodel.h"
8 #include "base58.h"
9 #include "key.h"
10 #include "main.h"
11 #include "multisigaddressentry.h"
12 #include "multisiginputentry.h"
13 #include "multisigdialog.h"
14 #include "ui_multisigdialog.h"
15 #include "script.h"
16 #include "sendcoinsentry.h"
17 #include "util.h"
18 #include "wallet.h"
19 #include "walletmodel.h"
20
21 #ifdef USE_LEVELDB
22 #include "txdb-leveldb.h"
23 #else
24 #include "txdb-bdb.h"
25 #endif
26
27 MultisigDialog::MultisigDialog(QWidget *parent) : QDialog(parent), ui(new Ui::MultisigDialog), model(0)
28 {
29     ui->setupUi(this);
30
31 #ifdef Q_WS_MAC // Icons on push buttons are very uncommon on Mac
32     ui->addPubKeyButton->setIcon(QIcon());
33     ui->clearButton->setIcon(QIcon());
34     ui->addInputButton->setIcon(QIcon());
35     ui->addOutputButton->setIcon(QIcon());
36     ui->signTransactionButton->setIcon(QIcon());
37     ui->sendTransactionButton->setIcon(QIcon());
38 #endif
39
40     addPubKey();
41     addPubKey();
42
43     connect(ui->addPubKeyButton, SIGNAL(clicked()), this, SLOT(addPubKey()));
44     connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear()));
45
46     addInput();
47     addOutput();
48
49     connect(ui->addInputButton, SIGNAL(clicked()), this, SLOT(addInput()));
50     connect(ui->addOutputButton, SIGNAL(clicked()), this, SLOT(addOutput()));
51
52     ui->signTransactionButton->setEnabled(false);
53     ui->sendTransactionButton->setEnabled(false);
54 }
55
56 void MultisigDialog::showEvent(QShowEvent *event)
57 {
58     QWidget::showEvent(event);
59
60     if (!model)
61         return;
62
63     updateAmounts();
64 }
65
66 void MultisigDialog::hideEvent(QHideEvent *event)
67 {
68     QWidget::hideEvent(event);
69
70     if (!model)
71         return;
72
73     clear();
74 }
75
76 MultisigDialog::~MultisigDialog()
77 {
78     delete ui;
79 }
80
81 void MultisigDialog::setModel(WalletModel *model)
82 {
83     this->model = model;
84
85     for(int i = 0; i < ui->pubkeyEntries->count(); i++)
86     {
87         MultisigAddressEntry *entry = qobject_cast<MultisigAddressEntry *>(ui->pubkeyEntries->itemAt(i)->widget());
88         if(entry)
89             entry->setModel(model);
90     }
91
92
93     for(int i = 0; i < ui->inputs->count(); i++)
94     {
95         MultisigInputEntry *entry = qobject_cast<MultisigInputEntry *>(ui->inputs->itemAt(i)->widget());
96         if(entry)
97             entry->setModel(model);
98     }
99
100
101     for(int i = 0; i < ui->outputs->count(); i++)
102     {
103         SendCoinsEntry *entry = qobject_cast<SendCoinsEntry *>(ui->outputs->itemAt(i)->widget());
104         if(entry)
105             entry->setModel(model);
106     }
107 }
108
109 void MultisigDialog::updateRemoveEnabled()
110 {
111     bool enabled = (ui->pubkeyEntries->count() > 2);
112
113     for(int i = 0; i < ui->pubkeyEntries->count(); i++)
114     {
115         MultisigAddressEntry *entry = qobject_cast<MultisigAddressEntry *>(ui->pubkeyEntries->itemAt(i)->widget());
116         if(entry)
117             entry->setRemoveEnabled(enabled);
118     }
119
120     QString maxSigsStr;
121     maxSigsStr.setNum(ui->pubkeyEntries->count());
122     ui->maxSignaturesLabel->setText(QString("/ ") + maxSigsStr);
123
124
125     enabled = (ui->inputs->count() > 1);
126     for(int i = 0; i < ui->inputs->count(); i++)
127     {
128         MultisigInputEntry *entry = qobject_cast<MultisigInputEntry *>(ui->inputs->itemAt(i)->widget());
129         if(entry)
130             entry->setRemoveEnabled(enabled);
131     }
132
133
134     enabled = (ui->outputs->count() > 1);
135     for(int i = 0; i < ui->outputs->count(); i++)
136     {
137         SendCoinsEntry *entry = qobject_cast<SendCoinsEntry *>(ui->outputs->itemAt(i)->widget());
138         if(entry)
139             entry->setRemoveEnabled(enabled);
140     }
141 }
142
143 void MultisigDialog::on_createAddressButton_clicked()
144 {
145     ui->multisigAddress->clear();
146     ui->redeemScript->clear();
147
148     if(!model)
149         return;
150
151     std::vector<CKey> pubkeys;
152     pubkeys.resize(ui->pubkeyEntries->count());
153     unsigned int required = ui->requiredSignatures->text().toUInt();
154
155     for(int i = 0; i < ui->pubkeyEntries->count(); i++)
156     {
157         MultisigAddressEntry *entry = qobject_cast<MultisigAddressEntry *>(ui->pubkeyEntries->itemAt(i)->widget());
158         if(!entry->validate())
159             return;
160         QString str = entry->getPubkey();
161         CPubKey vchPubKey(ParseHex(str.toStdString().c_str()));
162         if(!vchPubKey.IsValid())
163             return;
164         pubkeys[i].SetPubKey(vchPubKey);
165     }
166
167     if((required == 0) || (required > pubkeys.size()))
168         return;
169
170     CScript script;
171     script.SetMultisig(required, pubkeys);
172     if (script.size() > MAX_SCRIPT_ELEMENT_SIZE)
173     {
174         QMessageBox::warning(this, tr("Error"), tr("Redeem script exceeds size limit: %1 > %2\nReduce the number of addresses involved in the address creation.").arg(script.size()).arg(MAX_SCRIPT_ELEMENT_SIZE), QMessageBox::Ok);
175         return;
176     }
177     CScriptID scriptID = script.GetID();
178     CBitcoinAddress address(scriptID);
179
180     ui->multisigAddress->setText(address.ToString().c_str());
181     ui->redeemScript->setText(HexStr(script.begin(), script.end()).c_str());
182 }
183
184 void MultisigDialog::on_copyMultisigAddressButton_clicked()
185 {
186     QApplication::clipboard()->setText(ui->multisigAddress->text());
187 }
188
189 void MultisigDialog::on_copyRedeemScriptButton_clicked()
190 {
191     QApplication::clipboard()->setText(ui->redeemScript->text());
192 }
193
194 void MultisigDialog::on_saveRedeemScriptButton_clicked()
195 {
196     if(!model)
197         return;
198
199     CWallet *wallet = model->getWallet();
200     std::string redeemScript = ui->redeemScript->text().toStdString();
201     std::vector<unsigned char> scriptData(ParseHex(redeemScript));
202     CScript script(scriptData.begin(), scriptData.end());
203     CScriptID scriptID = script.GetID();
204
205     LOCK(wallet->cs_wallet);
206     if(!wallet->HaveCScript(scriptID))
207         wallet->AddCScript(script);
208 }
209
210 void MultisigDialog::on_saveMultisigAddressButton_clicked()
211 {
212     if(!model)
213         return;
214
215     CWallet *wallet = model->getWallet();
216     std::string redeemScript = ui->redeemScript->text().toStdString();
217     std::string address = ui->multisigAddress->text().toStdString();
218     std::string label("multisig");
219
220     if(!model->validateAddress(QString(address.c_str())))
221         return;
222
223     std::vector<unsigned char> scriptData(ParseHex(redeemScript));
224     CScript script(scriptData.begin(), scriptData.end());
225     CScriptID scriptID = script.GetID();
226
227     LOCK(wallet->cs_wallet);
228     if(!wallet->HaveCScript(scriptID))
229         wallet->AddCScript(script);
230     if(!wallet->mapAddressBook.count(CBitcoinAddress(address).Get()))
231         wallet->SetAddressBookName(CBitcoinAddress(address).Get(), label);
232 }
233
234 void MultisigDialog::clear()
235 {
236     while(ui->pubkeyEntries->count())
237         delete ui->pubkeyEntries->takeAt(0)->widget();
238
239     addPubKey();
240     addPubKey();
241     updateRemoveEnabled();
242 }
243
244 MultisigAddressEntry * MultisigDialog::addPubKey()
245 {
246     MultisigAddressEntry *entry = new MultisigAddressEntry(this);
247
248     entry->setModel(model);
249     ui->pubkeyEntries->addWidget(entry);
250     connect(entry, SIGNAL(removeEntry(MultisigAddressEntry *)), this, SLOT(removeEntry(MultisigAddressEntry *)));
251     updateRemoveEnabled();
252     entry->clear();
253     ui->scrollAreaWidgetContents->resize(ui->scrollAreaWidgetContents->sizeHint());
254     QScrollBar *bar = ui->scrollArea->verticalScrollBar();
255     if(bar)
256         bar->setSliderPosition(bar->maximum());
257
258     return entry;
259 }
260
261 void MultisigDialog::removeEntry(MultisigAddressEntry *entry)
262 {
263     delete entry;
264     updateRemoveEnabled();
265 }
266
267 void MultisigDialog::on_createTransactionButton_clicked()
268 {
269     CTransaction transaction;
270
271     // Get inputs
272     for(int i = 0; i < ui->inputs->count(); i++)
273     {
274         MultisigInputEntry *entry = qobject_cast<MultisigInputEntry *>(ui->inputs->itemAt(i)->widget());
275         if(entry)
276         {
277             if(entry->validate())
278             {
279                 CTxIn input = entry->getInput();
280                 transaction.vin.push_back(input);
281             }
282             else
283                 return;
284         }
285     }
286
287     // Get outputs
288     for(int i = 0; i < ui->outputs->count(); i++)
289     {
290         SendCoinsEntry *entry = qobject_cast<SendCoinsEntry *>(ui->outputs->itemAt(i)->widget());
291
292         if(entry)
293         {
294             if(entry->validate())
295             {
296                 SendCoinsRecipient recipient = entry->getValue();
297                 CBitcoinAddress address(recipient.address.toStdString());
298                 CScript scriptPubKey;
299                 scriptPubKey.SetDestination(address.Get());
300                 int64_t amount = recipient.amount;
301                 CTxOut output(amount, scriptPubKey);
302                 transaction.vout.push_back(output);
303             }
304             else
305                 return;
306         }
307     }
308
309     CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
310     ss << transaction;
311     ui->transaction->setText(HexStr(ss.begin(), ss.end()).c_str());
312 }
313
314 void MultisigDialog::on_transaction_textChanged()
315 {
316     while(ui->inputs->count())
317         delete ui->inputs->takeAt(0)->widget();
318     while(ui->outputs->count())
319         delete ui->outputs->takeAt(0)->widget();
320
321     if(ui->transaction->text().size() > 0)
322         ui->signTransactionButton->setEnabled(true);
323     else
324         ui->signTransactionButton->setEnabled(false);
325
326     // Decode the raw transaction
327     std::vector<unsigned char> txData(ParseHex(ui->transaction->text().toStdString()));
328     CDataStream ss(txData, SER_NETWORK, PROTOCOL_VERSION);
329     CTransaction tx;
330     try
331     {
332         ss >> tx;
333     }
334     catch(std::exception &e)
335     {
336         return;
337     }
338
339     // Fill input list
340     int index = -1;
341     BOOST_FOREACH(const CTxIn& txin, tx.vin)
342     {
343         uint256 prevoutHash = txin.prevout.hash;
344         addInput();
345         index++;
346         MultisigInputEntry *entry = qobject_cast<MultisigInputEntry *>(ui->inputs->itemAt(index)->widget());
347         if(entry)
348         {
349             entry->setTransactionId(QString(prevoutHash.GetHex().c_str()));
350             entry->setTransactionOutputIndex(txin.prevout.n);
351         }
352     }
353
354     // Fill output list
355     index = -1;
356     BOOST_FOREACH(const CTxOut& txout, tx.vout)
357     {
358         CScript scriptPubKey = txout.scriptPubKey;
359         CTxDestination addr;
360         ExtractDestination(scriptPubKey, addr);
361         CBitcoinAddress address(addr);
362         SendCoinsRecipient recipient;
363         recipient.address = QString(address.ToString().c_str());
364         recipient.amount = txout.nValue;
365         addOutput();
366         index++;
367         SendCoinsEntry *entry = qobject_cast<SendCoinsEntry *>(ui->outputs->itemAt(index)->widget());
368         if(entry)
369         {
370             entry->setValue(recipient);
371         }
372     }
373
374     updateRemoveEnabled();
375 }
376
377 void MultisigDialog::on_copyTransactionButton_clicked()
378 {
379     QApplication::clipboard()->setText(ui->transaction->text());
380 }
381
382 void MultisigDialog::on_pasteTransactionButton_clicked()
383 {
384     ui->transaction->setText(QApplication::clipboard()->text());
385 }
386
387 void MultisigDialog::on_signTransactionButton_clicked()
388 {
389     ui->signedTransaction->clear();
390
391     if(!model)
392         return;
393
394     CWallet *wallet = model->getWallet();
395
396     // Decode the raw transaction
397     std::vector<unsigned char> txData(ParseHex(ui->transaction->text().toStdString()));
398     CDataStream ss(txData, SER_NETWORK, PROTOCOL_VERSION);
399     CTransaction tx;
400     try
401     {
402         ss >> tx;
403     }
404     catch(std::exception &e)
405     {
406         return;
407     }
408     CTransaction mergedTx(tx);
409
410     // Fetch previous transactions (inputs)
411     std::map<COutPoint, CScript> mapPrevOut;
412     for(unsigned int i = 0; i < mergedTx.vin.size(); i++)
413     {
414         CTransaction tempTx;
415         MapPrevTx mapPrevTx;
416         CTxDB txdb("r");
417         std::map<uint256, CTxIndex> unused;
418         bool fInvalid;
419
420         tempTx.vin.push_back(mergedTx.vin[i]);
421         tempTx.FetchInputs(txdb, unused, false, false, mapPrevTx, fInvalid);
422
423         BOOST_FOREACH(const CTxIn& txin, tempTx.vin)
424         {
425             const uint256& prevHash = txin.prevout.hash;
426             if(mapPrevTx.count(prevHash) && mapPrevTx[prevHash].second.vout.size() > txin.prevout.n)
427                 mapPrevOut[txin.prevout] = mapPrevTx[prevHash].second.vout[txin.prevout.n].scriptPubKey;
428         }
429     }
430
431     // Add the redeem scripts to the wallet keystore
432     for(int i = 0; i < ui->inputs->count(); i++)
433     {
434         MultisigInputEntry *entry = qobject_cast<MultisigInputEntry *>(ui->inputs->itemAt(i)->widget());
435         if(entry)
436         {
437             QString redeemScriptStr = entry->getRedeemScript();
438             if(redeemScriptStr.size() > 0)
439             {
440                 std::vector<unsigned char> scriptData(ParseHex(redeemScriptStr.toStdString()));
441                 CScript redeemScript(scriptData.begin(), scriptData.end());
442                 wallet->AddCScript(redeemScript);
443             }
444         }
445     }
446
447     WalletModel::UnlockContext ctx(model->requestUnlock());
448     if(!ctx.isValid())
449         return;
450
451     // Sign what we can
452     bool fComplete = true;
453     for(unsigned int i = 0; i < mergedTx.vin.size(); i++)
454     {
455         CTxIn& txin = mergedTx.vin[i];
456         if(mapPrevOut.count(txin.prevout) == 0)
457         {
458             fComplete = false;
459             continue;
460         }
461         const CScript& prevPubKey = mapPrevOut[txin.prevout];
462
463         txin.scriptSig.clear();
464         SignSignature(*wallet, prevPubKey, mergedTx, i, SIGHASH_ALL);
465         txin.scriptSig = CombineSignatures(prevPubKey, mergedTx, i, txin.scriptSig, tx.vin[i].scriptSig);
466         if(!VerifyScript(txin.scriptSig, prevPubKey, mergedTx, i, true, 0))
467         {
468             fComplete = false;
469         }
470     }
471
472     CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
473     ssTx << mergedTx;
474     ui->signedTransaction->setText(HexStr(ssTx.begin(), ssTx.end()).c_str());
475
476     if(fComplete)
477     {
478         ui->statusLabel->setText(tr("Transaction signature is complete"));
479         ui->sendTransactionButton->setEnabled(true);
480     }
481     else
482     {
483         ui->statusLabel->setText(tr("Transaction is NOT completely signed"));
484         ui->sendTransactionButton->setEnabled(false);
485     }
486 }
487
488 void MultisigDialog::on_copySignedTransactionButton_clicked()
489 {
490     QApplication::clipboard()->setText(ui->signedTransaction->text());
491 }
492
493 void MultisigDialog::on_sendTransactionButton_clicked()
494 {
495     int64_t transactionSize = ui->signedTransaction->text().size() / 2;
496     if(transactionSize == 0)
497         return;
498
499     // Check the fee
500     int64_t fee = (int64_t ) (ui->fee->text().toDouble() * COIN);
501     int64_t minFee = MIN_TX_FEE * (1 + (int64_t) transactionSize / 1000);
502     if(fee < minFee)
503     {
504         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);
505         if(ret != QMessageBox::Yes)
506             return;
507     }
508     else if(fee > minFee)
509     {
510         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);
511         if(ret != QMessageBox::Yes)
512             return;
513     }
514
515     // Decode the raw transaction
516     std::vector<unsigned char> txData(ParseHex(ui->signedTransaction->text().toStdString()));
517     CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION);
518     CTransaction tx;
519     try
520     {
521         ssData >> tx;
522     }
523     catch(std::exception &e)
524     {
525         return;
526     }
527     uint256 txHash = tx.GetHash();
528
529     // Check if the transaction is already in the blockchain
530     CTransaction existingTx;
531     uint256 blockHash = 0;
532     if(GetTransaction(txHash, existingTx, blockHash))
533     {
534         if(blockHash != 0)
535             return;
536     }
537
538     // Send the transaction to the local node
539     CTxDB txdb("r");
540     if(!tx.AcceptToMemoryPool(txdb, false))
541     return;
542     SyncWithWallets(tx, NULL, true);
543     //(CInv(MSG_TX, txHash), tx);
544     RelayTransaction(tx, txHash);
545 }
546
547 MultisigInputEntry * MultisigDialog::addInput()
548 {
549     MultisigInputEntry *entry = new MultisigInputEntry(this);
550
551     entry->setModel(model);
552     ui->inputs->addWidget(entry);
553     connect(entry, SIGNAL(removeEntry(MultisigInputEntry *)), this, SLOT(removeEntry(MultisigInputEntry *)));
554     connect(entry, SIGNAL(updateAmount()), this, SLOT(updateAmounts()));
555     updateRemoveEnabled();
556     entry->clear();
557     ui->scrollAreaWidgetContents_2->resize(ui->scrollAreaWidgetContents_2->sizeHint());
558     QScrollBar *bar = ui->scrollArea_2->verticalScrollBar();
559     if(bar)
560         bar->setSliderPosition(bar->maximum());
561
562     return entry;
563 }
564
565 void MultisigDialog::removeEntry(MultisigInputEntry *entry)
566 {
567     delete entry;
568     updateRemoveEnabled();
569 }
570
571 SendCoinsEntry * MultisigDialog::addOutput()
572 {
573     SendCoinsEntry *entry = new SendCoinsEntry(this);
574
575     entry->setModel(model);
576     ui->outputs->addWidget(entry);
577     connect(entry, SIGNAL(removeEntry(SendCoinsEntry *)), this, SLOT(removeEntry(SendCoinsEntry *)));
578     connect(entry, SIGNAL(payAmountChanged()), this, SLOT(updateAmounts()));
579     updateRemoveEnabled();
580     entry->clear();
581     ui->scrollAreaWidgetContents_3->resize(ui->scrollAreaWidgetContents_3->sizeHint());
582     QScrollBar *bar = ui->scrollArea_3->verticalScrollBar();
583     if(bar)
584         bar->setSliderPosition(bar->maximum());
585
586     return entry;
587 }
588
589 void MultisigDialog::removeEntry(SendCoinsEntry *entry)
590 {
591     delete entry;
592     updateRemoveEnabled();
593 }
594
595 void MultisigDialog::updateAmounts()
596 {
597     // Update inputs amount
598     int64_t inputsAmount = 0;
599     for(int i = 0; i < ui->inputs->count(); i++)
600     {
601         MultisigInputEntry *entry = qobject_cast<MultisigInputEntry *>(ui->inputs->itemAt(i)->widget());
602         if(entry)
603             inputsAmount += entry->getAmount();
604     }
605     QString inputsAmountStr;
606     inputsAmountStr.sprintf("%.6f", (double) inputsAmount / COIN);
607     ui->inputsAmount->setText(inputsAmountStr);
608
609     // Update outputs amount
610     int64_t outputsAmount = 0;
611     for(int i = 0; i < ui->outputs->count(); i++)
612     {
613         SendCoinsEntry *entry = qobject_cast<SendCoinsEntry *>(ui->outputs->itemAt(i)->widget());
614         if(entry)
615             outputsAmount += entry->getValue().amount;
616     }
617     QString outputsAmountStr;
618     outputsAmountStr.sprintf("%.6f", (double) outputsAmount / COIN);
619     ui->outputsAmount->setText(outputsAmountStr);
620
621     // Update Fee amount
622     int64_t fee = inputsAmount - outputsAmount;
623     QString feeStr;
624     feeStr.sprintf("%.6f", (double) fee / COIN);
625     ui->fee->setText(feeStr);
626 }