/**
* Copyright 2013 multibit.org
*
* Licensed under the MIT license (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://opensource.org/licenses/mit-license.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.multibit.viewsystem.swing.action;
import com.google.bitcoin.core.Address;
import com.google.bitcoin.core.ECKey;
import com.google.bitcoin.core.NetworkParameters;
import com.google.bitcoin.core.Wallet;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import org.multibit.controller.bitcoin.BitcoinController;
import org.multibit.message.Message;
import org.multibit.message.MessageManager;
import org.multibit.model.bitcoin.WalletBusyListener;
import org.multibit.model.bitcoin.WalletData;
import org.multibit.viewsystem.swing.view.panels.CheckPrivateKeysPanel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.crypto.params.KeyParameter;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.math.BigInteger;
import java.nio.CharBuffer;
import java.util.List;
/**
* This {@link javax.swing.Action} checks private keys against the bitcoin addresses
*/
public class CheckPrivateKeysSubmitAction extends MultiBitSubmitAction implements WalletBusyListener {
private static final Logger log = LoggerFactory.getLogger(CheckPrivateKeysSubmitAction.class);
private static final long serialVersionUID = 1923333087598757765L;
public static final String MESSAGE_WINDOW_SEPARATOR = "----------------------------------------------------------------";
private CheckPrivateKeysPanel checkPrivateKeysPanel;
/**
* Creates a new {@link org.multibit.viewsystem.swing.action.CheckPrivateKeysSubmitAction}.
*/
public CheckPrivateKeysSubmitAction(BitcoinController bitcoinController,
CheckPrivateKeysPanel checkPrivateKeysPanel, ImageIcon icon) {
super(bitcoinController, "checkPrivateKeysAction.text", "showCheckPrivateKeysAction.tooltip", "showCheckPrivateKeysAction.mnemonicKey", icon);
this.checkPrivateKeysPanel = checkPrivateKeysPanel;
// This action is a WalletBusyListener.
super.bitcoinController.registerWalletBusyListener(this);
walletBusyChange(super.bitcoinController.getModel().getActivePerWalletModelData().isBusy());
}
/**
* Check the private keys against the bitcoin addresses
*/
@Override
public void actionPerformed(ActionEvent event) {
if (abort()) {
return;
}
if (checkPrivateKeysPanel == null) {
return;
}
CharSequence walletPassword = null;
if (checkPrivateKeysPanel.getWalletPasswordField() != null) {
walletPassword = CharBuffer.wrap(checkPrivateKeysPanel.getWalletPasswordField().getPassword());
if (bitcoinController.getModel().getActiveWallet().isEncrypted()) {
if (walletPassword.length() == 0) {
checkPrivateKeysPanel.setMessageText1(controller.getLocaliser().getString(
"showExportPrivateKeysAction.youMustEnterTheWalletPassword"));
checkPrivateKeysPanel.setMessageText2(" ");
return;
}
if (!bitcoinController.getModel().getActiveWallet().checkPassword(walletPassword)) {
// The password supplied is incorrect.
checkPrivateKeysPanel.setMessageText1(controller.getLocaliser().getString(
"createNewReceivingAddressSubmitAction.passwordIsIncorrect"));
checkPrivateKeysPanel.setMessageText2(" ");
return;
}
}
}
// Check the private keys match the bitcoin addresses
try {
checkPrivateKeysMatchAddresses(bitcoinController.getModel().getActivePerWalletModelData(), walletPassword);
} catch (PrivateKeysException pke) {
// Error messaging is handled in the method itself
pke.printStackTrace();
}
}
@Override
public void walletBusyChange(boolean newWalletIsBusy) {
// Update the enable status of the action to match the wallet busy
// status.
if (super.bitcoinController.getModel().getActivePerWalletModelData().isBusy()) {
// Wallet is busy with another operation that may change the private
// keys - Action is disabled.
putValue(
SHORT_DESCRIPTION,
controller.getLocaliser().getString(
"multiBitSubmitAction.walletIsBusy",
new Object[]{controller.getLocaliser().getString(
this.bitcoinController.getModel().getActivePerWalletModelData().getBusyTaskKey())}
)
);
setEnabled(false);
} else {
// Enable
putValue(SHORT_DESCRIPTION, controller.getLocaliser().getString("showCheckPrivateKeysAction.tooltip"));
setEnabled(true);
}
}
/**
* Check that the private key in the wallet file correctly creates the bitcoin address
*
* @param perWalletModelData the wallet data to check the private keys for
* @param password the wallet password
* @return badAddresses A list of the bad addresses i.e. the addresses for which the private key does not match the bitcoin address.
* If this is empty then all private keys are present and match the address.
*/
private List<String> checkPrivateKeysMatchAddresses(WalletData perWalletModelData, CharSequence password) throws PrivateKeysException {
if (perWalletModelData == null || perWalletModelData.getWallet() == null) {
throw new PrivateKeysException("No wallet specified");
}
Message separatorMessage = new Message(MESSAGE_WINDOW_SEPARATOR);
separatorMessage.setShowInStatusBar(false);
boolean allKeysAreOk = true;
List<String> badAddresses = Lists.newArrayList();
try {
Wallet walletToCheck = perWalletModelData.getWallet();
List<ECKey> keysToCheck = walletToCheck.getKeys();
// Derive keyParameter if wallet is encrypted
KeyParameter keyParameter = null;
if (password != null && !password.equals("") && walletToCheck.isEncrypted()) {
keyParameter = walletToCheck.getKeyCrypter().deriveKey(password);
}
for (ECKey loopECKey : keysToCheck) {
Address originalAddress = loopECKey.toAddress(NetworkParameters.fromID(NetworkParameters.ID_MAINNET));
try {
// Decrypt the ECKey if it is encrypted
if (loopECKey.isEncrypted()) {
loopECKey = loopECKey.decrypt(walletToCheck.getKeyCrypter(), keyParameter);
}
byte[] privateKeyBytes = loopECKey.getPrivKeyBytes();
if (privateKeyBytes == null) {
// The private key in the ecKey is missing
allKeysAreOk = false;
badAddresses.add(originalAddress.toString());
} else {
// Create an ECKey with just the private key bytes, it creates the public key - the address should be the same
ECKey rebornKey = new ECKey(new BigInteger(1, privateKeyBytes), null, loopECKey.isCompressed());
Address rebornAddress = rebornKey.toAddress(NetworkParameters.fromID(NetworkParameters.ID_MAINNET));
if (!rebornAddress.toString().equals(originalAddress.toString())) {
// The private key in the ecKey does not match the address - private key could be damaged or missing
allKeysAreOk = false;
badAddresses.add(originalAddress.toString());
}
}
} catch (Exception e) {
e.printStackTrace();
allKeysAreOk = false;
badAddresses.add(originalAddress.toString());
}
}
MessageManager.INSTANCE.addMessage(separatorMessage);
if (allKeysAreOk) {
// No problems
String messageText = super.bitcoinController.getLocaliser().getString("checkPrivateKeysSubmitAction.ok", new String[]{perWalletModelData.getWalletDescription()});
checkPrivateKeysPanel.setMessageText1(messageText);
checkPrivateKeysPanel.setMessageText2("");
Message message = new Message(messageText);
message.setShowInStatusBar(false);
MessageManager.INSTANCE.addMessage(message);
} else {
// Some private keys are missing or damaged
String messageText = super.bitcoinController.getLocaliser().getString("checkPrivateKeysSubmitAction.fail", new String[]{perWalletModelData.getWalletDescription(), "" + badAddresses.size()});
checkPrivateKeysPanel.setMessageText1(messageText);
checkPrivateKeysPanel.setMessageText2(super.bitcoinController.getLocaliser().getString("checkPrivateKeysSubmitAction.details"));
Message message = new Message(messageText);
message.setShowInStatusBar(false);
MessageManager.INSTANCE.addMessage(message);
message = new Message(super.bitcoinController.getLocaliser().getString("checkPrivateKeysSubmitAction.badAddresses", new String[]{Joiner.on(", ").join(badAddresses)}));
message.setShowInStatusBar(false);
MessageManager.INSTANCE.addMessage(message);
message = new Message(super.bitcoinController.getLocaliser().getString("checkPrivateKeysSubmitAction.doNotSend"));
message.setShowInStatusBar(false);
MessageManager.INSTANCE.addMessage(message);
}
} catch (Exception e) {
String messageText1 = super.bitcoinController.getLocaliser().getString("checkPrivateKeysSubmitAction.didNotComplete");
String messageText2 = super.bitcoinController.getLocaliser().getString("deleteWalletConfirmDialog.walletDeleteError2", new String[]{e.getClass().getCanonicalName() + " " + e.getMessage()});
checkPrivateKeysPanel.setMessageText1(messageText1);
checkPrivateKeysPanel.setMessageText2(messageText2);
Message message = new Message(messageText1);
message.setShowInStatusBar(false);
MessageManager.INSTANCE.addMessage(message);
message = new Message(messageText2);
message.setShowInStatusBar(false);
MessageManager.INSTANCE.addMessage(message);
throw new PrivateKeysException("The check of the private keys failed", e);
} finally {
MessageManager.INSTANCE.addMessage(separatorMessage);
}
return badAddresses;
}
}