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