package org.ripple.power.ui.btc; import java.awt.Color; import java.awt.Dialog; import java.awt.Dimension; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.List; import javax.swing.JDialog; import javax.swing.JMenuBar; import org.ripple.power.config.LSystem; import org.ripple.power.helper.HelperWindow; import org.ripple.power.txns.btc.AddressFormatException; import org.ripple.power.txns.btc.BTCLoader; import org.ripple.power.txns.btc.BlockStoreException; import org.ripple.power.txns.btc.BlockStoreListener; import org.ripple.power.txns.btc.ConnectionListener; import org.ripple.power.txns.btc.DumpedPrivateKey; import org.ripple.power.txns.btc.ECKey; import org.ripple.power.txns.btc.InventoryItem; import org.ripple.power.txns.btc.InventoryMessage; import org.ripple.power.txns.btc.Message; import org.ripple.power.txns.btc.Peer; import org.ripple.power.txns.btc.SendTransaction; import org.ripple.power.txns.btc.StoredHeader; import org.ripple.power.ui.UIRes; import org.ripple.power.ui.view.Menus; import org.ripple.power.ui.view.log.ErrorLog; import org.ripple.power.utils.SwingUtils; public final class BitcoinWalletDialog extends JDialog implements ActionListener, ConnectionListener, BlockStoreListener { /** * */ private static final long serialVersionUID = 1L; private boolean synchronizingTitle = false; private boolean txBroadcastDone = false; private boolean rescanChain = false; private final TransactionPanel transactionPanel; public static BitcoinWalletDialog showDialog(String text, Window parent) { BitcoinWalletDialog dialog = new BitcoinWalletDialog(text, parent); dialog.pack(); dialog.setLocationRelativeTo(parent); dialog.setVisible(true); return dialog; } public BitcoinWalletDialog(String text, Window parent) { super(LSystem.applicationMain, text, Dialog.ModalityType.DOCUMENT_MODAL); addWindowListener(HelperWindow.get()); setIconImage(UIRes.getIcon()); setResizable(false); int frameWidth = 900; int frameHeight = 580; if (LSystem.applicationMain != null) { frameWidth = LSystem.applicationMain.getWidth() - 150; frameHeight = LSystem.applicationMain.getHeight() - 150; } setPreferredSize(new Dimension(frameWidth, frameHeight)); JMenuBar menuBar = new JMenuBar(); menuBar.setOpaque(true); menuBar.setBackground(new Color(230, 230, 230)); menuBar.add(new Menus(this, "File", new String[] { "Exit", "exit" })); menuBar.add(new Menus(this, "View", new String[] { "Receive Addresses", "view receive" }, new String[] { "Send Addresses", "view send" })); menuBar.add(new Menus(this, "Actions", new String[] { "Send Coins", "send coins" }, new String[] { "Sign Message", "sign message" }, new String[] { "Verify Message", "verify message" })); menuBar.add(new Menus(this, "Tools", new String[] { "Export Keys", "export keys" }, new String[] { "Import Keys", "import keys" }, new String[] { "Rescan Block Chain", "rescan" })); setJMenuBar(menuBar); transactionPanel = new TransactionPanel(this); setContentPane(transactionPanel); if (BTCLoader.networkChainHeight > BTCLoader.blockStore .getChainHeight()) { setTitle("Bitcoin Wallet - Synchronizing with network"); synchronizingTitle = true; } addWindowListener(new ApplicationWindowListener()); BTCLoader.networkHandler.addListener(this); BTCLoader.databaseHandler.addListener(this); } @Override public void addChainBlock(StoredHeader blockHeader) { LSystem.invokeLater(new Runnable() { @Override public void run() { transactionPanel.statusChanged(); if (synchronizingTitle && !rescanChain && BTCLoader.networkChainHeight <= BTCLoader.blockStore .getChainHeight()) { synchronizingTitle = false; setTitle("Bitcoin Wallet"); } } }); } @Override public void txUpdated() { LSystem.invokeLater(new Runnable() { @Override public void run() { transactionPanel.walletChanged(); } }); } @Override public void rescanCompleted() { LSystem.invokeLater(new Runnable() { @Override public void run() { rescanChain = false; transactionPanel.statusChanged(); if (synchronizingTitle && BTCLoader.networkChainHeight <= BTCLoader.blockStore .getChainHeight()) { synchronizingTitle = false; setTitle("Bitcoin Wallet"); } } }); } @Override public void actionPerformed(ActionEvent ae) { try { String action = ae.getActionCommand(); switch (action) { case "exit": exit(); break; case "view receive": ReceiveAddressDialog.showDialog(this); transactionPanel.statusChanged(); break; case "view send": SendAddressDialog.showDialog(this); transactionPanel.statusChanged(); break; case "send coins": SendDialog.showDialog(this); break; case "sign message": if (BTCLoader.keys.isEmpty()) { UIRes.showErrorMessage(this,"Error", "There are no keys defined"); } else { SignDialog.showDialog(this); } break; case "verify message": VerifyDialog.showDialog(this); break; case "export keys": exportDefPrivateKeys(); break; case "import keys": importDefPrivateKeys(); break; case "rescan": rescan(); break; } } catch (IOException exc) { ErrorLog.get().logException("Unable to process key file", exc); } catch (AddressFormatException exc) { ErrorLog.get().logException("Key format is not valid", exc); } catch (BlockStoreException exc) { ErrorLog.get().logException("Unable to perform database operation", exc); } catch (Exception exc) { ErrorLog.get().logException("Exception while processing action event", exc); } } private void exportDefPrivateKeys() throws IOException { StringBuilder keyText = new StringBuilder(256); File keyFile = new File(LSystem.getBitcionDirectory() + LSystem.FS + "BTCWallet.keys"); if (keyFile.exists()) { keyFile.delete(); } try (BufferedWriter out = new BufferedWriter(new FileWriter(keyFile))) { for (ECKey key : BTCLoader.keys) { String address = key.toAddress().toString(); DumpedPrivateKey dumpedKey = key.getPrivKeyEncoded(); keyText.append("Label:"); keyText.append(key.getLabel()); keyText.append("\nTime:"); keyText.append(Long.toString(key.getCreationTime())); keyText.append("\nAddress:"); keyText.append(address); keyText.append("\nPrivate:"); keyText.append(dumpedKey.toString()); keyText.append("\n\n"); out.write(keyText.toString()); keyText.delete(0, keyText.length()); } } UIRes.showInfoMessage(this,"Keys Exported", "Keys exported to BTCWallet.keys" ); } private void importDefPrivateKeys() throws IOException, AddressFormatException, BlockStoreException { File keyFile = new File(LSystem.getBitcionDirectory() + LSystem.FS + "BTCWallet.keys"); if (!keyFile.exists()) { UIRes.showErrorMessage(this,"Error", "BTCWallet.keys does not exist"); return; } try (BufferedReader in = new BufferedReader(new FileReader(keyFile))) { String line; String importedLabel = ""; String importedTime = ""; String importedAddress = ""; String encodedPrivateKey = ""; boolean foundKey = false; while ((line = in.readLine()) != null) { line = line.trim(); if (line.length() == 0 || line.charAt(0) == '#') { continue; } int sep = line.indexOf(':'); if (sep < 1 || line.length() == sep + 1) { continue; } String keyword = line.substring(0, sep); String value = line.substring(sep + 1); switch (keyword) { case "Label": importedLabel = value; break; case "Time": importedTime = value; break; case "Address": importedAddress = value; break; case "Private": encodedPrivateKey = value; foundKey = true; break; } if (foundKey) { DumpedPrivateKey dumpedKey = new DumpedPrivateKey( encodedPrivateKey); ECKey key = dumpedKey.getKey(); if (importedAddress.equals(key.toAddress().toString())) { key.setLabel(importedLabel); key.setCreationTime(Long.parseLong(importedTime)); if (!BTCLoader.keys.contains(key)) { BTCLoader.blockStore.storeKey(key); synchronized (BTCLoader.lock) { boolean added = false; for (int i = 0; i < BTCLoader.keys.size(); i++) { if (BTCLoader.keys.get(i).getLabel() .compareToIgnoreCase(importedLabel) > 0) { BTCLoader.keys.add(i, key); added = true; break; } } if (!added) BTCLoader.keys.add(key); BTCLoader.bloomFilter.insert(key.getPubKey()); BTCLoader.bloomFilter.insert(key .getPubKeyHash()); } } } else { UIRes.showErrorMessage( this, "Error", String.format( "Address %s does not match imported private key", importedAddress)); } foundKey = false; importedLabel = ""; importedTime = ""; importedAddress = ""; encodedPrivateKey = ""; } } } UIRes.showInfoMessage(this,"Keys Imported", "Keys imported from BTCWallet.keys"); } private void rescan() throws BlockStoreException { long creationTime = System.currentTimeMillis() / 1000; for (ECKey key : BTCLoader.keys) { creationTime = Math.min(creationTime, key.getCreationTime()); } synchronizingTitle = true; rescanChain = true; setTitle("Bitcoin Wallet - Synchronizing with network"); BTCLoader.blockStore.deleteTransactions(creationTime); transactionPanel.walletChanged(); BTCLoader.databaseHandler.rescanChain(creationTime); } private void exit() throws IOException { BTCLoader.shutdown(); SwingUtils.close(this); } private class ApplicationWindowListener extends WindowAdapter { public ApplicationWindowListener() { } @Override public void windowIconified(WindowEvent we) { } @Override public void windowDeiconified(WindowEvent we) { } @Override public void windowClosing(WindowEvent we) { try { exit(); } catch (Exception exc) { ErrorLog.get().logException( "Exception while closing application window", exc); } } } @Override public void connectionStarted(Peer peer, int count) { if (!synchronizingTitle && BTCLoader.networkChainHeight > BTCLoader.blockStore .getChainHeight()) { synchronizingTitle = true; LSystem.invokeLater(new Runnable() { @Override public void run() { setTitle("Bitcoin Wallet - Synchronizing with network"); } }); } if (!txBroadcastDone) { txBroadcastDone = true; try { List<SendTransaction> sendList = BTCLoader.blockStore .getSendTxList(); if (!sendList.isEmpty()) { List<InventoryItem> invList = new ArrayList<InventoryItem>( sendList.size()); for (SendTransaction sendTx : sendList) { int depth = BTCLoader.blockStore.getTxDepth(sendTx .getTxHash()); if (depth == 0) invList.add(new InventoryItem(InventoryItem.INV_TX, sendTx.getTxHash())); } if (!invList.isEmpty()) { Message invMsg = InventoryMessage .buildInventoryMessage(peer, invList); BTCLoader.networkHandler.sendMessage(invMsg); BTCLoader.info(String.format( "Pending transaction inventory sent to %s", peer.getAddress().toString())); } } } catch (BlockStoreException exc) { ErrorLog.get().logException("Unable to get send transaction list", exc); } } } @Override public void connectionEnded(Peer peer, int count) { } }