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