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