e8ad839244324b9f08b1032535b6eaf8bc9eb9a5
[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(pubkeys.size() > 16)
168     {
169         QMessageBox::warning(this, tr("Error"), tr("Number of addresses involved in the address creation > %1\nReduce the number").arg(16), QMessageBox::Ok);
170         return;
171     }
172
173     if(required == 0)
174     {
175         QMessageBox::warning(this, tr("Error"), tr("Number of required signatures is 0\nNumber of required signatures must be between 1 and number of keys involved in the creation of address."), QMessageBox::Ok);
176         return;
177     }
178
179     if(required > pubkeys.size())
180     {
181         QMessageBox::warning(this, tr("Error"), tr("Number of required signatures > Number of keys involved in the creation of address."), QMessageBox::Ok);
182         return;
183     }
184     
185     CScript script;
186     script.SetMultisig(required, pubkeys);
187     if (script.size() > MAX_SCRIPT_ELEMENT_SIZE)
188     {
189         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);
190         return;
191     }
192     CScriptID scriptID = script.GetID();
193     CBitcoinAddress address(scriptID);
194
195     ui->multisigAddress->setText(address.ToString().c_str());
196     ui->redeemScript->setText(HexStr(script.begin(), script.end()).c_str());
197 }
198
199 void MultisigDialog::on_copyMultisigAddressButton_clicked()
200 {
201     QApplication::clipboard()->setText(ui->multisigAddress->text());
202 }
203
204 void MultisigDialog::on_copyRedeemScriptButton_clicked()
205 {
206     QApplication::clipboard()->setText(ui->redeemScript->text());
207 }
208
209 void MultisigDialog::on_saveRedeemScriptButton_clicked()
210 {
211     if(!model)
212         return;
213
214     CWallet *wallet = model->getWallet();
215     std::string redeemScript = ui->redeemScript->text().toStdString();
216     std::vector<unsigned char> scriptData(ParseHex(redeemScript));
217     CScript script(scriptData.begin(), scriptData.end());
218     CScriptID scriptID = script.GetID();
219
220     LOCK(wallet->cs_wallet);
221     if(!wallet->HaveCScript(scriptID))
222         wallet->AddCScript(script);
223 }
224
225 void MultisigDialog::on_saveMultisigAddressButton_clicked()
226 {
227     if(!model)
228         return;
229
230     CWallet *wallet = model->getWallet();
231     std::string redeemScript = ui->redeemScript->text().toStdString();
232     std::string address = ui->multisigAddress->text().toStdString();
233     std::string label("multisig");
234
235     if(!model->validateAddress(QString(address.c_str())))
236         return;
237
238     std::vector<unsigned char> scriptData(ParseHex(redeemScript));
239     CScript script(scriptData.begin(), scriptData.end());
240     CScriptID scriptID = script.GetID();
241
242     LOCK(wallet->cs_wallet);
243     if(!wallet->HaveCScript(scriptID))
244         wallet->AddCScript(script);
245     if(!wallet->mapAddressBook.count(CBitcoinAddress(address).Get()))
246         wallet->SetAddressBookName(CBitcoinAddress(address).Get(), label);
247 }
248
249 void MultisigDialog::clear()
250 {
251     while(ui->pubkeyEntries->count())
252         delete ui->pubkeyEntries->takeAt(0)->widget();
253
254     addPubKey();
255     addPubKey();
256     updateRemoveEnabled();
257 }
258
259 MultisigAddressEntry * MultisigDialog::addPubKey()
260 {
261     MultisigAddressEntry *entry = new MultisigAddressEntry(this);
262
263     entry->setModel(model);
264     ui->pubkeyEntries->addWidget(entry);
265     connect(entry, SIGNAL(removeEntry(MultisigAddressEntry *)), this, SLOT(removeEntry(MultisigAddressEntry *)));
266     updateRemoveEnabled();
267     entry->clear();
268     ui->scrollAreaWidgetContents->resize(ui->scrollAreaWidgetContents->sizeHint());
269     QScrollBar *bar = ui->scrollArea->verticalScrollBar();
270     if(bar)
271         bar->setSliderPosition(bar->maximum());
272
273     return entry;
274 }
275
276 void MultisigDialog::removeEntry(MultisigAddressEntry *entry)
277 {
278     delete entry;
279     updateRemoveEnabled();
280 }
281
282 void MultisigDialog::on_createTransactionButton_clicked()
283 {
284     CTransaction transaction;
285
286     // Get inputs
287     for(int i = 0; i < ui->inputs->count(); i++)
288     {
289         MultisigInputEntry *entry = qobject_cast<MultisigInputEntry *>(ui->inputs->itemAt(i)->widget());
290         if(entry)
291         {
292             if(entry->validate())
293             {
294                 CTxIn input = entry->getInput();
295                 transaction.vin.push_back(input);
296             }
297             else
298                 return;
299         }
300     }
301
302     // Get outputs
303     for(int i = 0; i < ui->outputs->count(); i++)
304     {
305         SendCoinsEntry *entry = qobject_cast<SendCoinsEntry *>(ui->outputs->itemAt(i)->widget());
306
307         if(entry)
308         {
309             if(entry->validate())
310             {
311                 SendCoinsRecipient recipient = entry->getValue();
312                 CBitcoinAddress address(recipient.address.toStdString());
313                 CScript scriptPubKey;
314                 scriptPubKey.SetDestination(address.Get());
315                 int64_t amount = recipient.amount;
316                 CTxOut output(amount, scriptPubKey);
317                 transaction.vout.push_back(output);
318             }
319             else
320                 return;
321         }
322     }
323
324     CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
325     ss << transaction;
326     ui->transaction->setText(HexStr(ss.begin(), ss.end()).c_str());
327 }
328
329 void MultisigDialog::on_transaction_textChanged()
330 {
331     while(ui->inputs->count())
332         delete ui->inputs->takeAt(0)->widget();
333     while(ui->outputs->count())
334         delete ui->outputs->takeAt(0)->widget();
335
336     if(ui->transaction->text().size() > 0)
337         ui->signTransactionButton->setEnabled(true);
338     else
339         ui->signTransactionButton->setEnabled(false);
340
341     // Decode the raw transaction
342     std::vector<unsigned char> txData(ParseHex(ui->transaction->text().toStdString()));
343     CDataStream ss(txData, SER_NETWORK, PROTOCOL_VERSION);
344     CTransaction tx;
345     try
346     {
347         ss >> tx;
348     }
349     catch(std::exception &e)
350     {
351         return;
352     }
353
354     // Fill input list
355     int index = -1;
356     BOOST_FOREACH(const CTxIn& txin, tx.vin)
357     {
358         uint256 prevoutHash = txin.prevout.hash;
359         addInput();
360         index++;
361         MultisigInputEntry *entry = qobject_cast<MultisigInputEntry *>(ui->inputs->itemAt(index)->widget());
362         if(entry)
363         {
364             entry->setTransactionId(QString(prevoutHash.GetHex().c_str()));
365             entry->setTransactionOutputIndex(txin.prevout.n);
366         }
367     }
368
369     // Fill output list
370     index = -1;
371     BOOST_FOREACH(const CTxOut& txout, tx.vout)
372     {
373         CScript scriptPubKey = txout.scriptPubKey;
374         CTxDestination addr;
375         ExtractDestination(scriptPubKey, addr);
376         CBitcoinAddress address(addr);
377         SendCoinsRecipient recipient;
378         recipient.address = QString(address.ToString().c_str());
379         recipient.amount = txout.nValue;
380         addOutput();
381         index++;
382         SendCoinsEntry *entry = qobject_cast<SendCoinsEntry *>(ui->outputs->itemAt(index)->widget());
383         if(entry)
384         {
385             entry->setValue(recipient);
386         }
387     }
388
389     updateRemoveEnabled();
390 }
391
392 void MultisigDialog::on_copyTransactionButton_clicked()
393 {
394     QApplication::clipboard()->setText(ui->transaction->text());
395 }
396
397 void MultisigDialog::on_pasteTransactionButton_clicked()
398 {
399     ui->transaction->setText(QApplication::clipboard()->text());
400 }
401
402 void MultisigDialog::on_signTransactionButton_clicked()
403 {
404     ui->signedTransaction->clear();
405
406     if(!model)
407         return;
408
409     CWallet *wallet = model->getWallet();
410
411     // Decode the raw transaction
412     std::vector<unsigned char> txData(ParseHex(ui->transaction->text().toStdString()));
413     CDataStream ss(txData, SER_NETWORK, PROTOCOL_VERSION);
414     CTransaction tx;
415     try
416     {
417         ss >> tx;
418     }
419     catch(std::exception &e)
420     {
421         return;
422     }
423     CTransaction mergedTx(tx);
424
425     // Fetch previous transactions (inputs)
426     std::map<COutPoint, CScript> mapPrevOut;
427     for(unsigned int i = 0; i < mergedTx.vin.size(); i++)
428     {
429         CTransaction tempTx;
430         MapPrevTx mapPrevTx;
431         CTxDB txdb("r");
432         std::map<uint256, CTxIndex> unused;
433         bool fInvalid;
434
435         tempTx.vin.push_back(mergedTx.vin[i]);
436         tempTx.FetchInputs(txdb, unused, false, false, mapPrevTx, fInvalid);
437
438         BOOST_FOREACH(const CTxIn& txin, tempTx.vin)
439         {
440             const uint256& prevHash = txin.prevout.hash;
441             if(mapPrevTx.count(prevHash) && mapPrevTx[prevHash].second.vout.size() > txin.prevout.n)
442                 mapPrevOut[txin.prevout] = mapPrevTx[prevHash].second.vout[txin.prevout.n].scriptPubKey;
443         }
444     }
445
446     // Add the redeem scripts to the wallet keystore
447     for(int i = 0; i < ui->inputs->count(); i++)
448     {
449         MultisigInputEntry *entry = qobject_cast<MultisigInputEntry *>(ui->inputs->itemAt(i)->widget());
450         if(entry)
451         {
452             QString redeemScriptStr = entry->getRedeemScript();
453             if(redeemScriptStr.size() > 0)
454             {
455                 std::vector<unsigned char> scriptData(ParseHex(redeemScriptStr.toStdString()));
456                 CScript redeemScript(scriptData.begin(), scriptData.end());
457                 wallet->AddCScript(redeemScript);
458             }
459         }
460     }
461
462     WalletModel::UnlockContext ctx(model->requestUnlock());
463     if(!ctx.isValid())
464         return;
465
466     // Sign what we can
467     bool fComplete = true;
468     for(unsigned int i = 0; i < mergedTx.vin.size(); i++)
469     {
470         CTxIn& txin = mergedTx.vin[i];
471         if(mapPrevOut.count(txin.prevout) == 0)
472         {
473             fComplete = false;
474             continue;
475         }
476         const CScript& prevPubKey = mapPrevOut[txin.prevout];
477
478         txin.scriptSig.clear();
479         SignSignature(*wallet, prevPubKey, mergedTx, i, SIGHASH_ALL);
480         txin.scriptSig = CombineSignatures(prevPubKey, mergedTx, i, txin.scriptSig, tx.vin[i].scriptSig);
481         if(!VerifyScript(txin.scriptSig, prevPubKey, mergedTx, i, true, 0))
482         {
483             fComplete = false;
484         }
485     }
486
487     CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
488     ssTx << mergedTx;
489     ui->signedTransaction->setText(HexStr(ssTx.begin(), ssTx.end()).c_str());
490
491     if(fComplete)
492     {
493         ui->statusLabel->setText(tr("Transaction signature is complete"));
494         ui->sendTransactionButton->setEnabled(true);
495     }
496     else
497     {
498         ui->statusLabel->setText(tr("Transaction is NOT completely signed"));
499         ui->sendTransactionButton->setEnabled(false);
500     }
501 }
502
503 void MultisigDialog::on_copySignedTransactionButton_clicked()
504 {
505     QApplication::clipboard()->setText(ui->signedTransaction->text());
506 }
507
508 void MultisigDialog::on_sendTransactionButton_clicked()
509 {
510     int64_t transactionSize = ui->signedTransaction->text().size() / 2;
511     if(transactionSize == 0)
512         return;
513
514     // Check the fee
515     int64_t fee = (int64_t ) (ui->fee->text().toDouble() * COIN);
516     int64_t minFee = MIN_TX_FEE * (1 + (int64_t) transactionSize / 1000);
517     if(fee < minFee)
518     {
519         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);
520         if(ret != QMessageBox::Yes)
521             return;
522     }
523     else if(fee > minFee)
524     {
525         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);
526         if(ret != QMessageBox::Yes)
527             return;
528     }
529
530     // Decode the raw transaction
531     std::vector<unsigned char> txData(ParseHex(ui->signedTransaction->text().toStdString()));
532     CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION);
533     CTransaction tx;
534     try
535     {
536         ssData >> tx;
537     }
538     catch(std::exception &e)
539     {
540         return;
541     }
542     uint256 txHash = tx.GetHash();
543
544     // Check if the transaction is already in the blockchain
545     CTransaction existingTx;
546     uint256 blockHash = 0;
547     if(GetTransaction(txHash, existingTx, blockHash))
548     {
549         if(blockHash != 0)
550             return;
551     }
552
553     // Send the transaction to the local node
554     CTxDB txdb("r");
555     if(!tx.AcceptToMemoryPool(txdb, false))
556     return;
557     SyncWithWallets(tx, NULL, true);
558     //(CInv(MSG_TX, txHash), tx);
559     RelayTransaction(tx, txHash);
560 }
561
562 MultisigInputEntry * MultisigDialog::addInput()
563 {
564     MultisigInputEntry *entry = new MultisigInputEntry(this);
565
566     entry->setModel(model);
567     ui->inputs->addWidget(entry);
568     connect(entry, SIGNAL(removeEntry(MultisigInputEntry *)), this, SLOT(removeEntry(MultisigInputEntry *)));
569     connect(entry, SIGNAL(updateAmount()), this, SLOT(updateAmounts()));
570     updateRemoveEnabled();
571     entry->clear();
572     ui->scrollAreaWidgetContents_2->resize(ui->scrollAreaWidgetContents_2->sizeHint());
573     QScrollBar *bar = ui->scrollArea_2->verticalScrollBar();
574     if(bar)
575         bar->setSliderPosition(bar->maximum());
576
577     return entry;
578 }
579
580 void MultisigDialog::removeEntry(MultisigInputEntry *entry)
581 {
582     delete entry;
583     updateRemoveEnabled();
584 }
585
586 SendCoinsEntry * MultisigDialog::addOutput()
587 {
588     SendCoinsEntry *entry = new SendCoinsEntry(this);
589
590     entry->setModel(model);
591     ui->outputs->addWidget(entry);
592     connect(entry, SIGNAL(removeEntry(SendCoinsEntry *)), this, SLOT(removeEntry(SendCoinsEntry *)));
593     connect(entry, SIGNAL(payAmountChanged()), this, SLOT(updateAmounts()));
594     updateRemoveEnabled();
595     entry->clear();
596     ui->scrollAreaWidgetContents_3->resize(ui->scrollAreaWidgetContents_3->sizeHint());
597     QScrollBar *bar = ui->scrollArea_3->verticalScrollBar();
598     if(bar)
599         bar->setSliderPosition(bar->maximum());
600
601     return entry;
602 }
603
604 void MultisigDialog::removeEntry(SendCoinsEntry *entry)
605 {
606     delete entry;
607     updateRemoveEnabled();
608 }
609
610 void MultisigDialog::updateAmounts()
611 {
612     // Update inputs amount
613     int64_t inputsAmount = 0;
614     for(int i = 0; i < ui->inputs->count(); i++)
615     {
616         MultisigInputEntry *entry = qobject_cast<MultisigInputEntry *>(ui->inputs->itemAt(i)->widget());
617         if(entry)
618             inputsAmount += entry->getAmount();
619     }
620     QString inputsAmountStr;
621     inputsAmountStr.sprintf("%.6f", (double) inputsAmount / COIN);
622     ui->inputsAmount->setText(inputsAmountStr);
623
624     // Update outputs amount
625     int64_t outputsAmount = 0;
626     for(int i = 0; i < ui->outputs->count(); i++)
627     {
628         SendCoinsEntry *entry = qobject_cast<SendCoinsEntry *>(ui->outputs->itemAt(i)->widget());
629         if(entry)
630             outputsAmount += entry->getValue().amount;
631     }
632     QString outputsAmountStr;
633     outputsAmountStr.sprintf("%.6f", (double) outputsAmount / COIN);
634     ui->outputsAmount->setText(outputsAmountStr);
635
636     // Update Fee amount
637     int64_t fee = inputsAmount - outputsAmount;
638     QString feeStr;
639     feeStr.sprintf("%.6f", (double) fee / COIN);
640     ui->fee->setText(feeStr);
641 }