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:
sqrrm 2020-06-03 15:41:52 +02:00
parent 752eb49b4a
commit ca9665fa3c
No known key found for this signature in database
GPG key ID: 45235F9EF87089EC
7 changed files with 244 additions and 19 deletions

View file

@ -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;
}

View file

@ -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

View file

@ -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();
}
}

View file

@ -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;
@ -60,8 +59,8 @@ public class AccountAgeWitnessUtils {
log.info("Orphaned signed account age witnesses:");
orphanSigners.forEach(w -> {
log.info("{}: Signer PKH: {} Owner PKH: {} time: {}", w.getVerificationMethod().toString(),
Utilities.bytesAsHexString(Hash.getRipemd160hash(w.getSignerPubKey())).substring(0,7),
Utilities.bytesAsHexString(Hash.getRipemd160hash(w.getWitnessOwnerPubKey())).substring(0,7),
Utilities.bytesAsHexString(Hash.getRipemd160hash(w.getSignerPubKey())).substring(0, 7),
Utilities.bytesAsHexString(Hash.getRipemd160hash(w.getWitnessOwnerPubKey())).substring(0, 7),
w.getDate());
logChild(w, " ", new Stack<>());
});
@ -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()));
}
///////////////////////////////////////////////////////////////////////////////////////////

View file

@ -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

View file

@ -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());
}
}

View file

@ -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();
}
}
}