Craig Raw
4 years ago
6 changed files with 340 additions and 5 deletions
@ -1 +1 @@ |
|||
Subproject commit 6b4b2529c803581b13e1604acd230dff24c839b4 |
|||
Subproject commit ee49ddd94bdfec1e87d334f17833a63382958790 |
@ -0,0 +1,286 @@ |
|||
package com.sparrowwallet.sparrow.control; |
|||
|
|||
import com.google.common.eventbus.Subscribe; |
|||
import com.sparrowwallet.drongo.SecureString; |
|||
import com.sparrowwallet.drongo.address.Address; |
|||
import com.sparrowwallet.drongo.address.InvalidAddressException; |
|||
import com.sparrowwallet.drongo.crypto.ECKey; |
|||
import com.sparrowwallet.drongo.wallet.Keystore; |
|||
import com.sparrowwallet.drongo.wallet.Wallet; |
|||
import com.sparrowwallet.drongo.wallet.WalletNode; |
|||
import com.sparrowwallet.sparrow.AppController; |
|||
import com.sparrowwallet.sparrow.EventManager; |
|||
import com.sparrowwallet.sparrow.event.OpenWalletsEvent; |
|||
import com.sparrowwallet.sparrow.event.RequestOpenWalletsEvent; |
|||
import com.sparrowwallet.sparrow.event.StorageEvent; |
|||
import com.sparrowwallet.sparrow.event.TimedEvent; |
|||
import com.sparrowwallet.sparrow.io.Storage; |
|||
import javafx.application.Platform; |
|||
import javafx.scene.control.*; |
|||
import javafx.scene.image.Image; |
|||
import javafx.scene.image.ImageView; |
|||
import javafx.scene.layout.VBox; |
|||
import org.controlsfx.validation.ValidationResult; |
|||
import org.controlsfx.validation.ValidationSupport; |
|||
import org.controlsfx.validation.decoration.StyleClassValidationDecoration; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
import tornadofx.control.Field; |
|||
import tornadofx.control.Fieldset; |
|||
import tornadofx.control.Form; |
|||
|
|||
import java.security.SignatureException; |
|||
import java.util.Arrays; |
|||
import java.util.Optional; |
|||
|
|||
import static com.sparrowwallet.sparrow.AppController.setStageIcon; |
|||
|
|||
public class MessageSignDialog extends Dialog<ButtonBar.ButtonData> { |
|||
private static final Logger log = LoggerFactory.getLogger(MessageSignDialog.class); |
|||
|
|||
private final TextField address; |
|||
private final TextArea message; |
|||
private final TextArea signature; |
|||
private final Wallet wallet; |
|||
private WalletNode walletNode; |
|||
|
|||
/** |
|||
* Verification only constructor |
|||
*/ |
|||
public MessageSignDialog() { |
|||
this(null, null); |
|||
} |
|||
|
|||
/** |
|||
* Sign and verify with user entered address |
|||
* |
|||
* @param wallet Wallet to sign with |
|||
*/ |
|||
public MessageSignDialog(Wallet wallet) { |
|||
this(wallet, null); |
|||
} |
|||
|
|||
/** |
|||
* Sign and verify with preset address |
|||
* |
|||
* @param wallet Wallet to sign with |
|||
* @param walletNode Wallet node to derive address from |
|||
*/ |
|||
public MessageSignDialog(Wallet wallet, WalletNode walletNode) { |
|||
if(wallet != null) { |
|||
if(wallet.getKeystores().size() != 1) { |
|||
throw new IllegalArgumentException("Cannot sign messages using a wallet with multiple keystores - a single key is required"); |
|||
} |
|||
if(!wallet.getKeystores().get(0).hasSeed()) { |
|||
throw new IllegalArgumentException("Cannot sign messages using a wallet without a seed"); |
|||
} |
|||
} |
|||
|
|||
this.wallet = wallet; |
|||
this.walletNode = walletNode; |
|||
|
|||
final DialogPane dialogPane = getDialogPane(); |
|||
dialogPane.getStylesheets().add(AppController.class.getResource("general.css").toExternalForm()); |
|||
AppController.setStageIcon(dialogPane.getScene().getWindow()); |
|||
dialogPane.setHeaderText(wallet == null ? "Verify Message" : "Sign/Verify Message"); |
|||
|
|||
Image image = new Image("image/seed.png", 50, 50, false, false); |
|||
if (!image.isError()) { |
|||
ImageView imageView = new ImageView(); |
|||
imageView.setSmooth(false); |
|||
imageView.setImage(image); |
|||
dialogPane.setGraphic(imageView); |
|||
} |
|||
|
|||
VBox vBox = new VBox(); |
|||
vBox.setSpacing(20); |
|||
|
|||
Form form = new Form(); |
|||
Fieldset fieldset = new Fieldset(); |
|||
fieldset.setText(""); |
|||
|
|||
Field addressField = new Field(); |
|||
addressField.setText("Address:"); |
|||
address = new TextField(); |
|||
address.getStyleClass().add("id"); |
|||
address.setEditable(walletNode == null); |
|||
addressField.getInputs().add(address); |
|||
|
|||
if(walletNode != null) { |
|||
address.setText(wallet.getAddress(walletNode).toString()); |
|||
} |
|||
|
|||
Field messageField = new Field(); |
|||
messageField.setText("Message:"); |
|||
message = new TextArea(); |
|||
message.setWrapText(true); |
|||
message.setPrefRowCount(10); |
|||
message.setStyle("-fx-pref-height: 180px"); |
|||
messageField.getInputs().add(message); |
|||
|
|||
Field signatureField = new Field(); |
|||
signatureField.setText("Signature:"); |
|||
signature = new TextArea(); |
|||
signature.getStyleClass().add("id"); |
|||
signature.setPrefRowCount(2); |
|||
signature.setStyle("-fx-pref-height: 60px"); |
|||
signature.setWrapText(true); |
|||
signatureField.getInputs().add(signature); |
|||
|
|||
fieldset.getChildren().addAll(addressField, messageField, signatureField); |
|||
form.getChildren().add(fieldset); |
|||
dialogPane.setContent(form); |
|||
|
|||
ButtonType signButtonType = new javafx.scene.control.ButtonType("Sign", ButtonBar.ButtonData.BACK_PREVIOUS); |
|||
ButtonType verifyButtonType = new javafx.scene.control.ButtonType("Verify", ButtonBar.ButtonData.NEXT_FORWARD); |
|||
ButtonType doneButtonType = new javafx.scene.control.ButtonType("Done", ButtonBar.ButtonData.OK_DONE); |
|||
dialogPane.getButtonTypes().addAll(signButtonType, verifyButtonType, doneButtonType); |
|||
|
|||
Button signButton = (Button)dialogPane.lookupButton(signButtonType); |
|||
signButton.setDisable(wallet == null); |
|||
signButton.setOnAction(event -> { |
|||
signMessage(); |
|||
}); |
|||
|
|||
Button verifyButton = (Button)dialogPane.lookupButton(verifyButtonType); |
|||
verifyButton.setDefaultButton(false); |
|||
verifyButton.setOnAction(event -> { |
|||
verifyMessage(); |
|||
}); |
|||
|
|||
boolean validAddress = isValidAddress(); |
|||
signButton.setDisable(!validAddress || (wallet == null)); |
|||
verifyButton.setDisable(!validAddress); |
|||
|
|||
ValidationSupport validationSupport = new ValidationSupport(); |
|||
Platform.runLater(() -> { |
|||
validationSupport.registerValidator(address, (Control c, String newValue) -> ValidationResult.fromErrorIf(c, "Invalid address", !isValidAddress())); |
|||
validationSupport.setValidationDecorator(new StyleClassValidationDecoration()); |
|||
}); |
|||
|
|||
address.textProperty().addListener((observable, oldValue, newValue) -> { |
|||
boolean valid = isValidAddress(); |
|||
signButton.setDisable(!valid || (wallet == null)); |
|||
verifyButton.setDisable(!valid); |
|||
|
|||
if(valid && wallet != null) { |
|||
try { |
|||
Address address = getAddress(); |
|||
setWalletNodeFromAddress(wallet, address); |
|||
} catch(InvalidAddressException e) { |
|||
//can't happen
|
|||
} |
|||
} |
|||
}); |
|||
|
|||
EventManager.get().register(this); |
|||
setOnCloseRequest(event -> { |
|||
if(ButtonBar.ButtonData.APPLY.equals(getResult())) { |
|||
event.consume(); |
|||
return; |
|||
} |
|||
|
|||
EventManager.get().unregister(this); |
|||
}); |
|||
|
|||
setResultConverter(dialogButton -> dialogButton == signButtonType || dialogButton == verifyButtonType ? ButtonBar.ButtonData.APPLY : ButtonBar.ButtonData.OK_DONE); |
|||
} |
|||
|
|||
private Address getAddress()throws InvalidAddressException { |
|||
return Address.fromString(address.getText()); |
|||
} |
|||
|
|||
private boolean isValidAddress() { |
|||
try { |
|||
getAddress(); |
|||
return true; |
|||
} catch (InvalidAddressException e) { |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
private void setWalletNodeFromAddress(Wallet wallet, Address address) { |
|||
walletNode = wallet.getWalletAddresses().get(address); |
|||
} |
|||
|
|||
private void signMessage() { |
|||
if(walletNode == null) { |
|||
AppController.showErrorDialog("Address not in wallet", "The provided address is not present in the currently selected wallet."); |
|||
return; |
|||
} |
|||
|
|||
if(wallet.isEncrypted()) { |
|||
EventManager.get().post(new RequestOpenWalletsEvent()); |
|||
} else { |
|||
signUnencryptedKeystore(wallet); |
|||
} |
|||
} |
|||
|
|||
private void signUnencryptedKeystore(Wallet decryptedWallet) { |
|||
try { |
|||
//Note we can expect a single keystore due to the check above
|
|||
Keystore keystore = decryptedWallet.getKeystores().get(0); |
|||
ECKey privKey = keystore.getKey(walletNode); |
|||
String signatureText = privKey.signMessage(message.getText(), null); |
|||
signature.clear(); |
|||
signature.appendText(signatureText); |
|||
} catch(Exception e) { |
|||
log.error("Could not sign message", e); |
|||
AppController.showErrorDialog("Could not sign message", e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
private void verifyMessage() { |
|||
try { |
|||
//Find ECKey from message and signature
|
|||
//http://www.secg.org/download/aid-780/sec1-v2.pdf section 4.1.6
|
|||
|
|||
ECKey signedMessageKey = ECKey.signedMessageToKey(message.getText(), signature.getText()); |
|||
Address address = getAddress(); |
|||
|
|||
byte[] pubKeyHash = address.getOutputScriptData(); |
|||
byte[] signedKeyHash = signedMessageKey.getPubKeyHash(); |
|||
|
|||
if(!Arrays.equals(pubKeyHash, signedKeyHash)) { |
|||
throw new SignatureException("Address pubkey hash did not match signed pubkey hash"); |
|||
} |
|||
|
|||
Alert alert = new Alert(Alert.AlertType.INFORMATION); |
|||
setStageIcon(alert.getDialogPane().getScene().getWindow()); |
|||
alert.setTitle("Verification Succeeded"); |
|||
alert.setHeaderText("Verification Succeeded"); |
|||
alert.setContentText("The signature verified against the message."); |
|||
alert.showAndWait(); |
|||
} catch(SignatureException e) { |
|||
AppController.showErrorDialog("Verification failed", "The provided signature did not match the message for this address."); |
|||
} catch(Exception e) { |
|||
log.error("Could not verify message", e); |
|||
AppController.showErrorDialog("Could not verify message", e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
@Subscribe |
|||
public void openWallets(OpenWalletsEvent event) { |
|||
Storage storage = event.getStorage(wallet); |
|||
if(storage == null) { |
|||
throw new IllegalStateException("Wallet " + wallet + " without Storage"); |
|||
} |
|||
|
|||
WalletPasswordDialog dlg = new WalletPasswordDialog(WalletPasswordDialog.PasswordRequirement.LOAD); |
|||
Optional<SecureString> password = dlg.showAndWait(); |
|||
if(password.isPresent()) { |
|||
Storage.DecryptWalletService decryptWalletService = new Storage.DecryptWalletService(wallet.copy(), password.get()); |
|||
decryptWalletService.setOnSucceeded(workerStateEvent -> { |
|||
EventManager.get().post(new StorageEvent(storage.getWalletFile(), TimedEvent.Action.END, "Done")); |
|||
Wallet decryptedWallet = decryptWalletService.getValue(); |
|||
signUnencryptedKeystore(decryptedWallet); |
|||
}); |
|||
decryptWalletService.setOnFailed(workerStateEvent -> { |
|||
EventManager.get().post(new StorageEvent(storage.getWalletFile(), TimedEvent.Action.END, "Failed")); |
|||
AppController.showErrorDialog("Incorrect Password", decryptWalletService.getException().getMessage()); |
|||
}); |
|||
EventManager.get().post(new StorageEvent(storage.getWalletFile(), TimedEvent.Action.START, "Decrypting wallet...")); |
|||
decryptWalletService.start(); |
|||
} |
|||
} |
|||
} |
Loading…
Reference in new issue