mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-23 15:00:30 +01:00
Sign unsigned keys
Legacy arbitrator can sign unsigned signed witness signer pubkeys To sign, from legacy arbitrator support, ctrl+O
This commit is contained in:
parent
752eb49b4a
commit
ca9665fa3c
7 changed files with 244 additions and 19 deletions
|
@ -42,10 +42,6 @@ import javafx.scene.input.KeyCodeCombination;
|
|||
import javafx.scene.input.KeyCombination;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
|
@ -462,8 +458,7 @@ public class Utilities {
|
|||
}
|
||||
|
||||
// Helper to filter unique elements by key
|
||||
public static <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor)
|
||||
{
|
||||
public static <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor) {
|
||||
Map<Object, Boolean> map = new ConcurrentHashMap<>();
|
||||
return t -> map.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
|
||||
}
|
||||
|
|
|
@ -65,8 +65,6 @@ import java.util.stream.Collectors;
|
|||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.common.util.Utilities.distinctByKey;
|
||||
|
||||
@Slf4j
|
||||
public class SignedWitnessService {
|
||||
public static final long SIGNER_AGE_DAYS = 30;
|
||||
|
@ -224,6 +222,15 @@ public class SignedWitnessService {
|
|||
return signAccountAgeWitness(MINIMUM_TRADE_AMOUNT_FOR_SIGNING, accountAgeWitness, key, witnessPubKey, time);
|
||||
}
|
||||
|
||||
// Arbitrators sign with EC key
|
||||
public String signTraderPubKey(ECKey key,
|
||||
byte[] peersPubKey,
|
||||
long childSignTime) {
|
||||
var time = childSignTime - SIGNER_AGE - 1;
|
||||
var dummyAccountAgeWitness = new AccountAgeWitness(Hash.getRipemd160hash(peersPubKey), time);
|
||||
return signAccountAgeWitness(MINIMUM_TRADE_AMOUNT_FOR_SIGNING, dummyAccountAgeWitness, key, peersPubKey, time);
|
||||
}
|
||||
|
||||
// Arbitrators sign with EC key
|
||||
private String signAccountAgeWitness(Coin tradeAmount,
|
||||
AccountAgeWitness accountAgeWitness,
|
||||
|
@ -350,11 +357,14 @@ public class SignedWitnessService {
|
|||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public Set<byte[]> getUnsignedSignersPubKeys() {
|
||||
return getRootSignedWitnessSet(false).stream()
|
||||
.map(SignedWitness::getSignerPubKey)
|
||||
.filter(distinctByKey(key -> Utilities.bytesAsHexString(Hash.getRipemd160hash(key))))
|
||||
.collect(Collectors.toSet());
|
||||
// Find first (in time) SignedWitness per missing signer
|
||||
public Set<SignedWitness> getUnsignedSignerPubKeys() {
|
||||
var oldestUnsignedSigners = new HashMap<P2PDataStorage.ByteArray, SignedWitness>();
|
||||
getRootSignedWitnessSet(false).forEach(signedWitness ->
|
||||
oldestUnsignedSigners.compute(new P2PDataStorage.ByteArray(signedWitness.getSignerPubKey()),
|
||||
(key, oldValue) -> oldValue == null ? signedWitness :
|
||||
oldValue.getDate() > signedWitness.getDate() ? signedWitness : oldValue));
|
||||
return new HashSet<>(oldestUnsignedSigners.values());
|
||||
}
|
||||
|
||||
// We go one level up by using the signer Key to lookup for SignedWitness objects which contain the signerKey as
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package bisq.core.account.witness;
|
||||
|
||||
import bisq.core.account.sign.SignedWitness;
|
||||
import bisq.core.account.sign.SignedWitnessService;
|
||||
import bisq.core.filter.FilterManager;
|
||||
import bisq.core.filter.PaymentAccountFilter;
|
||||
|
@ -648,6 +649,12 @@ public class AccountAgeWitnessService {
|
|||
time);
|
||||
}
|
||||
|
||||
public String arbitratorSignOrphanPubKey(ECKey key,
|
||||
byte[] peersPubKey,
|
||||
long childSignTime) {
|
||||
return signedWitnessService.signTraderPubKey(key, peersPubKey, childSignTime);
|
||||
}
|
||||
|
||||
public void arbitratorSignAccountAgeWitness(AccountAgeWitness accountAgeWitness,
|
||||
ECKey key,
|
||||
byte[] tradersPubKey,
|
||||
|
@ -806,4 +813,8 @@ public class AccountAgeWitnessService {
|
|||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public Set<SignedWitness> getUnsignedSignerPubKeys() {
|
||||
return signedWitnessService.getUnsignedSignerPubKeys();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,6 @@ import bisq.common.util.Utilities;
|
|||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.Stack;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
@ -100,8 +99,10 @@ public class AccountAgeWitnessUtils {
|
|||
|
||||
public void logUnsignedSignerPubKeys() {
|
||||
log.info("Unsigned signer pubkeys");
|
||||
signedWitnessService.getUnsignedSignersPubKeys().forEach(pubKey ->
|
||||
log.info("PK hash {}",Utilities.bytesAsHexString(Hash.getRipemd160hash(pubKey))));
|
||||
signedWitnessService.getUnsignedSignerPubKeys().forEach(signedWitness ->
|
||||
log.info("PK hash {} date {}",
|
||||
Utilities.bytesAsHexString(Hash.getRipemd160hash(signedWitness.getSignerPubKey())),
|
||||
signedWitness.getDate()));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -2739,6 +2739,12 @@ 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
|
||||
popup.accountSigning.unsignedPubKeys.signed=Pubkeys were signed
|
||||
popup.accountSigning.unsignedPubKeys.result.headline=Signing completed
|
||||
popup.accountSigning.unsignedPubKeys.result.signed=Signed pubkeys
|
||||
popup.accountSigning.unsignedPubKeys.result.failed=Failed to sign
|
||||
|
||||
####################################################################
|
||||
# Notifications
|
||||
|
|
|
@ -0,0 +1,196 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.desktop.main.overlays.windows;
|
||||
|
||||
import bisq.desktop.components.AutoTooltipButton;
|
||||
import bisq.desktop.components.InputTextField;
|
||||
import bisq.desktop.main.overlays.Overlay;
|
||||
import bisq.desktop.main.overlays.popups.Popup;
|
||||
|
||||
import bisq.core.account.sign.SignedWitness;
|
||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
||||
|
||||
import bisq.common.crypto.Hash;
|
||||
import bisq.common.util.Tuple3;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import org.bitcoinj.core.Utils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import javafx.geometry.VPos;
|
||||
|
||||
import javafx.collections.FXCollections;
|
||||
|
||||
import javafx.util.Callback;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.desktop.util.FormBuilder.*;
|
||||
|
||||
@Slf4j
|
||||
public class SignUnsignedPubKeysWindow extends Overlay<SignUnsignedPubKeysWindow> {
|
||||
|
||||
private InputTextField searchTextField;
|
||||
private ListView<SignedWitness> unsignedPubKeys = new ListView<>();
|
||||
private InputTextField privateKey;
|
||||
private final AccountAgeWitnessService accountAgeWitnessService;
|
||||
private final ArbitratorManager arbitratorManager;
|
||||
private ListView<SignedWitness> signedWitnessListView = new ListView<>();
|
||||
private ListView<String> failedView = new ListView<>();
|
||||
private List<SignedWitness> signedWitnessList = new ArrayList<>();
|
||||
private List<String> failed = new ArrayList<>();
|
||||
private Callback<ListView<SignedWitness>, ListCell<SignedWitness>> signedWitnessCellFactory;
|
||||
|
||||
@Inject
|
||||
public SignUnsignedPubKeysWindow(AccountAgeWitnessService accountAgeWitnessService,
|
||||
ArbitratorManager arbitratorManager) {
|
||||
this.accountAgeWitnessService = accountAgeWitnessService;
|
||||
this.arbitratorManager = arbitratorManager;
|
||||
|
||||
signedWitnessCellFactory = new Callback<>() {
|
||||
@Override
|
||||
public ListCell<SignedWitness> call(
|
||||
ListView<SignedWitness> param) {
|
||||
return new ListCell<>() {
|
||||
@Override
|
||||
protected void updateItem(SignedWitness item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (item != null && !empty) {
|
||||
setText(Utilities.bytesAsHexString(Hash.getRipemd160hash(item.getSignerPubKey())));
|
||||
} else {
|
||||
setText(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
width = 1000;
|
||||
rowIndex = -1;
|
||||
createGridPane();
|
||||
|
||||
gridPane.setPrefHeight(600);
|
||||
gridPane.getColumnConstraints().get(1).setHgrow(Priority.NEVER);
|
||||
headLine(Res.get("popup.accountSigning.singleAccountSelect.headline"));
|
||||
type = Type.Attention;
|
||||
|
||||
addHeadLine();
|
||||
addUnsignedPubKeysContent();
|
||||
addECKeyField();
|
||||
addButtons();
|
||||
applyStyles();
|
||||
|
||||
display();
|
||||
}
|
||||
|
||||
private void addUnsignedPubKeysContent() {
|
||||
Tuple3<Label, ListView<SignedWitness>, VBox> unsignedPubKeysTuple = addTopLabelListView(gridPane, ++rowIndex,
|
||||
Res.get("popup.accountSigning.unsignedPubKeys.headline"));
|
||||
unsignedPubKeys = unsignedPubKeysTuple.second;
|
||||
unsignedPubKeys.setCellFactory(signedWitnessCellFactory);
|
||||
unsignedPubKeys.setItems(FXCollections.observableArrayList(
|
||||
accountAgeWitnessService.getUnsignedSignerPubKeys()));
|
||||
}
|
||||
|
||||
private void addECKeyField() {
|
||||
privateKey = addInputTextField(gridPane, ++rowIndex, Res.get("popup.accountSigning.signAccounts.ECKey"));
|
||||
GridPane.setVgrow(privateKey, Priority.ALWAYS);
|
||||
GridPane.setValignment(privateKey, VPos.TOP);
|
||||
}
|
||||
|
||||
private void removeContent() {
|
||||
removeRowsFromGridPane(gridPane, 1, 3);
|
||||
rowIndex = 1;
|
||||
}
|
||||
|
||||
private void signPubKeys() {
|
||||
removeContent();
|
||||
headLineLabel.setText(Res.get("popup.accountSigning.unsignedPubKeys.signed"));
|
||||
var arbitratorKey = arbitratorManager.getRegistrationKey(privateKey.getText());
|
||||
if (arbitratorKey != null) {
|
||||
var arbitratorPubKeyAsHex = Utils.HEX.encode(arbitratorKey.getPubKey());
|
||||
var isKeyValid = arbitratorManager.isPublicKeyInList(arbitratorPubKeyAsHex);
|
||||
failed.clear();
|
||||
if (isKeyValid) {
|
||||
unsignedPubKeys.getItems().forEach(signedWitness -> {
|
||||
var result = accountAgeWitnessService.arbitratorSignOrphanPubKey(arbitratorKey,
|
||||
signedWitness.getSignerPubKey(), signedWitness.getDate());
|
||||
if (result.isEmpty()) {
|
||||
signedWitnessList.add(signedWitness);
|
||||
} else {
|
||||
failed.add("Signing pubkey " + Utilities.bytesAsHexString(Hash.getRipemd160hash(
|
||||
signedWitness.getSignerPubKey())) + " failed with error " + result);
|
||||
}
|
||||
});
|
||||
showResult();
|
||||
}
|
||||
} else {
|
||||
new Popup().error(Res.get("popup.accountSigning.signAccounts.ECKey.error")).onClose(this::hide).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void showResult() {
|
||||
removeContent();
|
||||
closeButton.setVisible(false);
|
||||
closeButton.setManaged(false);
|
||||
|
||||
Tuple3<Label, ListView<SignedWitness>, VBox> signedTuple = addTopLabelListView(gridPane, ++rowIndex,
|
||||
Res.get("popup.accountSigning.unsignedPubKeys.result.signed"));
|
||||
signedWitnessListView = signedTuple.second;
|
||||
signedWitnessListView.setCellFactory(signedWitnessCellFactory);
|
||||
signedWitnessListView.setItems(FXCollections.observableArrayList(signedWitnessList));
|
||||
Tuple3<Label, ListView<String>, VBox> failedTuple = addTopLabelListView(gridPane, ++rowIndex,
|
||||
Res.get("popup.accountSigning.unsignedPubKeys.result.failed"));
|
||||
failedView = failedTuple.second;
|
||||
failedView.setItems(FXCollections.observableArrayList(failed));
|
||||
|
||||
((AutoTooltipButton) actionButton).updateText(Res.get("shared.ok"));
|
||||
actionButton.setOnAction(a -> hide());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addButtons() {
|
||||
var buttonTuple = add2ButtonsAfterGroup(gridPane, ++rowIndex + 1,
|
||||
Res.get("popup.accountSigning.unsignedPubKeys.sign"), Res.get("shared.cancel"));
|
||||
|
||||
actionButton = buttonTuple.first;
|
||||
actionButton.setDisable(unsignedPubKeys.getItems().size() == 0);
|
||||
actionButton.setOnAction(e -> signPubKeys());
|
||||
|
||||
closeButton = (AutoTooltipButton) buttonTuple.second;
|
||||
closeButton.setOnAction(e -> hide());
|
||||
|
||||
}
|
||||
}
|
|
@ -24,6 +24,7 @@ import bisq.desktop.main.overlays.windows.ContractWindow;
|
|||
import bisq.desktop.main.overlays.windows.DisputeSummaryWindow;
|
||||
import bisq.desktop.main.overlays.windows.SignPaymentAccountsWindow;
|
||||
import bisq.desktop.main.overlays.windows.SignSpecificWitnessWindow;
|
||||
import bisq.desktop.main.overlays.windows.SignUnsignedPubKeysWindow;
|
||||
import bisq.desktop.main.overlays.windows.TradeDetailsWindow;
|
||||
import bisq.desktop.main.support.dispute.agent.DisputeAgentView;
|
||||
|
||||
|
@ -51,6 +52,7 @@ public class ArbitratorView extends DisputeAgentView {
|
|||
|
||||
private final SignPaymentAccountsWindow signPaymentAccountsWindow;
|
||||
private final SignSpecificWitnessWindow signSpecificWitnessWindow;
|
||||
private final SignUnsignedPubKeysWindow signUnsignedPubKeysWindow;
|
||||
|
||||
@Inject
|
||||
public ArbitratorView(ArbitrationManager arbitrationManager,
|
||||
|
@ -64,7 +66,8 @@ public class ArbitratorView extends DisputeAgentView {
|
|||
AccountAgeWitnessService accountAgeWitnessService,
|
||||
@Named(Config.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys,
|
||||
SignPaymentAccountsWindow signPaymentAccountsWindow,
|
||||
SignSpecificWitnessWindow signSpecificWitnessWindow) {
|
||||
SignSpecificWitnessWindow signSpecificWitnessWindow,
|
||||
SignUnsignedPubKeysWindow signUnsignedPubKeysWindow) {
|
||||
super(arbitrationManager,
|
||||
keyRing,
|
||||
tradeManager,
|
||||
|
@ -77,6 +80,7 @@ public class ArbitratorView extends DisputeAgentView {
|
|||
useDevPrivilegeKeys);
|
||||
this.signPaymentAccountsWindow = signPaymentAccountsWindow;
|
||||
this.signSpecificWitnessWindow = signSpecificWitnessWindow;
|
||||
this.signUnsignedPubKeysWindow = signUnsignedPubKeysWindow;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -95,6 +99,8 @@ public class ArbitratorView extends DisputeAgentView {
|
|||
signPaymentAccountsWindow.show();
|
||||
} else if (Utilities.isAltOrCtrlPressed(KeyCode.P, event)) {
|
||||
signSpecificWitnessWindow.show();
|
||||
} else if (Utilities.isAltOrCtrlPressed(KeyCode.O, event)) {
|
||||
signUnsignedPubKeysWindow.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue