From 425e476f202273dcc4e721a3aa92defca31196f4 Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Fri, 7 May 2021 16:24:28 +0200 Subject: [PATCH] add aopp proof of address ownership support --- .../sparrowwallet/sparrow/AppController.java | 9 +- .../sparrowwallet/sparrow/AppServices.java | 61 ++++--- .../sparrow/control/MessageSignDialog.java | 128 ++++++++++---- .../sparrow/event/ReceiveActionEvent.java | 13 +- .../sparrow/event/ReceiveProofEvent.java | 22 +++ .../com/sparrowwallet/sparrow/net/Aopp.java | 167 ++++++++++++++++++ .../sparrow/net/FeeRatesSource.java | 17 +- .../sparrow/wallet/ReceiveController.java | 42 ++++- .../sparrow/wallet/WalletController.java | 2 +- 9 files changed, 372 insertions(+), 89 deletions(-) create mode 100644 src/main/java/com/sparrowwallet/sparrow/event/ReceiveProofEvent.java create mode 100644 src/main/java/com/sparrowwallet/sparrow/net/Aopp.java diff --git a/src/main/java/com/sparrowwallet/sparrow/AppController.java b/src/main/java/com/sparrowwallet/sparrow/AppController.java index e5aa6029..677737c5 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppController.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppController.java @@ -315,7 +315,7 @@ public class AppController implements Initializable { } } - public void showDocumentation(ActionEvent event) throws IOException { + public void showDocumentation(ActionEvent event) { AppServices.get().getApplication().getHostServices().showDocument("https://sparrowwallet.com/docs"); } @@ -328,7 +328,7 @@ public class AppController implements Initializable { } } - public void submitBugReport(ActionEvent event) throws IOException { + public void submitBugReport(ActionEvent event) { AppServices.get().getApplication().getHostServices().showDocument("https://sparrowwallet.com/submitbugreport"); } @@ -1800,4 +1800,9 @@ public class AppController implements Initializable { public void sendAction(SendActionEvent event) { selectTab(event.getWallet()); } + + @Subscribe + public void recieveAction(ReceiveActionEvent event) { + selectTab(event.getWallet()); + } } \ No newline at end of file diff --git a/src/main/java/com/sparrowwallet/sparrow/AppServices.java b/src/main/java/com/sparrowwallet/sparrow/AppServices.java index cb441bce..2883c234 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppServices.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppServices.java @@ -4,6 +4,7 @@ import com.google.common.eventbus.Subscribe; import com.google.common.net.HostAndPort; import com.sparrowwallet.drongo.Network; import com.sparrowwallet.drongo.address.Address; +import com.sparrowwallet.drongo.protocol.ScriptType; import com.sparrowwallet.drongo.protocol.Transaction; import com.sparrowwallet.drongo.psbt.PSBT; import com.sparrowwallet.drongo.uri.BitcoinURI; @@ -605,7 +606,7 @@ public class AppServices { if("bitcoin".equals(uri.getScheme())) { Platform.runLater(() -> openBitcoinUri(uri)); } else if("aopp".equals(uri.getScheme())) { - log.info(uri.toString()); + Platform.runLater(() -> openAddressOwnershipProof(uri)); } }); } @@ -613,25 +614,7 @@ public class AppServices { private static void openBitcoinUri(URI uri) { try { BitcoinURI bitcoinURI = new BitcoinURI(uri.toString()); - - Wallet wallet = null; - Set wallets = get().getOpenWallets().keySet(); - if(wallets.isEmpty()) { - showErrorDialog("No wallet available", "Open a wallet to send to the provided bitcoin URI."); - } else if(wallets.size() == 1) { - wallet = wallets.iterator().next(); - } else { - ChoiceDialog walletChoiceDialog = new ChoiceDialog<>(wallets.iterator().next(), wallets); - walletChoiceDialog.setTitle("Choose Wallet"); - walletChoiceDialog.setHeaderText("Choose a wallet to pay from"); - Image image = new Image("/image/sparrow-small.png"); - walletChoiceDialog.getDialogPane().setGraphic(new ImageView(image)); - AppServices.setStageIcon(walletChoiceDialog.getDialogPane().getScene().getWindow()); - Optional optWallet = walletChoiceDialog.showAndWait(); - if(optWallet.isPresent()) { - wallet = optWallet.get(); - } - } + Wallet wallet = selectWallet(null, "pay from"); if(wallet != null) { final Wallet sendingWallet = wallet; @@ -643,6 +626,44 @@ public class AppServices { } } + public static void openAddressOwnershipProof(URI uri) { + try { + Aopp aopp = new Aopp(uri); + Wallet wallet = selectWallet(aopp.getScriptType(), "send proof of address"); + + if(wallet != null) { + EventManager.get().post(new ReceiveActionEvent(wallet)); + Platform.runLater(() -> EventManager.get().post(new ReceiveProofEvent(wallet, aopp))); + } + } catch(Exception e) { + showErrorDialog("Not a valid AOPP URI", e.getMessage()); + } + } + + private static Wallet selectWallet(ScriptType scriptType, String actionDescription) { + Wallet wallet = null; + List wallets = get().getOpenWallets().keySet().stream().filter(w -> scriptType == null || w.getScriptType() == scriptType).collect(Collectors.toList()); + if(wallets.isEmpty()) { + showErrorDialog("No wallet available", "Open a" + (scriptType == null ? "" : " " + scriptType.getDescription()) + " wallet to " + actionDescription + "."); + } else if(wallets.size() == 1) { + wallet = wallets.iterator().next(); + } else { + ChoiceDialog walletChoiceDialog = new ChoiceDialog<>(wallets.iterator().next(), wallets); + walletChoiceDialog.setTitle("Choose Wallet"); + walletChoiceDialog.setHeaderText("Choose a wallet to " + actionDescription); + Image image = new Image("/image/sparrow-small.png"); + walletChoiceDialog.getDialogPane().setGraphic(new ImageView(image)); + setStageIcon(walletChoiceDialog.getDialogPane().getScene().getWindow()); + moveToActiveWindowScreen(walletChoiceDialog); + Optional optWallet = walletChoiceDialog.showAndWait(); + if(optWallet.isPresent()) { + wallet = optWallet.get(); + } + } + + return wallet; + } + public static Font getMonospaceFont() { return Font.font("Roboto Mono", 13); } diff --git a/src/main/java/com/sparrowwallet/sparrow/control/MessageSignDialog.java b/src/main/java/com/sparrowwallet/sparrow/control/MessageSignDialog.java index 7eee7230..9f39094a 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/MessageSignDialog.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/MessageSignDialog.java @@ -34,6 +34,7 @@ import tornadofx.control.Fieldset; import tornadofx.control.Form; import java.security.SignatureException; +import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -45,6 +46,7 @@ public class MessageSignDialog extends Dialog { private final TextArea signature; private final Wallet wallet; private WalletNode walletNode; + private boolean electrumSignatureFormat; /** * Verification only constructor @@ -69,6 +71,19 @@ public class MessageSignDialog extends Dialog { * @param walletNode Wallet node to derive address from */ public MessageSignDialog(Wallet wallet, WalletNode walletNode) { + this(wallet, walletNode, null, null); + } + + /** + * Sign and verify with preset address, and supplied title, message and dialog buttons + * + * @param wallet Wallet to sign with + * @param walletNode Wallet node to derive address from + * @param title Header text of dialog + * @param msg Message to sign (all fields will be made uneditable) + * @param buttons The dialog buttons to display. If one contains the text "sign" it will trigger the signing process + */ + public MessageSignDialog(Wallet wallet, WalletNode walletNode, String title, String msg, ButtonType... buttons) { 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"); @@ -84,7 +99,7 @@ public class MessageSignDialog extends Dialog { final DialogPane dialogPane = getDialogPane(); dialogPane.getStylesheets().add(AppServices.class.getResource("general.css").toExternalForm()); AppServices.setStageIcon(dialogPane.getScene().getWindow()); - dialogPane.setHeaderText(wallet == null ? "Verify Message" : "Sign/Verify Message"); + dialogPane.setHeaderText(title == null ? (wallet == null ? "Verify Message" : "Sign/Verify Message") : title); Image image = new Image("image/seed.png", 50, 50, false, false); if (!image.isError()) { @@ -133,47 +148,70 @@ public class MessageSignDialog extends Dialog { form.getChildren().add(fieldset); dialogPane.setContent(form); + if(msg != null) { + message.setText(msg); + address.setEditable(false); + message.setEditable(false); + signature.setEditable(false); + } + 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(); - }); + if(buttons.length > 0) { + dialogPane.getButtonTypes().addAll(buttons); - 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 - } + ButtonType customSignButtonType = Arrays.stream(buttons).filter(buttonType -> buttonType.getText().toLowerCase().contains("sign")).findFirst().orElse(null); + if(customSignButtonType != null) { + Button customSignButton = (Button)dialogPane.lookupButton(customSignButtonType); + customSignButton.setDefaultButton(true); + customSignButton.setOnAction(event -> { + customSignButton.setDisable(true); + signMessage(); + setResult(ButtonBar.ButtonData.OK_DONE); + }); } - }); + } else { + 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 -> { @@ -186,13 +224,26 @@ public class MessageSignDialog extends Dialog { }); AppServices.moveToActiveWindowScreen(this); - setResultConverter(dialogButton -> dialogButton == signButtonType || dialogButton == verifyButtonType ? ButtonBar.ButtonData.APPLY : ButtonBar.ButtonData.OK_DONE); + setResultConverter(dialogButton -> dialogButton == signButtonType || dialogButton == verifyButtonType ? ButtonBar.ButtonData.APPLY : dialogButton.getButtonData()); } private Address getAddress()throws InvalidAddressException { return Address.fromString(address.getText()); } + public String getSignature() { + return signature.getText(); + } + + /** + * Use the Electrum signing format, which uses the non-segwit compressed signing parameters for both segwit types (p2sh-p2wpkh and p2wpkh) + * + * @param electrumSignatureFormat + */ + public void setElectrumSignatureFormat(boolean electrumSignatureFormat) { + this.electrumSignatureFormat = electrumSignatureFormat; + } + private boolean isValidAddress() { try { getAddress(); @@ -228,7 +279,8 @@ public class MessageSignDialog extends Dialog { //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().trim(), decryptedWallet.getScriptType(), null); + ScriptType scriptType = electrumSignatureFormat ? ScriptType.P2PKH : decryptedWallet.getScriptType(); + String signatureText = privKey.signMessage(message.getText().trim(), scriptType, null); signature.clear(); signature.appendText(signatureText); privKey.clear(); diff --git a/src/main/java/com/sparrowwallet/sparrow/event/ReceiveActionEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/ReceiveActionEvent.java index b842c3e7..e10fdce9 100644 --- a/src/main/java/com/sparrowwallet/sparrow/event/ReceiveActionEvent.java +++ b/src/main/java/com/sparrowwallet/sparrow/event/ReceiveActionEvent.java @@ -1,15 +1,20 @@ package com.sparrowwallet.sparrow.event; +import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.sparrow.wallet.NodeEntry; public class ReceiveActionEvent { - private final NodeEntry receiveEntry; + private final Wallet wallet; public ReceiveActionEvent(NodeEntry receiveEntry) { - this.receiveEntry = receiveEntry; + this.wallet = receiveEntry.getWallet(); } - public NodeEntry getReceiveEntry() { - return receiveEntry; + public ReceiveActionEvent(Wallet wallet) { + this.wallet = wallet; + } + + public Wallet getWallet() { + return wallet; } } diff --git a/src/main/java/com/sparrowwallet/sparrow/event/ReceiveProofEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/ReceiveProofEvent.java new file mode 100644 index 00000000..2f1abf19 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/event/ReceiveProofEvent.java @@ -0,0 +1,22 @@ +package com.sparrowwallet.sparrow.event; + +import com.sparrowwallet.drongo.wallet.Wallet; +import com.sparrowwallet.sparrow.net.Aopp; + +public class ReceiveProofEvent { + private final Wallet wallet; + private final Aopp aopp; + + public ReceiveProofEvent(Wallet wallet, Aopp aopp) { + this.wallet = wallet; + this.aopp = aopp; + } + + public Wallet getWallet() { + return wallet; + } + + public Aopp getAopp() { + return aopp; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/net/Aopp.java b/src/main/java/com/sparrowwallet/sparrow/net/Aopp.java new file mode 100644 index 00000000..6ff82b10 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/net/Aopp.java @@ -0,0 +1,167 @@ +package com.sparrowwallet.sparrow.net; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.sparrowwallet.drongo.address.Address; +import com.sparrowwallet.drongo.protocol.ScriptType; +import com.sparrowwallet.sparrow.AppServices; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.net.*; +import java.nio.charset.StandardCharsets; +import java.util.LinkedHashMap; +import java.util.Map; + +public class Aopp { + private static final Logger log = LoggerFactory.getLogger(Aopp.class); + + public static final String SCHEME = "aopp"; + + private final int version; + private final String message; + private final ScriptType scriptType; + private final URL callback; + + public Aopp(URI uri) throws MalformedURLException { + if(!uri.getScheme().equals(SCHEME)) { + throw new IllegalArgumentException("Uri " + uri + " does not have the correct scheme"); + } + + Map parameterMap = new LinkedHashMap<>(); + String query = uri.getSchemeSpecificPart(); + if(query.startsWith("?")) { + query = query.substring(1); + } + + String[] pairs = query.split("&"); + for(String pair : pairs) { + int idx = pair.indexOf("="); + parameterMap.put(pair.substring(0, idx), pair.substring(idx + 1)); + } + + String strVersion = parameterMap.get("v"); + if(strVersion == null) { + throw new IllegalArgumentException("No version parameter provided"); + } + + version = Integer.parseInt(strVersion); + if(version != 0) { + throw new IllegalArgumentException("Unsupported version number " + version); + } + + String strMessage = parameterMap.get("msg"); + if(strMessage == null) { + throw new IllegalArgumentException("No msg parameter provided"); + } + message = strMessage.replace('+', ' '); + + String asset = parameterMap.get("asset"); + if(asset == null || !asset.equals("btc")) { + throw new IllegalArgumentException("Unsupported asset type " + asset); + } + + String format = parameterMap.get("format"); + if(format == null) { + throw new IllegalArgumentException("No format parameter provided"); + } + if(format.equals("p2sh")) { + format = "p2sh_p2wpkh"; + } + + if(format.equals("any")) { + scriptType = null; + } else { + scriptType = ScriptType.valueOf(format.toUpperCase()); + } + + String callbackUrl = parameterMap.get("callback"); + if(callbackUrl == null) { + throw new IllegalArgumentException("No callback parameter provided"); + } + + callback = new URL(callbackUrl); + } + + public void sendProofOfAddress(Address address, String signature) throws URISyntaxException, IOException, InterruptedException, AoppException { + Proxy proxy = AppServices.getProxy(); + + CallbackRequest callbackRequest = new CallbackRequest(version, address.toString(), signature); + Gson gson = new GsonBuilder().disableHtmlEscaping().create(); + String json = gson.toJson(callbackRequest); + + if(log.isDebugEnabled()) { + log.debug("Sending " + json + " to " + callback); + } + + HttpURLConnection connection = proxy == null ? (HttpURLConnection)callback.openConnection() : (HttpURLConnection)callback.openConnection(proxy); + connection.setRequestMethod("POST"); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setDoOutput(true); + + try(OutputStream os = connection.getOutputStream()) { + byte[] jsonBytes = json.getBytes(StandardCharsets.UTF_8); + os.write(jsonBytes); + } + + StringBuilder response = new StringBuilder(); + try(BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) { + String responseLine; + while((responseLine = br.readLine()) != null) { + response.append(responseLine.trim()); + } + } + int statusCode = connection.getResponseCode(); + + if(log.isDebugEnabled()) { + log.debug("Received " + statusCode + " " + response); + } + + if(statusCode < 200 || statusCode >= 300) { + throw new AoppException("Could not send proof of ownership. Server returned " + response); + } + } + + public ScriptType getScriptType() { + return scriptType; + } + + public String getMessage() { + return message; + } + + public URL getCallback() { + return callback; + } + + private static class CallbackRequest { + public CallbackRequest(int version, String address, String signature) { + this.version = version; + this.address = address; + this.signature = signature; + } + + public int version; + public String address; + public String signature; + } + + public static final class AoppException extends Exception { + public AoppException(String message) { + super(message); + } + + public AoppException(String message, Throwable cause) { + super(message, cause); + } + + public AoppException(Throwable cause) { + super(cause); + } + + public AoppException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/net/FeeRatesSource.java b/src/main/java/com/sparrowwallet/sparrow/net/FeeRatesSource.java index 2351f564..6ec9216e 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/FeeRatesSource.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/FeeRatesSource.java @@ -1,15 +1,13 @@ package com.sparrowwallet.sparrow.net; -import com.google.common.net.HostAndPort; import com.google.gson.Gson; -import com.sparrowwallet.sparrow.io.Config; +import com.sparrowwallet.sparrow.AppServices; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; -import java.net.InetSocketAddress; import java.net.Proxy; import java.net.URL; import java.nio.charset.StandardCharsets; @@ -57,7 +55,7 @@ public enum FeeRatesSource { } private static Map getThreeTierFeeRates(Map defaultblockTargetFeeRates, String url) { - Proxy proxy = getProxy(); + Proxy proxy = AppServices.getProxy(); Map blockTargetFeeRates = new LinkedHashMap<>(); try(InputStream is = (proxy == null ? new URL(url).openStream() : new URL(url).openConnection(proxy).getInputStream()); Reader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) { @@ -97,17 +95,6 @@ public enum FeeRatesSource { return blockTargetFeeRates; } - private static Proxy getProxy() { - Config config = Config.get(); - if(config.isUseProxy()) { - HostAndPort proxy = HostAndPort.fromString(config.getProxyServer()); - InetSocketAddress proxyAddress = new InetSocketAddress(proxy.getHost(), proxy.getPortOrDefault(ProxyTcpOverTlsTransport.DEFAULT_PROXY_PORT)); - return new Proxy(Proxy.Type.SOCKS, proxyAddress); - } - - return null; - } - @Override public String toString() { return name; diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/ReceiveController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/ReceiveController.java index 7c295284..b5a541cf 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/ReceiveController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/ReceiveController.java @@ -9,6 +9,7 @@ import com.google.zxing.common.BitMatrix; import com.google.zxing.qrcode.QRCodeWriter; import com.sparrowwallet.drongo.KeyPurpose; import com.sparrowwallet.drongo.OutputDescriptor; +import com.sparrowwallet.drongo.address.Address; import com.sparrowwallet.drongo.wallet.BlockTransactionHashIndex; import com.sparrowwallet.drongo.wallet.KeystoreSource; import com.sparrowwallet.drongo.wallet.Wallet; @@ -19,17 +20,14 @@ import com.sparrowwallet.sparrow.event.*; import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; import com.sparrowwallet.sparrow.io.Device; import com.sparrowwallet.sparrow.io.Hwi; +import com.sparrowwallet.sparrow.net.Aopp; import javafx.application.Platform; import javafx.event.ActionEvent; -import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.fxml.Initializable; -import javafx.scene.control.Button; -import javafx.scene.control.Label; -import javafx.scene.control.TextField; +import javafx.scene.control.*; import javafx.scene.image.Image; import javafx.scene.image.ImageView; -import javafx.scene.input.MouseEvent; import org.controlsfx.glyphfont.Glyph; import org.fxmisc.richtext.CodeArea; import org.slf4j.Logger; @@ -40,10 +38,7 @@ import java.io.ByteArrayOutputStream; import java.net.URL; import java.text.DateFormat; import java.text.SimpleDateFormat; -import java.util.List; -import java.util.Map; -import java.util.ResourceBundle; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; public class ReceiveController extends WalletFormController implements Initializable { @@ -248,6 +243,28 @@ public class ReceiveController extends WalletFormController implements Initializ this.currentEntry = null; } + private void signAndSendProofOfAddress(Aopp aopp) { + if(currentEntry == null) { + Platform.runLater(() -> signAndSendProofOfAddress(aopp)); + } else { + try { + ButtonType signSendButtonType = new ButtonType("Sign & Send", ButtonBar.ButtonData.APPLY); + ButtonType cancelButtonType = new ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE); + MessageSignDialog messageSignDialog = new MessageSignDialog(getWalletForm().getWallet(), currentEntry.getNode(), "Send Proof of Address", aopp.getMessage(), signSendButtonType, cancelButtonType); + messageSignDialog.setElectrumSignatureFormat(true); + Optional buttonData = messageSignDialog.showAndWait(); + if(buttonData.isPresent() && buttonData.get() == ButtonBar.ButtonData.OK_DONE) { + Address address = getWalletForm().getWallet().getAddress(currentEntry.getNode()); + String signature = messageSignDialog.getSignature(); + aopp.sendProofOfAddress(address, signature); + AppServices.showAlertDialog("Proof of Address Sent", "Proof of ownership of address " + address + " has been successfully sent to " + aopp.getCallback().getHost() + ".", Alert.AlertType.INFORMATION); + } + } catch(Exception e) { + AppServices.showErrorDialog("Cannot send proof of ownership", e.getMessage()); + } + } + } + public static Glyph getUnusedGlyph() { Glyph checkGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.CHECK_CIRCLE); checkGlyph.getStyleClass().add("unused-check"); @@ -280,6 +297,13 @@ public class ReceiveController extends WalletFormController implements Initializ } } + @Subscribe + public void receiveProof(ReceiveProofEvent event) { + if(event.getWallet().equals(getWalletForm().getWallet())) { + Platform.runLater(() -> signAndSendProofOfAddress(event.getAopp())); + } + } + @Subscribe public void walletNodesChanged(WalletNodesChangedEvent event) { if(event.getWallet().equals(walletForm.getWallet())) { diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletController.java index b27b82ee..b53c4009 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletController.java @@ -114,7 +114,7 @@ public class WalletController extends WalletFormController implements Initializa @Subscribe public void receiveAction(ReceiveActionEvent event) { - if(event.getReceiveEntry().getWallet().equals(walletForm.getWallet())) { + if(event.getWallet().equals(walletForm.getWallet())) { selectFunction(Function.RECEIVE); } }