diff --git a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java index b7678c7fa0c..e947cba1287 100644 --- a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java +++ b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java @@ -51,6 +51,7 @@ import bisq.common.crypto.Sig; import bisq.common.handlers.ErrorMessageHandler; import bisq.common.util.MathUtils; +import bisq.common.util.Tuple2; import bisq.common.util.Utilities; import org.bitcoinj.core.Coin; @@ -880,4 +881,29 @@ public boolean isSignWitnessTrade(Trade trade) { !peerHasSignedWitness(trade) && tradeAmountIsSufficient(trade.getTradeAmount()); } + + public String getSignInfoFromAccount(PaymentAccount paymentAccount) { + var pubKey = keyRing.getSignatureKeyPair().getPublic(); + var witness = getMyWitness(paymentAccount.getPaymentAccountPayload()); + return Utilities.bytesAsHexString(witness.getHash()) + "," + Utilities.bytesAsHexString(pubKey.getEncoded()); + } + + public Tuple2 getSignInfoFromString(String signInfo) { + var parts = signInfo.split(","); + if (parts.length != 2) { + return null; + } + byte[] pubKeyHash; + Optional accountAgeWitness; + try { + var accountAgeWitnessHash = Utilities.decodeFromHex(parts[0]); + pubKeyHash = Utilities.decodeFromHex(parts[1]); + accountAgeWitness = getWitnessByHash(accountAgeWitnessHash); + return accountAgeWitness + .map(ageWitness -> new Tuple2<>(ageWitness, pubKeyHash)) + .orElse(null); + } catch (Exception e) { + return null; + } + } } diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 98b6a9940f4..c7d6a3195ef 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -2892,15 +2892,12 @@ popup.accountSigning.peerLimitLifted=The initial limit for one of your accounts popup.accountSigning.peerSigner=One of your accounts is mature enough to sign other payment accounts \ and the initial limit for one of your accounts has been lifted.\n\n{0} -popup.accountSigning.singleAccountSelect.headline=Select account age witness -popup.accountSigning.singleAccountSelect.description=Search for account age witness. -popup.accountSigning.singleAccountSelect.datePicker=Select point of time for signing +popup.accountSigning.singleAccountSelect.headline=Import unsigned account age witness popup.accountSigning.confirmSingleAccount.headline=Confirm selected account age witness popup.accountSigning.confirmSingleAccount.selectedHash=Selected witness hash popup.accountSigning.confirmSingleAccount.button=Sign account age witness popup.accountSigning.successSingleAccount.description=Witness {0} was signed popup.accountSigning.successSingleAccount.success.headline=Success -popup.accountSigning.successSingleAccount.signError=Failed to sign witness, {0} popup.accountSigning.unsignedPubKeys.headline=Unsigned Pubkeys popup.accountSigning.unsignedPubKeys.sign=Sign Pubkeys diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/PaymentAccountsView.java b/desktop/src/main/java/bisq/desktop/main/account/content/PaymentAccountsView.java index 0ea778b2b0f..6b26f663126 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/content/PaymentAccountsView.java +++ b/desktop/src/main/java/bisq/desktop/main/account/content/PaymentAccountsView.java @@ -62,6 +62,8 @@ public void initialize() { accountAgeWitnessService.getAccountAgeWitnessUtils().logSigners(); } else if (Utilities.isCtrlShiftPressed(KeyCode.U, event)) { accountAgeWitnessService.getAccountAgeWitnessUtils().logUnsignedSignerPubKeys(); + } else if (Utilities.isAltOrCtrlPressed(KeyCode.C, event)) { + copyAccount(); } }; @@ -174,4 +176,7 @@ public void updateItem(final PaymentAccount item, boolean empty) { protected abstract void buildForm(); protected abstract void onSelectAccount(PaymentAccount paymentAccount); + + protected void copyAccount() { + } } diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/fiataccounts/FiatAccountsView.java b/desktop/src/main/java/bisq/desktop/main/account/content/fiataccounts/FiatAccountsView.java index bd9da46515c..a9421c728ba 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/content/fiataccounts/FiatAccountsView.java +++ b/desktop/src/main/java/bisq/desktop/main/account/content/fiataccounts/FiatAccountsView.java @@ -99,6 +99,7 @@ import bisq.common.config.Config; import bisq.common.util.Tuple2; import bisq.common.util.Tuple3; +import bisq.common.util.Utilities; import org.bitcoinj.core.Coin; @@ -541,5 +542,14 @@ private void removeAccountRows() { gridRow = 1; } + @Override + protected void copyAccount() { + var selectedAccount = paymentAccountsListView.getSelectionModel().getSelectedItem(); + if (selectedAccount == null) { + return; + } + Utilities.copyToClipboard(accountAgeWitnessService.getSignInfoFromAccount(selectedAccount)); + } + } diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/SignSpecificWitnessWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/SignSpecificWitnessWindow.java index 3f8638878e8..21859213588 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/SignSpecificWitnessWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/SignSpecificWitnessWindow.java @@ -18,6 +18,7 @@ package bisq.desktop.main.overlays.windows; import bisq.desktop.components.AutoTooltipButton; +import bisq.desktop.components.BisqTextArea; import bisq.desktop.components.InputTextField; import bisq.desktop.main.overlays.Overlay; import bisq.desktop.main.overlays.popups.Popup; @@ -27,24 +28,21 @@ import bisq.core.locale.Res; import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; +import bisq.common.util.Tuple2; import bisq.common.util.Utilities; +import org.bitcoinj.core.ECKey; import org.bitcoinj.core.Utils; import javax.inject.Inject; -import com.jfoenix.controls.JFXAutoCompletePopup; - -import javafx.scene.control.DatePicker; -import javafx.scene.control.ListCell; +import javafx.scene.control.TextArea; import javafx.scene.layout.GridPane; import javafx.scene.layout.Priority; import javafx.geometry.VPos; -import java.time.Instant; -import java.time.ZoneId; -import java.time.ZoneOffset; +import java.util.Date; import lombok.extern.slf4j.Slf4j; @@ -53,10 +51,7 @@ @Slf4j public class SignSpecificWitnessWindow extends Overlay { - private InputTextField searchTextField; - private JFXAutoCompletePopup searchAutoComplete; - private AccountAgeWitness selectedWitness; - private DatePicker datePicker; + private Tuple2 signInfo; private InputTextField privateKey; private final AccountAgeWitnessService accountAgeWitnessService; private final ArbitratorManager arbitratorManager; @@ -89,56 +84,37 @@ public void show() { } private void addSelectWitnessContent() { - searchTextField = addInputTextField(gridPane, ++rowIndex, - Res.get("popup.accountSigning.singleAccountSelect.description")); - - searchAutoComplete = new JFXAutoCompletePopup<>(); - searchAutoComplete.setPrefWidth(400); - searchAutoComplete.getSuggestions().addAll(accountAgeWitnessService.getOrphanSignedWitnesses()); - searchAutoComplete.setSuggestionsCellFactory(param -> new ListCell<>() { - @Override - protected void updateItem(AccountAgeWitness item, boolean empty) { - super.updateItem(item, empty); - if (item != null) { - setText(Utilities.bytesAsHexString(item.getHash())); - } else { - setText(""); - } + TextArea accountInfoText = new BisqTextArea(); + accountInfoText.setPrefHeight(270); + accountInfoText.setWrapText(true); + GridPane.setRowIndex(accountInfoText, ++rowIndex); + gridPane.getChildren().add(accountInfoText); + + accountInfoText.textProperty().addListener((observable, oldValue, newValue) -> { + if (newValue == null || newValue.isEmpty()) { + return; } - }); - searchAutoComplete.setSelectionHandler(event -> { - searchTextField.setText(Utilities.bytesAsHexString(event.getObject().getHash())); - selectedWitness = event.getObject(); - if (selectedWitness != null) { - datePicker.setValue(Instant.ofEpochMilli(selectedWitness.getDate()).atZone( - ZoneId.systemDefault()).toLocalDate()); + signInfo = accountAgeWitnessService.getSignInfoFromString(newValue); + if (signInfo == null) { + actionButton.setDisable(true); + return; } + actionButton.setDisable(false); }); - - searchTextField.textProperty().addListener(observable -> { - searchAutoComplete.filter(witness -> Utilities.bytesAsHexString(witness.getHash()).startsWith( - searchTextField.getText().toLowerCase())); - if (searchAutoComplete.getFilteredSuggestions().isEmpty()) { - searchAutoComplete.hide(); - } else { - searchAutoComplete.show(searchTextField); - } - }); - - datePicker = addTopLabelDatePicker(gridPane, ++rowIndex, - Res.get("popup.accountSigning.singleAccountSelect.datePicker"), - 0).second; - datePicker.setOnAction(e -> updateWitnessSelectionState()); } private void addECKeyField() { privateKey = addInputTextField(gridPane, ++rowIndex, Res.get("popup.accountSigning.signAccounts.ECKey")); + actionButton.setDisable(true); GridPane.setVgrow(privateKey, Priority.ALWAYS); GridPane.setValignment(privateKey, VPos.TOP); - } - - private void updateWitnessSelectionState() { - actionButton.setDisable(selectedWitness == null || datePicker.getValue() == null); + privateKey.textProperty().addListener((observable, oldValue, newValue) -> { + if (checkedArbitratorKey() == null) { + actionButton.setDisable(true); + return; + } + actionButton.setDisable(false); + }); } private void removeContent() { @@ -146,30 +122,22 @@ private void removeContent() { rowIndex = 1; } - private void selectAccountAgeWitness() { + private void importAccountAgeWitness() { removeContent(); headLineLabel.setText(Res.get("popup.accountSigning.confirmSingleAccount.headline")); var selectedWitnessTextField = addTopLabelTextField(gridPane, ++rowIndex, Res.get("popup.accountSigning.confirmSingleAccount.selectedHash")).second; - selectedWitnessTextField.setText(Utilities.bytesAsHexString(selectedWitness.getHash())); + selectedWitnessTextField.setText(Utilities.bytesAsHexString(signInfo.first.getHash())); addECKeyField(); ((AutoTooltipButton) actionButton).updateText(Res.get("popup.accountSigning.confirmSingleAccount.button")); actionButton.setOnAction(a -> { - var arbitratorKey = arbitratorManager.getRegistrationKey(privateKey.getText()); + var arbitratorKey = checkedArbitratorKey(); if (arbitratorKey != null) { - var arbitratorPubKeyAsHex = Utils.HEX.encode(arbitratorKey.getPubKey()); - var isKeyValid = arbitratorManager.isPublicKeyInList(arbitratorPubKeyAsHex); - if (isKeyValid) { - var result = accountAgeWitnessService.arbitratorSignOrphanWitness(selectedWitness, - arbitratorKey, - datePicker.getValue().atStartOfDay().toEpochSecond(ZoneOffset.UTC) * 1000); - if (result.isEmpty()) { - addSuccessContent(); - } else { - new Popup().error(Res.get("popup.accountSigning.successSingleAccount.signError", result)) - .onClose(this::hide).show(); - } - } + accountAgeWitnessService.arbitratorSignAccountAgeWitness(signInfo.first, + arbitratorKey, + signInfo.second, + new Date().getTime()); + addSuccessContent(); } else { new Popup().error(Res.get("popup.accountSigning.signAccounts.ECKey.error")).onClose(this::hide).show(); } @@ -177,7 +145,6 @@ private void selectAccountAgeWitness() { }); } - private void addSuccessContent() { removeContent(); closeButton.setVisible(false); @@ -185,7 +152,7 @@ private void addSuccessContent() { headLineLabel.setText(Res.get("popup.accountSigning.successSingleAccount.success.headline")); var descriptionLabel = addMultilineLabel(gridPane, ++rowIndex, Res.get("popup.accountSigning.successSingleAccount.description", - Utilities.bytesAsHexString(selectedWitness.getHash()))); + Utilities.bytesAsHexString(signInfo.first.getHash()))); GridPane.setVgrow(descriptionLabel, Priority.ALWAYS); GridPane.setValignment(descriptionLabel, VPos.TOP); ((AutoTooltipButton) actionButton).updateText(Res.get("shared.ok")); @@ -194,15 +161,24 @@ private void addSuccessContent() { @Override protected void addButtons() { - var buttonTuple = add2ButtonsAfterGroup(gridPane, ++rowIndex + 1, + var buttonTuple = add2ButtonsAfterGroup(gridPane, ++rowIndex + 2, Res.get("popup.accountSigning.singleAccountSelect.headline"), Res.get("shared.cancel")); actionButton = buttonTuple.first; actionButton.setDisable(true); - actionButton.setOnAction(e -> selectAccountAgeWitness()); + actionButton.setOnAction(e -> importAccountAgeWitness()); closeButton = (AutoTooltipButton) buttonTuple.second; closeButton.setOnAction(e -> hide()); + } + private ECKey checkedArbitratorKey() { + var arbitratorKey = arbitratorManager.getRegistrationKey(privateKey.getText()); + if (arbitratorKey == null) { + return null; + } + var arbitratorPubKeyAsHex = Utils.HEX.encode(arbitratorKey.getPubKey()); + var isKeyValid = arbitratorManager.isPublicKeyInList(arbitratorPubKeyAsHex); + return isKeyValid ? arbitratorKey : null; } }