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