mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-19 09:52:23 +01:00
Merge pull request #4280 from sqrrm/arbitrator-witness-signing
Arbitrator witness signing
This commit is contained in:
commit
c190f4200f
@ -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;
|
||||
|
||||
@ -56,15 +52,19 @@ import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -330,6 +330,10 @@ public class Utilities {
|
||||
return new KeyCodeCombination(keyCode, KeyCombination.ALT_DOWN).match(keyEvent);
|
||||
}
|
||||
|
||||
public static boolean isCtrlShiftPressed(KeyCode keyCode, KeyEvent keyEvent) {
|
||||
return new KeyCodeCombination(keyCode, KeyCombination.CONTROL_DOWN, KeyCombination.SHIFT_DOWN).match(keyEvent);
|
||||
}
|
||||
|
||||
public static byte[] concatenateByteArrays(byte[] array1, byte[] array2) {
|
||||
return ArrayUtils.addAll(array1, array2);
|
||||
}
|
||||
@ -452,4 +456,10 @@ public class Utilities {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Helper to filter unique elements by key
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -168,7 +168,7 @@ public class SignedWitness implements ProcessOncePersistableNetworkPayload, Pers
|
||||
// Getters
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
P2PDataStorage.ByteArray getHashAsByteArray() {
|
||||
public P2PDataStorage.ByteArray getHashAsByteArray() {
|
||||
return new P2PDataStorage.ByteArray(hash);
|
||||
}
|
||||
|
||||
|
@ -29,6 +29,7 @@ import bisq.network.p2p.storage.persistence.AppendOnlyDataStoreService;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.crypto.CryptoException;
|
||||
import bisq.common.crypto.Hash;
|
||||
import bisq.common.crypto.KeyRing;
|
||||
import bisq.common.crypto.Sig;
|
||||
import bisq.common.util.Utilities;
|
||||
@ -52,25 +53,28 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.Stack;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class SignedWitnessService {
|
||||
public static final long SIGNER_AGE_DAYS = 30;
|
||||
private static final long SIGNER_AGE = SIGNER_AGE_DAYS * ChronoUnit.DAYS.getDuration().toMillis();
|
||||
static final Coin MINIMUM_TRADE_AMOUNT_FOR_SIGNING = Coin.parseCoin("0.0025");
|
||||
public static final Coin MINIMUM_TRADE_AMOUNT_FOR_SIGNING = Coin.parseCoin("0.0025");
|
||||
|
||||
private final KeyRing keyRing;
|
||||
private final P2PService p2PService;
|
||||
private final ArbitratorManager arbitratorManager;
|
||||
private final User user;
|
||||
|
||||
@Getter
|
||||
private final Map<P2PDataStorage.ByteArray, SignedWitness> signedWitnessMap = new HashMap<>();
|
||||
private final FilterManager filterManager;
|
||||
|
||||
@ -123,6 +127,8 @@ public class SignedWitnessService {
|
||||
}
|
||||
});
|
||||
}
|
||||
// TODO: Enable cleaning of signed witness list when necessary
|
||||
// cleanSignedWitnesses();
|
||||
}
|
||||
|
||||
private void onBootstrapComplete() {
|
||||
@ -176,7 +182,14 @@ public class SignedWitnessService {
|
||||
.anyMatch(ownerPubKey -> filterManager.isSignerPubKeyBanned(Utils.HEX.encode(ownerPubKey)));
|
||||
}
|
||||
|
||||
public String ownerPubKey(AccountAgeWitness accountAgeWitness) {
|
||||
private byte[] ownerPubKey(AccountAgeWitness accountAgeWitness) {
|
||||
return getSignedWitnessSet(accountAgeWitness).stream()
|
||||
.map(SignedWitness::getWitnessOwnerPubKey)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
public String ownerPubKeyAsString(AccountAgeWitness accountAgeWitness) {
|
||||
return getSignedWitnessSet(accountAgeWitness).stream()
|
||||
.map(signedWitness -> Utils.HEX.encode(signedWitness.getWitnessOwnerPubKey()))
|
||||
.findFirst()
|
||||
@ -195,9 +208,42 @@ public class SignedWitnessService {
|
||||
AccountAgeWitness accountAgeWitness,
|
||||
ECKey key,
|
||||
PublicKey peersPubKey) {
|
||||
signAccountAgeWitness(tradeAmount, accountAgeWitness, key, peersPubKey.getEncoded(), new Date().getTime());
|
||||
}
|
||||
|
||||
// Arbitrators sign with EC key
|
||||
public String signAccountAgeWitness(AccountAgeWitness accountAgeWitness,
|
||||
ECKey key,
|
||||
byte[] peersPubKey,
|
||||
long time) {
|
||||
var witnessPubKey = peersPubKey == null ? ownerPubKey(accountAgeWitness) : peersPubKey;
|
||||
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,
|
||||
ECKey key,
|
||||
byte[] peersPubKey,
|
||||
long time) {
|
||||
if (isSignedAccountAgeWitness(accountAgeWitness)) {
|
||||
log.warn("Arbitrator trying to sign already signed accountagewitness {}", accountAgeWitness.toString());
|
||||
return;
|
||||
var err = "Arbitrator trying to sign already signed accountagewitness " + accountAgeWitness.toString();
|
||||
log.warn(err);
|
||||
return err;
|
||||
}
|
||||
if (peersPubKey == null) {
|
||||
var err = "Trying to sign accountAgeWitness " + accountAgeWitness.toString() + "\nwith owner pubkey=null";
|
||||
log.warn(err);
|
||||
return err;
|
||||
}
|
||||
|
||||
String accountAgeWitnessHashAsHex = Utilities.encodeToHex(accountAgeWitness.getHash());
|
||||
@ -206,11 +252,12 @@ public class SignedWitnessService {
|
||||
accountAgeWitness.getHash(),
|
||||
signatureBase64.getBytes(Charsets.UTF_8),
|
||||
key.getPubKey(),
|
||||
peersPubKey.getEncoded(),
|
||||
new Date().getTime(),
|
||||
peersPubKey,
|
||||
time,
|
||||
tradeAmount.value);
|
||||
publishSignedWitness(signedWitness);
|
||||
log.info("Arbitrator signed witness {}", signedWitness.toString());
|
||||
return "";
|
||||
}
|
||||
|
||||
public void selfSignAccountAgeWitness(AccountAgeWitness accountAgeWitness) throws CryptoException {
|
||||
@ -306,6 +353,24 @@ public class SignedWitnessService {
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public Set<SignedWitness> getRootSignedWitnessSet(boolean includeSignedByArbitrator) {
|
||||
return signedWitnessMap.values().stream()
|
||||
.filter(witness -> getSignedWitnessSetByOwnerPubKey(witness.getSignerPubKey(), new Stack<>()).isEmpty())
|
||||
.filter(witness -> includeSignedByArbitrator ||
|
||||
witness.getVerificationMethod() != SignedWitness.VerificationMethod.ARBITRATOR)
|
||||
.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
|
||||
// witnessOwnerPubKey
|
||||
private Set<SignedWitness> getSignedWitnessSetByOwnerPubKey(byte[] ownerPubKey,
|
||||
@ -405,7 +470,6 @@ public class SignedWitnessService {
|
||||
|
||||
@VisibleForTesting
|
||||
void addToMap(SignedWitness signedWitness) {
|
||||
// TODO: Perhaps filter out all but one signedwitness per accountagewitness
|
||||
signedWitnessMap.putIfAbsent(signedWitness.getHashAsByteArray(), signedWitness);
|
||||
}
|
||||
|
||||
@ -420,4 +484,19 @@ public class SignedWitnessService {
|
||||
private void doRepublishAllSignedWitnesses() {
|
||||
signedWitnessMap.forEach((e, signedWitness) -> p2PService.addPersistableNetworkPayload(signedWitness, true));
|
||||
}
|
||||
|
||||
// Remove SignedWitnesses that are signed by TRADE that also have an ARBITRATOR signature
|
||||
// for the same ownerPubKey and AccountAgeWitnessHash
|
||||
// private void cleanSignedWitnesses() {
|
||||
// var orphans = getRootSignedWitnessSet(false);
|
||||
// var signedWitnessesCopy = new HashSet<>(signedWitnessMap.values());
|
||||
// signedWitnessesCopy.forEach(sw -> orphans.forEach(orphan -> {
|
||||
// if (sw.getVerificationMethod() == SignedWitness.VerificationMethod.ARBITRATOR &&
|
||||
// Arrays.equals(sw.getWitnessOwnerPubKey(), orphan.getWitnessOwnerPubKey()) &&
|
||||
// Arrays.equals(sw.getAccountAgeWitnessHash(), orphan.getAccountAgeWitnessHash())) {
|
||||
// signedWitnessMap.remove(orphan.getHashAsByteArray());
|
||||
// log.info("Remove duplicate SignedWitness: {}", orphan.toString());
|
||||
// }
|
||||
// }));
|
||||
// }
|
||||
}
|
||||
|
@ -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;
|
||||
@ -33,7 +34,6 @@ import bisq.core.payment.payload.PaymentMethod;
|
||||
import bisq.core.support.dispute.Dispute;
|
||||
import bisq.core.support.dispute.DisputeResult;
|
||||
import bisq.core.support.dispute.arbitration.TraderDataItem;
|
||||
import bisq.core.trade.Contract;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.protocol.TradingPeer;
|
||||
import bisq.core.user.User;
|
||||
@ -72,14 +72,14 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
@ -130,7 +130,10 @@ public class AccountAgeWitnessService {
|
||||
private final SignedWitnessService signedWitnessService;
|
||||
private final ChargeBackRisk chargeBackRisk;
|
||||
private final FilterManager filterManager;
|
||||
@Getter
|
||||
private final AccountAgeWitnessUtils accountAgeWitnessUtils;
|
||||
|
||||
@Getter
|
||||
private final Map<P2PDataStorage.ByteArray, AccountAgeWitness> accountAgeWitnessMap = new HashMap<>();
|
||||
|
||||
|
||||
@ -155,6 +158,11 @@ public class AccountAgeWitnessService {
|
||||
this.chargeBackRisk = chargeBackRisk;
|
||||
this.filterManager = filterManager;
|
||||
|
||||
accountAgeWitnessUtils = new AccountAgeWitnessUtils(
|
||||
this,
|
||||
signedWitnessService,
|
||||
keyRing);
|
||||
|
||||
// We need to add that early (before onAllServicesInitialized) as it will be used at startup.
|
||||
appendOnlyDataStoreService.addService(accountAgeWitnessStorageService);
|
||||
}
|
||||
@ -225,11 +233,11 @@ public class AccountAgeWitnessService {
|
||||
|
||||
public byte[] getPeerAccountAgeWitnessHash(Trade trade) {
|
||||
return findTradePeerWitness(trade)
|
||||
.map(accountAgeWitness -> accountAgeWitness.getHash())
|
||||
.map(AccountAgeWitness::getHash)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private byte[] getAccountInputDataWithSalt(PaymentAccountPayload paymentAccountPayload) {
|
||||
byte[] getAccountInputDataWithSalt(PaymentAccountPayload paymentAccountPayload) {
|
||||
return Utilities.concatenateByteArrays(paymentAccountPayload.getAgeWitnessInputData(), paymentAccountPayload.getSalt());
|
||||
}
|
||||
|
||||
@ -241,8 +249,8 @@ public class AccountAgeWitnessService {
|
||||
return new AccountAgeWitness(hash, new Date().getTime());
|
||||
}
|
||||
|
||||
private Optional<AccountAgeWitness> findWitness(PaymentAccountPayload paymentAccountPayload,
|
||||
PubKeyRing pubKeyRing) {
|
||||
Optional<AccountAgeWitness> findWitness(PaymentAccountPayload paymentAccountPayload,
|
||||
PubKeyRing pubKeyRing) {
|
||||
byte[] accountInputDataWithSalt = getAccountInputDataWithSalt(paymentAccountPayload);
|
||||
byte[] hash = Hash.getSha256Ripemd160hash(Utilities.concatenateByteArrays(accountInputDataWithSalt,
|
||||
pubKeyRing.getSignaturePubKeyBytes()));
|
||||
@ -634,6 +642,32 @@ public class AccountAgeWitnessService {
|
||||
signedWitnessService.signAccountAgeWitness(tradeAmount, accountAgeWitness, key, peersPubKey);
|
||||
}
|
||||
|
||||
public String arbitratorSignOrphanWitness(AccountAgeWitness accountAgeWitness,
|
||||
ECKey key,
|
||||
long time) {
|
||||
// Find AccountAgeWitness as signedwitness
|
||||
var signedWitness = signedWitnessService.getSignedWitnessMap().values().stream()
|
||||
.filter(sw -> Arrays.equals(sw.getAccountAgeWitnessHash(), accountAgeWitness.getHash()))
|
||||
.findAny()
|
||||
.orElse(null);
|
||||
checkNotNull(signedWitness);
|
||||
return signedWitnessService.signAccountAgeWitness(accountAgeWitness, key, signedWitness.getWitnessOwnerPubKey(),
|
||||
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,
|
||||
long time) {
|
||||
signedWitnessService.signAccountAgeWitness(accountAgeWitness, key, tradersPubKey, time);
|
||||
}
|
||||
|
||||
public void traderSignPeersAccountAgeWitness(Trade trade) {
|
||||
AccountAgeWitness peersWitness = findTradePeerWitness(trade).orElse(null);
|
||||
Coin tradeAmount = trade.getTradeAmount();
|
||||
@ -758,7 +792,7 @@ public class AccountAgeWitnessService {
|
||||
public SignState getSignState(AccountAgeWitness accountAgeWitness) {
|
||||
// Add hash to sign state info when running in debug mode
|
||||
String hash = log.isDebugEnabled() ? Utilities.bytesAsHexString(accountAgeWitness.getHash()) + "\n" +
|
||||
signedWitnessService.ownerPubKey(accountAgeWitness) : "";
|
||||
signedWitnessService.ownerPubKeyAsString(accountAgeWitness) : "";
|
||||
if (signedWitnessService.isFilteredWitness(accountAgeWitness)) {
|
||||
return SignState.BANNED.addHash(hash);
|
||||
}
|
||||
@ -779,6 +813,13 @@ public class AccountAgeWitnessService {
|
||||
}
|
||||
}
|
||||
|
||||
public Set<AccountAgeWitness> getOrphanSignedWitnesses() {
|
||||
return signedWitnessService.getRootSignedWitnessSet(false).stream()
|
||||
.map(signedWitness -> getWitnessByHash(signedWitness.getAccountAgeWitnessHash()).orElse(null))
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public void signSameNameAccounts() {
|
||||
// Collect accounts that have ownerId to sign unsigned accounts with the same ownderId
|
||||
var signerAccounts = Objects.requireNonNull(user.getPaymentAccounts()).stream()
|
||||
@ -803,63 +844,7 @@ public class AccountAgeWitnessService {
|
||||
}));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Debug logs
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
private String getWitnessDebugLog(PaymentAccountPayload paymentAccountPayload,
|
||||
PubKeyRing pubKeyRing) {
|
||||
Optional<AccountAgeWitness> accountAgeWitness = findWitness(paymentAccountPayload, pubKeyRing);
|
||||
if (!accountAgeWitness.isPresent()) {
|
||||
byte[] accountInputDataWithSalt = getAccountInputDataWithSalt(paymentAccountPayload);
|
||||
byte[] hash = Hash.getSha256Ripemd160hash(Utilities.concatenateByteArrays(accountInputDataWithSalt,
|
||||
pubKeyRing.getSignaturePubKeyBytes()));
|
||||
return "No accountAgeWitness found for paymentAccountPayload with hash " + Utilities.bytesAsHexString(hash);
|
||||
}
|
||||
|
||||
SignState signState = getSignState(accountAgeWitness.get());
|
||||
return signState.name() + " " + signState.getPresentation() +
|
||||
"\n" + accountAgeWitness.toString();
|
||||
}
|
||||
|
||||
public void witnessDebugLog(Trade trade, @Nullable AccountAgeWitness myWitness) {
|
||||
// Log to find why accounts sometimes don't get signed as expected
|
||||
// TODO: Demote to debug or remove once account signing is working ok
|
||||
checkNotNull(trade.getContract());
|
||||
checkNotNull(trade.getContract().getBuyerPaymentAccountPayload());
|
||||
boolean checkingSignTrade = true;
|
||||
boolean isBuyer = trade.getContract().isMyRoleBuyer(keyRing.getPubKeyRing());
|
||||
AccountAgeWitness witness = myWitness;
|
||||
if (witness == null) {
|
||||
witness = isBuyer ?
|
||||
getMyWitness(trade.getContract().getBuyerPaymentAccountPayload()) :
|
||||
getMyWitness(trade.getContract().getSellerPaymentAccountPayload());
|
||||
checkingSignTrade = false;
|
||||
}
|
||||
boolean isSignWitnessTrade = accountIsSigner(witness) &&
|
||||
!peerHasSignedWitness(trade) &&
|
||||
tradeAmountIsSufficient(trade.getTradeAmount());
|
||||
log.info("AccountSigning: " +
|
||||
"\ntradeId: {}" +
|
||||
"\nis buyer: {}" +
|
||||
"\nbuyer account age witness info: {}" +
|
||||
"\nseller account age witness info: {}" +
|
||||
"\nchecking for sign trade: {}" +
|
||||
"\nis myWitness signer: {}" +
|
||||
"\npeer has signed witness: {}" +
|
||||
"\ntrade amount: {}" +
|
||||
"\ntrade amount is sufficient: {}" +
|
||||
"\nisSignWitnessTrade: {}",
|
||||
trade.getId(),
|
||||
isBuyer,
|
||||
getWitnessDebugLog(trade.getContract().getBuyerPaymentAccountPayload(),
|
||||
trade.getContract().getBuyerPubKeyRing()),
|
||||
getWitnessDebugLog(trade.getContract().getSellerPaymentAccountPayload(),
|
||||
trade.getContract().getSellerPubKeyRing()),
|
||||
checkingSignTrade, // Following cases added to use same logic as in seller signing check
|
||||
accountIsSigner(witness),
|
||||
peerHasSignedWitness(trade),
|
||||
trade.getTradeAmount(),
|
||||
tradeAmountIsSufficient(trade.getTradeAmount()),
|
||||
isSignWitnessTrade);
|
||||
public Set<SignedWitness> getUnsignedSignerPubKeys() {
|
||||
return signedWitnessService.getUnsignedSignerPubKeys();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,171 @@
|
||||
/*
|
||||
* 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.core.account.witness;
|
||||
|
||||
import bisq.core.account.sign.SignedWitness;
|
||||
import bisq.core.account.sign.SignedWitnessService;
|
||||
import bisq.core.payment.payload.PaymentAccountPayload;
|
||||
import bisq.core.trade.Trade;
|
||||
|
||||
import bisq.network.p2p.storage.P2PDataStorage;
|
||||
|
||||
import bisq.common.crypto.Hash;
|
||||
import bisq.common.crypto.KeyRing;
|
||||
import bisq.common.crypto.PubKeyRing;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.Stack;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
public class AccountAgeWitnessUtils {
|
||||
private final AccountAgeWitnessService accountAgeWitnessService;
|
||||
private final SignedWitnessService signedWitnessService;
|
||||
private final KeyRing keyRing;
|
||||
|
||||
AccountAgeWitnessUtils(AccountAgeWitnessService accountAgeWitnessService,
|
||||
SignedWitnessService signedWitnessService,
|
||||
KeyRing keyRing) {
|
||||
this.accountAgeWitnessService = accountAgeWitnessService;
|
||||
this.signedWitnessService = signedWitnessService;
|
||||
this.keyRing = keyRing;
|
||||
}
|
||||
|
||||
// Log tree of signed witnesses
|
||||
public void logSignedWitnesses() {
|
||||
var orphanSigners = signedWitnessService.getRootSignedWitnessSet(true);
|
||||
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),
|
||||
w.getDate());
|
||||
logChild(w, " ", new Stack<>());
|
||||
});
|
||||
}
|
||||
|
||||
private void logChild(SignedWitness sigWit, String initString, Stack<P2PDataStorage.ByteArray> excluded) {
|
||||
var allSig = signedWitnessService.getSignedWitnessMap();
|
||||
log.info("{}AEW: {} PKH: {} time: {}", initString,
|
||||
Utilities.bytesAsHexString(sigWit.getAccountAgeWitnessHash()).substring(0, 7),
|
||||
Utilities.bytesAsHexString(Hash.getRipemd160hash(sigWit.getWitnessOwnerPubKey())).substring(0, 7),
|
||||
sigWit.getDate());
|
||||
allSig.values().forEach(w -> {
|
||||
if (!excluded.contains(new P2PDataStorage.ByteArray(w.getWitnessOwnerPubKey())) &&
|
||||
Arrays.equals(w.getSignerPubKey(), sigWit.getWitnessOwnerPubKey())) {
|
||||
excluded.push(new P2PDataStorage.ByteArray(w.getWitnessOwnerPubKey()));
|
||||
logChild(w, initString + " ", excluded);
|
||||
excluded.pop();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Log signers per
|
||||
public void logSigners() {
|
||||
log.info("Signers per AEW");
|
||||
var allSig = signedWitnessService.getSignedWitnessMap();
|
||||
allSig.values().forEach(w -> {
|
||||
log.info("AEW {}", Utilities.bytesAsHexString(w.getAccountAgeWitnessHash()));
|
||||
allSig.values().forEach(ww -> {
|
||||
if (Arrays.equals(w.getSignerPubKey(), ww.getWitnessOwnerPubKey())) {
|
||||
log.info(" {}", Utilities.bytesAsHexString(ww.getAccountAgeWitnessHash()));
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public void logUnsignedSignerPubKeys() {
|
||||
log.info("Unsigned signer pubkeys");
|
||||
signedWitnessService.getUnsignedSignerPubKeys().forEach(signedWitness ->
|
||||
log.info("PK hash {} date {}",
|
||||
Utilities.bytesAsHexString(Hash.getRipemd160hash(signedWitness.getSignerPubKey())),
|
||||
signedWitness.getDate()));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Debug logs
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private String getWitnessDebugLog(PaymentAccountPayload paymentAccountPayload,
|
||||
PubKeyRing pubKeyRing) {
|
||||
Optional<AccountAgeWitness> accountAgeWitness =
|
||||
accountAgeWitnessService.findWitness(paymentAccountPayload, pubKeyRing);
|
||||
if (!accountAgeWitness.isPresent()) {
|
||||
byte[] accountInputDataWithSalt =
|
||||
accountAgeWitnessService.getAccountInputDataWithSalt(paymentAccountPayload);
|
||||
byte[] hash = Hash.getSha256Ripemd160hash(Utilities.concatenateByteArrays(accountInputDataWithSalt,
|
||||
pubKeyRing.getSignaturePubKeyBytes()));
|
||||
return "No accountAgeWitness found for paymentAccountPayload with hash " + Utilities.bytesAsHexString(hash);
|
||||
}
|
||||
|
||||
AccountAgeWitnessService.SignState signState =
|
||||
accountAgeWitnessService.getSignState(accountAgeWitness.get());
|
||||
return signState.name() + " " + signState.getPresentation() +
|
||||
"\n" + accountAgeWitness.toString();
|
||||
}
|
||||
|
||||
public void witnessDebugLog(Trade trade, @Nullable AccountAgeWitness myWitness) {
|
||||
// Log to find why accounts sometimes don't get signed as expected
|
||||
// TODO: Demote to debug or remove once account signing is working ok
|
||||
checkNotNull(trade.getContract());
|
||||
checkNotNull(trade.getContract().getBuyerPaymentAccountPayload());
|
||||
boolean checkingSignTrade = true;
|
||||
boolean isBuyer = trade.getContract().isMyRoleBuyer(keyRing.getPubKeyRing());
|
||||
AccountAgeWitness witness = myWitness;
|
||||
if (witness == null) {
|
||||
witness = isBuyer ?
|
||||
accountAgeWitnessService.getMyWitness(trade.getContract().getBuyerPaymentAccountPayload()) :
|
||||
accountAgeWitnessService.getMyWitness(trade.getContract().getSellerPaymentAccountPayload());
|
||||
checkingSignTrade = false;
|
||||
}
|
||||
boolean isSignWitnessTrade = accountAgeWitnessService.accountIsSigner(witness) &&
|
||||
!accountAgeWitnessService.peerHasSignedWitness(trade) &&
|
||||
accountAgeWitnessService.tradeAmountIsSufficient(trade.getTradeAmount());
|
||||
log.info("AccountSigning: " +
|
||||
"\ntradeId: {}" +
|
||||
"\nis buyer: {}" +
|
||||
"\nbuyer account age witness info: {}" +
|
||||
"\nseller account age witness info: {}" +
|
||||
"\nchecking for sign trade: {}" +
|
||||
"\nis myWitness signer: {}" +
|
||||
"\npeer has signed witness: {}" +
|
||||
"\ntrade amount: {}" +
|
||||
"\ntrade amount is sufficient: {}" +
|
||||
"\nisSignWitnessTrade: {}",
|
||||
trade.getId(),
|
||||
isBuyer,
|
||||
getWitnessDebugLog(trade.getContract().getBuyerPaymentAccountPayload(),
|
||||
trade.getContract().getBuyerPubKeyRing()),
|
||||
getWitnessDebugLog(trade.getContract().getSellerPaymentAccountPayload(),
|
||||
trade.getContract().getSellerPubKeyRing()),
|
||||
checkingSignTrade, // Following cases added to use same logic as in seller signing check
|
||||
accountAgeWitnessService.accountIsSigner(witness),
|
||||
accountAgeWitnessService.peerHasSignedWitness(trade),
|
||||
trade.getTradeAmount(),
|
||||
accountAgeWitnessService.tradeAmountIsSufficient(trade.getTradeAmount()),
|
||||
isSignWitnessTrade);
|
||||
}
|
||||
}
|
@ -358,6 +358,6 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||
}
|
||||
|
||||
void logTrade(Trade trade) {
|
||||
accountAgeWitnessService.witnessDebugLog(trade, null);
|
||||
accountAgeWitnessService.getAccountAgeWitnessUtils().witnessDebugLog(trade, null);
|
||||
}
|
||||
}
|
||||
|
@ -2730,6 +2730,23 @@ 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.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
|
||||
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
|
||||
####################################################################
|
||||
|
@ -37,6 +37,7 @@ import bisq.network.p2p.P2PService;
|
||||
import bisq.network.p2p.storage.persistence.AppendOnlyDataStoreService;
|
||||
|
||||
import bisq.common.crypto.CryptoException;
|
||||
import bisq.common.crypto.Hash;
|
||||
import bisq.common.crypto.KeyRing;
|
||||
import bisq.common.crypto.KeyStorage;
|
||||
import bisq.common.crypto.PubKeyRing;
|
||||
@ -53,9 +54,11 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
@ -81,12 +84,25 @@ public class AccountAgeWitnessServiceTest {
|
||||
private AccountAgeWitnessService service;
|
||||
private ChargeBackRisk chargeBackRisk;
|
||||
private FilterManager filterManager;
|
||||
private File dir1;
|
||||
private File dir2;
|
||||
private File dir3;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
public void setup() throws IOException {
|
||||
KeyRing keyRing = mock(KeyRing.class);
|
||||
setupService(keyRing);
|
||||
keypair = Sig.generateKeyPair();
|
||||
publicKey = keypair.getPublic();
|
||||
// Setup temp storage dir
|
||||
dir1 = makeDir("temp_tests1");
|
||||
dir2 = makeDir("temp_tests1");
|
||||
dir3 = makeDir("temp_tests1");
|
||||
}
|
||||
|
||||
private void setupService(KeyRing keyRing) {
|
||||
chargeBackRisk = mock(ChargeBackRisk.class);
|
||||
AppendOnlyDataStoreService dataStoreService = mock(AppendOnlyDataStoreService.class);
|
||||
KeyRing keyRing = mock(KeyRing.class);
|
||||
P2PService p2pService = mock(P2PService.class);
|
||||
ArbitratorManager arbitratorManager = mock(ArbitratorManager.class);
|
||||
when(arbitratorManager.isPublicKeyInList(any())).thenReturn(true);
|
||||
@ -94,8 +110,13 @@ public class AccountAgeWitnessServiceTest {
|
||||
filterManager = mock(FilterManager.class);
|
||||
signedWitnessService = new SignedWitnessService(keyRing, p2pService, arbitratorManager, null, appendOnlyDataStoreService, null, filterManager);
|
||||
service = new AccountAgeWitnessService(null, null, null, signedWitnessService, chargeBackRisk, null, dataStoreService, filterManager);
|
||||
keypair = Sig.generateKeyPair();
|
||||
publicKey = keypair.getPublic();
|
||||
}
|
||||
|
||||
private File makeDir(String name) throws IOException {
|
||||
var dir = File.createTempFile(name, "");
|
||||
dir.delete();
|
||||
dir.mkdir();
|
||||
return dir;
|
||||
}
|
||||
|
||||
@After
|
||||
@ -105,21 +126,21 @@ public class AccountAgeWitnessServiceTest {
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
public void testIsTradeDateAfterReleaseDate() throws CryptoException {
|
||||
Date ageWitnessReleaseDate = new GregorianCalendar(2017, 9, 23).getTime();
|
||||
Date tradeDate = new GregorianCalendar(2017, 10, 1).getTime();
|
||||
public void testIsTradeDateAfterReleaseDate() {
|
||||
Date ageWitnessReleaseDate = new GregorianCalendar(2017, Calendar.OCTOBER, 23).getTime();
|
||||
Date tradeDate = new GregorianCalendar(2017, Calendar.NOVEMBER, 1).getTime();
|
||||
assertTrue(service.isDateAfterReleaseDate(tradeDate.getTime(), ageWitnessReleaseDate, errorMessage -> {
|
||||
}));
|
||||
tradeDate = new GregorianCalendar(2017, 9, 23).getTime();
|
||||
tradeDate = new GregorianCalendar(2017, Calendar.OCTOBER, 23).getTime();
|
||||
assertTrue(service.isDateAfterReleaseDate(tradeDate.getTime(), ageWitnessReleaseDate, errorMessage -> {
|
||||
}));
|
||||
tradeDate = new GregorianCalendar(2017, 9, 22, 0, 0, 1).getTime();
|
||||
tradeDate = new GregorianCalendar(2017, Calendar.OCTOBER, 22, 0, 0, 1).getTime();
|
||||
assertTrue(service.isDateAfterReleaseDate(tradeDate.getTime(), ageWitnessReleaseDate, errorMessage -> {
|
||||
}));
|
||||
tradeDate = new GregorianCalendar(2017, 9, 22).getTime();
|
||||
tradeDate = new GregorianCalendar(2017, Calendar.OCTOBER, 22).getTime();
|
||||
assertFalse(service.isDateAfterReleaseDate(tradeDate.getTime(), ageWitnessReleaseDate, errorMessage -> {
|
||||
}));
|
||||
tradeDate = new GregorianCalendar(2017, 9, 21).getTime();
|
||||
tradeDate = new GregorianCalendar(2017, Calendar.OCTOBER, 21).getTime();
|
||||
assertFalse(service.isDateAfterReleaseDate(tradeDate.getTime(), ageWitnessReleaseDate, errorMessage -> {
|
||||
}));
|
||||
}
|
||||
@ -140,15 +161,7 @@ public class AccountAgeWitnessServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testArbitratorSignWitness() throws IOException {
|
||||
// Setup temp storage dir
|
||||
File dir1 = File.createTempFile("temp_tests1", "");
|
||||
dir1.delete();
|
||||
dir1.mkdir();
|
||||
File dir2 = File.createTempFile("temp_tests1", "");
|
||||
dir2.delete();
|
||||
dir2.mkdir();
|
||||
|
||||
public void testArbitratorSignWitness() {
|
||||
KeyRing buyerKeyRing = new KeyRing(new KeyStorage(dir1));
|
||||
KeyRing sellerKeyRing = new KeyRing(new KeyStorage(dir2));
|
||||
|
||||
@ -238,13 +251,110 @@ public class AccountAgeWitnessServiceTest {
|
||||
buyerPubKeyRing.getSignaturePubKeyBytes()).stream()
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
assert foundBuyerSignedWitness != null;
|
||||
assertEquals(Utilities.bytesAsHexString(foundBuyerSignedWitness.getAccountAgeWitnessHash()),
|
||||
Utilities.bytesAsHexString(buyerAccountAgeWitness.getHash()));
|
||||
SignedWitness foundSellerSignedWitness = signedWitnessService.getSignedWitnessSetByOwnerPubKey(
|
||||
sellerPubKeyRing.getSignaturePubKeyBytes()).stream()
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
assert foundSellerSignedWitness != null;
|
||||
assertEquals(Utilities.bytesAsHexString(foundSellerSignedWitness.getAccountAgeWitnessHash()),
|
||||
Utilities.bytesAsHexString(sellerAccountAgeWitness.getHash()));
|
||||
}
|
||||
|
||||
// Create a tree of signed witnesses Arb -(SWA)-> aew1 -(SW1)-> aew2 -(SW2)-> aew3
|
||||
// Delete SWA signature, none of the account age witnesses are considered signed
|
||||
// Sign a dummy AccountAgeWitness using the signerPubkey from SW1; aew2 and aew3 are not considered signed. The
|
||||
// lost SignedWitness isn't possible to recover so aew1 is still not signed, but it's pubkey is a signer.
|
||||
@Test
|
||||
public void testArbitratorSignDummyWitness() throws CryptoException {
|
||||
ECKey arbitratorKey = new ECKey();
|
||||
// Init 2 user accounts
|
||||
var user1KeyRing = new KeyRing(new KeyStorage(dir1));
|
||||
var user2KeyRing = new KeyRing(new KeyStorage(dir2));
|
||||
var user3KeyRing = new KeyRing(new KeyStorage(dir3));
|
||||
var pubKeyRing1 = user1KeyRing.getPubKeyRing();
|
||||
var pubKeyRing2 = user2KeyRing.getPubKeyRing();
|
||||
var pubKeyRing3 = user3KeyRing.getPubKeyRing();
|
||||
var account1 = new SepaAccountPayload(PaymentMethod.SEPA_ID, "1", CountryUtil.getAllSepaCountries());
|
||||
var account2 = new SepaAccountPayload(PaymentMethod.SEPA_ID, "2", CountryUtil.getAllSepaCountries());
|
||||
var account3 = new SepaAccountPayload(PaymentMethod.SEPA_ID, "3", CountryUtil.getAllSepaCountries());
|
||||
var aew1 = service.getNewWitness(account1, pubKeyRing1);
|
||||
var aew2 = service.getNewWitness(account2, pubKeyRing2);
|
||||
var aew3 = service.getNewWitness(account3, pubKeyRing3);
|
||||
// Backdate witness1 70 days
|
||||
aew1 = new AccountAgeWitness(aew1.getHash(), new Date().getTime() - TimeUnit.DAYS.toMillis(70));
|
||||
aew2 = new AccountAgeWitness(aew2.getHash(), new Date().getTime() - TimeUnit.DAYS.toMillis(35));
|
||||
aew3 = new AccountAgeWitness(aew3.getHash(), new Date().getTime() - TimeUnit.DAYS.toMillis(1));
|
||||
service.addToMap(aew1);
|
||||
service.addToMap(aew2);
|
||||
service.addToMap(aew3);
|
||||
|
||||
// Test as user1. It's still possible to sign as arbitrator since the ECKey is passed as an argument.
|
||||
setupService(user1KeyRing);
|
||||
|
||||
// Arbitrator signs user1
|
||||
service.arbitratorSignAccountAgeWitness(aew1, arbitratorKey, pubKeyRing1.getSignaturePubKeyBytes(),
|
||||
aew1.getDate());
|
||||
// user1 signs user2
|
||||
signAccountAgeWitness(aew2, pubKeyRing2.getSignaturePubKey(), aew2.getDate(), user1KeyRing);
|
||||
// user2 signs user3
|
||||
signAccountAgeWitness(aew3, pubKeyRing3.getSignaturePubKey(), aew3.getDate(), user2KeyRing);
|
||||
signedWitnessService.signAccountAgeWitness(SignedWitnessService.MINIMUM_TRADE_AMOUNT_FOR_SIGNING, aew2,
|
||||
pubKeyRing2.getSignaturePubKey());
|
||||
assertTrue(service.accountIsSigner(aew1));
|
||||
assertTrue(service.accountIsSigner(aew2));
|
||||
assertFalse(service.accountIsSigner(aew3));
|
||||
assertTrue(signedWitnessService.isSignedAccountAgeWitness(aew3));
|
||||
|
||||
// Remove SignedWitness signed by arbitrator
|
||||
@SuppressWarnings("OptionalGetWithoutIsPresent")
|
||||
var signedWitnessArb = signedWitnessService.getSignedWitnessMap().values().stream()
|
||||
.filter(sw -> sw.getVerificationMethod() == SignedWitness.VerificationMethod.ARBITRATOR)
|
||||
.findAny()
|
||||
.get();
|
||||
signedWitnessService.getSignedWitnessMap().remove(signedWitnessArb.getHashAsByteArray());
|
||||
assertEquals(signedWitnessService.getSignedWitnessMap().size(), 2);
|
||||
|
||||
// Check that no account age witness is a signer
|
||||
assertFalse(service.accountIsSigner(aew1));
|
||||
assertFalse(service.accountIsSigner(aew2));
|
||||
assertFalse(service.accountIsSigner(aew3));
|
||||
assertFalse(signedWitnessService.isSignedAccountAgeWitness(aew2));
|
||||
|
||||
// Sign dummy AccountAgeWitness using signer key from SW_1
|
||||
assertEquals(signedWitnessService.getRootSignedWitnessSet(false).size(), 1);
|
||||
|
||||
// TODO: move this to accountagewitnessservice
|
||||
@SuppressWarnings("OptionalGetWithoutIsPresent")
|
||||
var orphanedSignedWitness = signedWitnessService.getRootSignedWitnessSet(false).stream().findAny().get();
|
||||
var dummyAccountAgeWitnessHash = Hash.getRipemd160hash(orphanedSignedWitness.getSignerPubKey());
|
||||
var dummyAEW = new AccountAgeWitness(dummyAccountAgeWitnessHash,
|
||||
orphanedSignedWitness.getDate() -
|
||||
(TimeUnit.DAYS.toMillis(SignedWitnessService.SIGNER_AGE_DAYS + 1)));
|
||||
service.arbitratorSignAccountAgeWitness(
|
||||
dummyAEW, arbitratorKey, orphanedSignedWitness.getSignerPubKey(), dummyAEW.getDate());
|
||||
|
||||
assertFalse(service.accountIsSigner(aew1));
|
||||
assertTrue(service.accountIsSigner(aew2));
|
||||
assertFalse(service.accountIsSigner(aew3));
|
||||
assertTrue(signedWitnessService.isSignedAccountAgeWitness(aew2));
|
||||
}
|
||||
|
||||
private void signAccountAgeWitness(AccountAgeWitness accountAgeWitness,
|
||||
PublicKey witnessOwnerPubKey,
|
||||
long time,
|
||||
KeyRing signerKeyRing) throws CryptoException {
|
||||
byte[] signature = Sig.sign(signerKeyRing.getSignatureKeyPair().getPrivate(), accountAgeWitness.getHash());
|
||||
SignedWitness signedWitness = new SignedWitness(SignedWitness.VerificationMethod.TRADE,
|
||||
accountAgeWitness.getHash(),
|
||||
signature,
|
||||
signerKeyRing.getSignatureKeyPair().getPublic().getEncoded(),
|
||||
witnessOwnerPubKey.getEncoded(),
|
||||
time,
|
||||
SignedWitnessService.MINIMUM_TRADE_AMOUNT_FOR_SIGNING.value);
|
||||
signedWitnessService.getSignedWitnessMap().putIfAbsent(signedWitness.getHashAsByteArray(), signedWitness);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,6 +16,8 @@ import bisq.core.payment.PaymentAccount;
|
||||
import bisq.core.payment.payload.PaymentMethod;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.app.DevEnv;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
@ -26,10 +28,14 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
|
||||
import javafx.beans.value.ChangeListener;
|
||||
|
||||
import javafx.event.EventHandler;
|
||||
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
import javafx.util.Callback;
|
||||
@ -41,8 +47,8 @@ public abstract class PaymentAccountsView<R extends Node, M extends ActivatableW
|
||||
protected ListView<PaymentAccount> paymentAccountsListView;
|
||||
private ChangeListener<PaymentAccount> paymentAccountChangeListener;
|
||||
protected Button addAccountButton, exportButton, importButton;
|
||||
SignedWitnessService signedWitnessService;
|
||||
protected AccountAgeWitnessService accountAgeWitnessService;
|
||||
private EventHandler<KeyEvent> keyEventEventHandler;
|
||||
|
||||
public PaymentAccountsView(M model, AccountAgeWitnessService accountAgeWitnessService) {
|
||||
super(model);
|
||||
@ -51,6 +57,16 @@ public abstract class PaymentAccountsView<R extends Node, M extends ActivatableW
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
keyEventEventHandler = event -> {
|
||||
if (Utilities.isCtrlShiftPressed(KeyCode.L, event)) {
|
||||
accountAgeWitnessService.getAccountAgeWitnessUtils().logSignedWitnesses();
|
||||
} else if (Utilities.isCtrlShiftPressed(KeyCode.S, event)) {
|
||||
accountAgeWitnessService.getAccountAgeWitnessUtils().logSigners();
|
||||
} else if (Utilities.isCtrlShiftPressed(KeyCode.U, event)) {
|
||||
accountAgeWitnessService.getAccountAgeWitnessUtils().logUnsignedSignerPubKeys();
|
||||
}
|
||||
};
|
||||
|
||||
buildForm();
|
||||
paymentAccountChangeListener = (observable, oldValue, newValue) -> {
|
||||
if (newValue != null)
|
||||
@ -68,6 +84,8 @@ public abstract class PaymentAccountsView<R extends Node, M extends ActivatableW
|
||||
addAccountButton.setOnAction(event -> addNewAccount());
|
||||
exportButton.setOnAction(event -> exportAccounts());
|
||||
importButton.setOnAction(event -> importAccounts());
|
||||
if (root.getScene() != null)
|
||||
root.getScene().addEventHandler(KeyEvent.KEY_RELEASED, keyEventEventHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -76,6 +94,8 @@ public abstract class PaymentAccountsView<R extends Node, M extends ActivatableW
|
||||
addAccountButton.setOnAction(null);
|
||||
exportButton.setOnAction(null);
|
||||
importButton.setOnAction(null);
|
||||
if (root.getScene() != null)
|
||||
root.getScene().removeEventHandler(KeyEvent.KEY_RELEASED, keyEventEventHandler);
|
||||
}
|
||||
|
||||
protected void onDeleteAccount(PaymentAccount paymentAccount) {
|
||||
|
@ -0,0 +1,208 @@
|
||||
/*
|
||||
* 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.witness.AccountAgeWitness;
|
||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
||||
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
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.layout.GridPane;
|
||||
import javafx.scene.layout.Priority;
|
||||
|
||||
import javafx.geometry.VPos;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.desktop.util.FormBuilder.*;
|
||||
|
||||
@Slf4j
|
||||
public class SignSpecificWitnessWindow extends Overlay<SignSpecificWitnessWindow> {
|
||||
|
||||
private InputTextField searchTextField;
|
||||
private JFXAutoCompletePopup<AccountAgeWitness> searchAutoComplete;
|
||||
private AccountAgeWitness selectedWitness;
|
||||
private DatePicker datePicker;
|
||||
private InputTextField privateKey;
|
||||
private final AccountAgeWitnessService accountAgeWitnessService;
|
||||
private final ArbitratorManager arbitratorManager;
|
||||
|
||||
|
||||
@Inject
|
||||
public SignSpecificWitnessWindow(AccountAgeWitnessService accountAgeWitnessService,
|
||||
ArbitratorManager arbitratorManager) {
|
||||
this.accountAgeWitnessService = accountAgeWitnessService;
|
||||
this.arbitratorManager = arbitratorManager;
|
||||
}
|
||||
|
||||
@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();
|
||||
addSelectWitnessContent();
|
||||
addButtons();
|
||||
applyStyles();
|
||||
|
||||
display();
|
||||
}
|
||||
|
||||
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("");
|
||||
}
|
||||
}
|
||||
});
|
||||
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());
|
||||
}
|
||||
});
|
||||
|
||||
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"));
|
||||
GridPane.setVgrow(privateKey, Priority.ALWAYS);
|
||||
GridPane.setValignment(privateKey, VPos.TOP);
|
||||
}
|
||||
|
||||
private void updateWitnessSelectionState() {
|
||||
actionButton.setDisable(selectedWitness == null || datePicker.getValue() == null);
|
||||
}
|
||||
|
||||
private void removeContent() {
|
||||
removeRowsFromGridPane(gridPane, 1, 3);
|
||||
rowIndex = 1;
|
||||
}
|
||||
|
||||
private void selectAccountAgeWitness() {
|
||||
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()));
|
||||
addECKeyField();
|
||||
((AutoTooltipButton) actionButton).updateText(Res.get("popup.accountSigning.confirmSingleAccount.button"));
|
||||
actionButton.setOnAction(a -> {
|
||||
var arbitratorKey = arbitratorManager.getRegistrationKey(privateKey.getText());
|
||||
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();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
new Popup().error(Res.get("popup.accountSigning.signAccounts.ECKey.error")).onClose(this::hide).show();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void addSuccessContent() {
|
||||
removeContent();
|
||||
closeButton.setVisible(false);
|
||||
closeButton.setManaged(false);
|
||||
headLineLabel.setText(Res.get("popup.accountSigning.successSingleAccount.success.headline"));
|
||||
var descriptionLabel = addMultilineLabel(gridPane, ++rowIndex,
|
||||
Res.get("popup.accountSigning.successSingleAccount.description",
|
||||
Utilities.bytesAsHexString(selectedWitness.getHash())));
|
||||
GridPane.setVgrow(descriptionLabel, Priority.ALWAYS);
|
||||
GridPane.setValignment(descriptionLabel, VPos.TOP);
|
||||
((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.singleAccountSelect.headline"), Res.get("shared.cancel"));
|
||||
|
||||
actionButton = buttonTuple.first;
|
||||
actionButton.setDisable(true);
|
||||
actionButton.setOnAction(e -> selectAccountAgeWitness());
|
||||
|
||||
closeButton = (AutoTooltipButton) buttonTuple.second;
|
||||
closeButton.setOnAction(e -> hide());
|
||||
|
||||
}
|
||||
}
|
@ -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.add2ButtonsAfterGroup;
|
||||
import static bisq.desktop.util.FormBuilder.addInputTextField;
|
||||
import static bisq.desktop.util.FormBuilder.addTopLabelListView;
|
||||
import static bisq.desktop.util.FormBuilder.removeRowsFromGridPane;
|
||||
|
||||
@Slf4j
|
||||
public class SignUnsignedPubKeysWindow extends Overlay<SignUnsignedPubKeysWindow> {
|
||||
|
||||
private ListView<SignedWitness> unsignedPubKeys = new ListView<>();
|
||||
private InputTextField privateKey;
|
||||
private final AccountAgeWitnessService accountAgeWitnessService;
|
||||
private final ArbitratorManager arbitratorManager;
|
||||
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"));
|
||||
ListView<SignedWitness> 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"));
|
||||
ListView<String> 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());
|
||||
|
||||
}
|
||||
}
|
@ -374,7 +374,7 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
|
||||
checkNotNull(trade.getOffer(), "offer must not be null");
|
||||
AccountAgeWitness myWitness = accountAgeWitnessService.getMyWitness(dataModel.getSellersPaymentAccountPayload());
|
||||
|
||||
accountAgeWitnessService.witnessDebugLog(trade, myWitness);
|
||||
accountAgeWitnessService.getAccountAgeWitnessUtils().witnessDebugLog(trade, myWitness);
|
||||
|
||||
return accountAgeWitnessService.accountIsSigner(myWitness) &&
|
||||
!accountAgeWitnessService.peerHasSignedWitness(trade) &&
|
||||
|
@ -23,6 +23,8 @@ import bisq.desktop.common.view.FxmlView;
|
||||
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;
|
||||
|
||||
@ -49,6 +51,8 @@ import javax.inject.Inject;
|
||||
public class ArbitratorView extends DisputeAgentView {
|
||||
|
||||
private final SignPaymentAccountsWindow signPaymentAccountsWindow;
|
||||
private final SignSpecificWitnessWindow signSpecificWitnessWindow;
|
||||
private final SignUnsignedPubKeysWindow signUnsignedPubKeysWindow;
|
||||
|
||||
@Inject
|
||||
public ArbitratorView(ArbitrationManager arbitrationManager,
|
||||
@ -61,7 +65,9 @@ public class ArbitratorView extends DisputeAgentView {
|
||||
TradeDetailsWindow tradeDetailsWindow,
|
||||
AccountAgeWitnessService accountAgeWitnessService,
|
||||
@Named(Config.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys,
|
||||
SignPaymentAccountsWindow signPaymentAccountsWindow) {
|
||||
SignPaymentAccountsWindow signPaymentAccountsWindow,
|
||||
SignSpecificWitnessWindow signSpecificWitnessWindow,
|
||||
SignUnsignedPubKeysWindow signUnsignedPubKeysWindow) {
|
||||
super(arbitrationManager,
|
||||
keyRing,
|
||||
tradeManager,
|
||||
@ -73,6 +79,8 @@ public class ArbitratorView extends DisputeAgentView {
|
||||
accountAgeWitnessService,
|
||||
useDevPrivilegeKeys);
|
||||
this.signPaymentAccountsWindow = signPaymentAccountsWindow;
|
||||
this.signSpecificWitnessWindow = signSpecificWitnessWindow;
|
||||
this.signUnsignedPubKeysWindow = signUnsignedPubKeysWindow;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -89,6 +97,10 @@ public class ArbitratorView extends DisputeAgentView {
|
||||
protected void handleKeyPressed(KeyEvent event) {
|
||||
if (Utilities.isAltOrCtrlPressed(KeyCode.S, event)) {
|
||||
signPaymentAccountsWindow.show();
|
||||
} else if (Utilities.isAltOrCtrlPressed(KeyCode.P, event)) {
|
||||
signSpecificWitnessWindow.show();
|
||||
} else if (Utilities.isAltOrCtrlPressed(KeyCode.O, event)) {
|
||||
signUnsignedPubKeysWindow.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user