Add signed witness filter (#4124)

* Add signed witness filter

- Add a filter to pubkeys used in AccountAgeWitness signing
- Fix inverted arbitrator signing of initial account age witnesses from
disputes
- Add test to verify that signed witness filter works
- Add test to verify that the arbitrator signing was fixed

* Fix codacy complaints

* Prevent NullPointerException during toggle group initialization

* Add scrollbar to filter window

* Format test class

Co-authored-by: Christoph Atteneder <christoph.atteneder@gmail.com>
This commit is contained in:
sqrrm 2020-04-10 15:14:01 +02:00 committed by GitHub
parent aeda234ef1
commit 09141eba92
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 447 additions and 36 deletions

View File

@ -18,6 +18,7 @@
package bisq.core.account.sign;
import bisq.core.account.witness.AccountAgeWitness;
import bisq.core.filter.FilterManager;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
import bisq.core.user.User;
@ -34,6 +35,7 @@ import bisq.common.util.Utilities;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.Utils;
import javax.inject.Inject;
@ -46,6 +48,7 @@ import java.security.SignatureException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
@ -60,7 +63,7 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
public class SignedWitnessService {
public static final long SIGNER_AGE_DAYS = 30;
public static final long SIGNER_AGE = SIGNER_AGE_DAYS * ChronoUnit.DAYS.getDuration().toMillis();
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");
private final KeyRing keyRing;
@ -69,6 +72,7 @@ public class SignedWitnessService {
private final User user;
private final Map<P2PDataStorage.ByteArray, SignedWitness> signedWitnessMap = new HashMap<>();
private final FilterManager filterManager;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
@ -80,11 +84,13 @@ public class SignedWitnessService {
ArbitratorManager arbitratorManager,
SignedWitnessStorageService signedWitnessStorageService,
AppendOnlyDataStoreService appendOnlyDataStoreService,
User user) {
User user,
FilterManager filterManager) {
this.keyRing = keyRing;
this.p2PService = p2PService;
this.arbitratorManager = arbitratorManager;
this.user = user;
this.filterManager = filterManager;
// We need to add that early (before onAllServicesInitialized) as it will be used at startup.
appendOnlyDataStoreService.addService(signedWitnessStorageService);
@ -131,8 +137,13 @@ public class SignedWitnessService {
/**
* List of dates as long when accountAgeWitness was signed
*
* Witnesses that were added but are no longer considered signed won't be shown
*/
public List<Long> getVerifiedWitnessDateList(AccountAgeWitness accountAgeWitness) {
if (!isSignedAccountAgeWitness(accountAgeWitness)) {
return new ArrayList<>();
}
return getSignedWitnessSet(accountAgeWitness).stream()
.filter(this::verifySignature)
.map(SignedWitness::getDate)
@ -159,6 +170,26 @@ public class SignedWitnessService {
.orElse(false);
}
public boolean isFilteredWitness(AccountAgeWitness accountAgeWitness) {
return getSignedWitnessSet(accountAgeWitness).stream()
.map(SignedWitness::getWitnessOwnerPubKey)
.anyMatch(ownerPubKey -> filterManager.isSignerPubKeyBanned(Utils.HEX.encode(ownerPubKey)));
}
public String ownerPubKey(AccountAgeWitness accountAgeWitness) {
return getSignedWitnessSet(accountAgeWitness).stream()
.map(signedWitness -> Utils.HEX.encode(signedWitness.getWitnessOwnerPubKey()))
.findFirst()
.orElse("");
}
@VisibleForTesting
public Set<SignedWitness> getSignedWitnessSetByOwnerPubKey(byte[] ownerPubKey) {
return signedWitnessMap.values().stream()
.filter(e -> Arrays.equals(e.getWitnessOwnerPubKey(), ownerPubKey))
.collect(Collectors.toSet());
}
// Arbitrators sign with EC key
public void signAccountAgeWitness(Coin tradeAmount,
AccountAgeWitness accountAgeWitness,
@ -322,6 +353,9 @@ public class SignedWitnessService {
private boolean isValidSignerWitnessInternal(SignedWitness signedWitness,
long childSignedWitnessDateMillis,
Stack<P2PDataStorage.ByteArray> excludedPubKeys) {
if (filterManager.isSignerPubKeyBanned(Utils.HEX.encode(signedWitness.getWitnessOwnerPubKey()))) {
return false;
}
if (!verifySignature(signedWitness)) {
return false;
}
@ -373,6 +407,7 @@ public class SignedWitnessService {
if (!signedWitnessMap.containsKey(signedWitness.getHashAsByteArray())) {
log.info("broadcast signed witness {}", signedWitness.toString());
p2PService.addPersistableNetworkPayload(signedWitness, false);
addToMap(signedWitness);
}
}

View File

@ -33,6 +33,7 @@ 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;
@ -54,9 +55,12 @@ import bisq.common.util.Utilities;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.Utils;
import javax.inject.Inject;
import com.google.common.annotations.VisibleForTesting;
import java.security.PublicKey;
import java.util.Arrays;
@ -96,15 +100,25 @@ public class AccountAgeWitnessService {
ARBITRATOR(Res.get("offerbook.timeSinceSigning.info.arbitrator")),
PEER_INITIAL(Res.get("offerbook.timeSinceSigning.info.peer")),
PEER_LIMIT_LIFTED(Res.get("offerbook.timeSinceSigning.info.peerLimitLifted")),
PEER_SIGNER(Res.get("offerbook.timeSinceSigning.info.signer"));
PEER_SIGNER(Res.get("offerbook.timeSinceSigning.info.signer")),
BANNED(Res.get("offerbook.timeSinceSigning.info.banned"));
private String presentation;
private String hash = "";
SignState(String presentation) {
this.presentation = presentation;
}
public SignState addHash(String hash) {
this.hash = hash;
return this;
}
public String getPresentation() {
if (!hash.isEmpty()) { // Only showing in DEBUG mode
return presentation + " " + hash;
}
return presentation;
}
@ -187,7 +201,8 @@ public class AccountAgeWitnessService {
});
}
private void addToMap(AccountAgeWitness accountAgeWitness) {
@VisibleForTesting
public void addToMap(AccountAgeWitness accountAgeWitness) {
accountAgeWitnessMap.putIfAbsent(accountAgeWitness.getHashAsByteArray(), accountAgeWitness);
}
@ -202,11 +217,18 @@ public class AccountAgeWitnessService {
p2PService.addPersistableNetworkPayload(accountAgeWitness, false);
}
public byte[] getPeerAccountAgeWitnessHash(Trade trade) {
return findTradePeerWitness(trade)
.map(accountAgeWitness -> accountAgeWitness.getHash())
.orElse(null);
}
private byte[] getAccountInputDataWithSalt(PaymentAccountPayload paymentAccountPayload) {
return Utilities.concatenateByteArrays(paymentAccountPayload.getAgeWitnessInputData(), paymentAccountPayload.getSalt());
}
private AccountAgeWitness getNewWitness(PaymentAccountPayload paymentAccountPayload, PubKeyRing pubKeyRing) {
@VisibleForTesting
public AccountAgeWitness getNewWitness(PaymentAccountPayload paymentAccountPayload, PubKeyRing pubKeyRing) {
byte[] accountInputDataWithSalt = getAccountInputDataWithSalt(paymentAccountPayload);
byte[] hash = Hash.getSha256Ripemd160hash(Utilities.concatenateByteArrays(accountInputDataWithSalt,
pubKeyRing.getSignaturePubKeyBytes()));
@ -623,7 +645,8 @@ public class AccountAgeWitnessService {
}
// Arbitrator signing
public List<TraderDataItem> getTraderPaymentAccounts(long safeDate, PaymentMethod paymentMethod,
public List<TraderDataItem> getTraderPaymentAccounts(long safeDate,
PaymentMethod paymentMethod,
List<Dispute> disputes) {
return disputes.stream()
.filter(dispute -> dispute.getContract().getPaymentMethodId().equals(paymentMethod.getId()))
@ -648,11 +671,16 @@ public class AccountAgeWitnessService {
filterManager.isPeersPaymentAccountDataAreBanned(dispute.getContract().getBuyerPaymentAccountPayload(),
new PaymentAccountFilter[1]) ||
filterManager.isPeersPaymentAccountDataAreBanned(dispute.getContract().getSellerPaymentAccountPayload(),
new PaymentAccountFilter[1]);
new PaymentAccountFilter[1]) ||
filterManager.isSignerPubKeyBanned(
Utils.HEX.encode(dispute.getContract().getBuyerPubKeyRing().getSignaturePubKeyBytes())) ||
filterManager.isSignerPubKeyBanned(
Utils.HEX.encode(dispute.getContract().getSellerPubKeyRing().getSignaturePubKeyBytes()));
return !isFiltered;
}
private boolean hasChargebackRisk(Dispute dispute) {
@VisibleForTesting
public boolean hasChargebackRisk(Dispute dispute) {
return chargeBackRisk.hasChargebackRisk(dispute.getContract().getPaymentMethodId(),
dispute.getContract().getOfferPayload().getCurrencyCode());
}
@ -677,14 +705,14 @@ public class AccountAgeWitnessService {
buyerPaymentAccountPaload,
witness,
tradeAmount,
sellerPubKeyRing.getSignaturePubKey()))
buyerPubKeyRing.getSignaturePubKey()))
.orElse(null);
TraderDataItem sellerData = findWitness(sellerPaymentAccountPaload, sellerPubKeyRing)
.map(witness -> new TraderDataItem(
sellerPaymentAccountPaload,
witness,
tradeAmount,
buyerPubKeyRing.getSignaturePubKey()))
sellerPubKeyRing.getSignaturePubKey()))
.orElse(null);
return Stream.of(buyerData, sellerData);
}
@ -722,19 +750,25 @@ 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) : "";
if (signedWitnessService.isFilteredWitness(accountAgeWitness)) {
return SignState.BANNED.addHash(hash);
}
if (signedWitnessService.isSignedByArbitrator(accountAgeWitness)) {
return SignState.ARBITRATOR;
return SignState.ARBITRATOR.addHash(hash);
} else {
final long accountSignAge = getWitnessSignAge(accountAgeWitness, new Date());
switch (getAccountAgeCategory(accountSignAge)) {
case TWO_MONTHS_OR_MORE:
case ONE_TO_TWO_MONTHS:
return SignState.PEER_SIGNER;
return SignState.PEER_SIGNER.addHash(hash);
case LESS_ONE_MONTH:
return SignState.PEER_INITIAL;
return SignState.PEER_INITIAL.addHash(hash);
case UNVERIFIED:
default:
return SignState.UNSIGNED;
return SignState.UNSIGNED.addHash(hash);
}
}
}

View File

@ -101,6 +101,10 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
@Nullable
private final List<String> refundAgents;
// added in v1.2.x
@Nullable
private final List<String> bannedSignerPubKeys;
public Filter(List<String> bannedOfferIds,
List<String> bannedNodeAddress,
List<PaymentAccountFilter> bannedPaymentAccounts,
@ -115,7 +119,8 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
@Nullable String disableDaoBelowVersion,
@Nullable String disableTradeBelowVersion,
@Nullable List<String> mediators,
@Nullable List<String> refundAgents) {
@Nullable List<String> refundAgents,
@Nullable List<String> bannedSignerPubKeys) {
this.bannedOfferIds = bannedOfferIds;
this.bannedNodeAddress = bannedNodeAddress;
this.bannedPaymentAccounts = bannedPaymentAccounts;
@ -131,6 +136,7 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
this.disableTradeBelowVersion = disableTradeBelowVersion;
this.mediators = mediators;
this.refundAgents = refundAgents;
this.bannedSignerPubKeys = bannedSignerPubKeys;
}
@ -156,7 +162,8 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
byte[] ownerPubKeyBytes,
@Nullable Map<String, String> extraDataMap,
@Nullable List<String> mediators,
@Nullable List<String> refundAgents) {
@Nullable List<String> refundAgents,
@Nullable List<String> bannedSignerPubKeys) {
this(bannedOfferIds,
bannedNodeAddress,
bannedPaymentAccounts,
@ -171,7 +178,8 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
disableDaoBelowVersion,
disableTradeBelowVersion,
mediators,
refundAgents);
refundAgents,
bannedSignerPubKeys);
this.signatureAsBase64 = signatureAsBase64;
this.ownerPubKeyBytes = ownerPubKeyBytes;
this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap);
@ -206,6 +214,7 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData);
Optional.ofNullable(mediators).ifPresent(builder::addAllMediators);
Optional.ofNullable(refundAgents).ifPresent(builder::addAllRefundAgents);
Optional.ofNullable(bannedSignerPubKeys).ifPresent(builder::addAllBannedSignerPubKeys);
return protobuf.StoragePayload.newBuilder().setFilter(builder).build();
}
@ -230,7 +239,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
proto.getOwnerPubKeyBytes().toByteArray(),
CollectionUtils.isEmpty(proto.getExtraDataMap()) ? null : proto.getExtraDataMap(),
CollectionUtils.isEmpty(proto.getMediatorsList()) ? null : new ArrayList<>(proto.getMediatorsList()),
CollectionUtils.isEmpty(proto.getRefundAgentsList()) ? null : new ArrayList<>(proto.getRefundAgentsList()));
CollectionUtils.isEmpty(proto.getRefundAgentsList()) ? null : new ArrayList<>(proto.getRefundAgentsList()),
CollectionUtils.isEmpty(proto.getBannedSignerPubKeysList()) ?
null : new ArrayList<>(proto.getBannedSignerPubKeysList()));
}
@ -269,6 +280,7 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
",\n disableTradeBelowVersion='" + disableTradeBelowVersion + '\'' +
",\n mediators=" + mediators +
",\n refundAgents=" + refundAgents +
",\n bannedSignerPubKeys=" + bannedSignerPubKeys +
"\n}";
}
}

View File

@ -17,6 +17,7 @@
package bisq.core.filter;
import bisq.core.account.witness.AccountAgeWitness;
import bisq.core.btc.nodes.BtcNodes;
import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.payment.payload.PaymentMethod;
@ -332,6 +333,7 @@ public class FilterManager {
Optional.ofNullable(filter.getBannedCurrencies()).ifPresent(builder::addAllBannedCurrencies);
Optional.ofNullable(filter.getBannedPaymentMethods()).ifPresent(builder::addAllBannedPaymentMethods);
Optional.ofNullable(filter.getBannedSignerPubKeys()).ifPresent(builder::addAllBannedSignerPubKeys);
return Utils.HEX.encode(builder.build().toByteArray());
}
@ -417,4 +419,12 @@ public class FilterManager {
}
});
}
public boolean isSignerPubKeyBanned(String signerPubKeyAsHex) {
return getFilter() != null &&
getFilter().getBannedSignerPubKeys() != null &&
getFilter().getBannedSignerPubKeys().stream()
.anyMatch(e -> e.equals(signerPubKeyAsHex));
}
}

View File

@ -343,6 +343,7 @@ offerbook.timeSinceSigning.info.arbitrator=signed by an arbitrator and can sign
offerbook.timeSinceSigning.info.peer=signed by a peer, waiting for limits to be lifted
offerbook.timeSinceSigning.info.peerLimitLifted=signed by a peer and limits were lifted
offerbook.timeSinceSigning.info.signer=signed by peer and can sign peer accounts (limits lifted)
offerbook.timeSinceSigning.info.banned=account was banned
offerbook.timeSinceSigning.daysSinceSigning={0} days
offerbook.timeSinceSigning.daysSinceSigning.long={0} since signing
@ -2398,6 +2399,7 @@ filterWindow.onions=Filtered onion addresses (comma sep.)
filterWindow.accounts=Filtered trading account data:\nFormat: comma sep. list of [payment method id | data field | value]
filterWindow.bannedCurrencies=Filtered currency codes (comma sep.)
filterWindow.bannedPaymentMethods=Filtered payment method IDs (comma sep.)
filterWindow.bannedSignerPubKeys=Filtered signer pubkeys (comma sep. hex of pubkeys)
filterWindow.arbitrators=Filtered arbitrators (comma sep. onion addresses)
filterWindow.mediators=Filtered mediators (comma sep. onion addresses)
filterWindow.refundAgents=Filtered refund agents (comma sep. onion addresses)
@ -2477,6 +2479,7 @@ tradeDetailsWindow.disputedPayoutTxId=Disputed payout transaction ID:
tradeDetailsWindow.tradeDate=Trade date
tradeDetailsWindow.txFee=Mining fee
tradeDetailsWindow.tradingPeersOnion=Trading peers onion address
tradeDetailsWindow.tradingPeersPubKeyHash=Trading peers pubkey hash
tradeDetailsWindow.tradeState=Trade state
tradeDetailsWindow.agentAddresses=Arbitrator/Mediator

View File

@ -19,6 +19,7 @@ package bisq.core.account.sign;
import bisq.core.account.witness.AccountAgeWitness;
import bisq.core.filter.FilterManager;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
import bisq.network.p2p.P2PService;
@ -79,7 +80,11 @@ public class SignedWitnessServiceTest {
private long SIGN_AGE_3 = SignedWitnessService.SIGNER_AGE_DAYS + 3;
private KeyRing keyRing;
private P2PService p2pService;
private FilterManager filterManager;
private ECKey arbitrator1Key;
KeyPair peer1KeyPair;
KeyPair peer2KeyPair;
KeyPair peer3KeyPair;
@Before
public void setup() throws Exception {
@ -88,7 +93,8 @@ public class SignedWitnessServiceTest {
when(arbitratorManager.isPublicKeyInList(any())).thenReturn(true);
keyRing = mock(KeyRing.class);
p2pService = mock(P2PService.class);
signedWitnessService = new SignedWitnessService(keyRing, p2pService, arbitratorManager, null, appendOnlyDataStoreService, null);
filterManager = mock(FilterManager.class);
signedWitnessService = new SignedWitnessService(keyRing, p2pService, arbitratorManager, null, appendOnlyDataStoreService, null, filterManager);
account1DataHash = org.bitcoinj.core.Utils.sha256hash160(new byte[]{1});
account2DataHash = org.bitcoinj.core.Utils.sha256hash160(new byte[]{2});
account3DataHash = org.bitcoinj.core.Utils.sha256hash160(new byte[]{3});
@ -98,10 +104,10 @@ public class SignedWitnessServiceTest {
aew1 = new AccountAgeWitness(account1DataHash, account1CreationTime);
aew2 = new AccountAgeWitness(account2DataHash, account2CreationTime);
aew3 = new AccountAgeWitness(account3DataHash, account3CreationTime);
ECKey arbitrator1Key = new ECKey();
KeyPair peer1KeyPair = Sig.generateKeyPair();
KeyPair peer2KeyPair = Sig.generateKeyPair();
KeyPair peer3KeyPair = Sig.generateKeyPair();
arbitrator1Key = new ECKey();
peer1KeyPair = Sig.generateKeyPair();
peer2KeyPair = Sig.generateKeyPair();
peer3KeyPair = Sig.generateKeyPair();
signature1 = arbitrator1Key.signMessage(Utilities.encodeToHex(account1DataHash)).getBytes(Charsets.UTF_8);
signature2 = Sig.sign(peer1KeyPair.getPrivate(), Utilities.encodeToHex(account2DataHash).getBytes(Charsets.UTF_8));
signature3 = Sig.sign(peer2KeyPair.getPrivate(), Utilities.encodeToHex(account3DataHash).getBytes(Charsets.UTF_8));
@ -368,5 +374,134 @@ public class SignedWitnessServiceTest {
verify(p2pService, times(1)).addPersistableNetworkPayload(any(PersistableNetworkPayload.class), anyBoolean());
}
/* Signed witness tree
Each edge in the graph represents one signature
Arbitrator
|
sw1
|
sw2
|
sw3
*/
@Test
public void testBanFilterSingleTree() {
SignedWitness sw1 = new SignedWitness(ARBITRATOR, account1DataHash, signature1, signer1PubKey, witnessOwner1PubKey, date1, tradeAmount1);
SignedWitness sw2 = new SignedWitness(TRADE, account2DataHash, signature2, signer2PubKey, witnessOwner2PubKey, date2, tradeAmount2);
SignedWitness sw3 = new SignedWitness(TRADE, account3DataHash, signature3, signer3PubKey, witnessOwner3PubKey, date3, tradeAmount3);
signedWitnessService.addToMap(sw1);
signedWitnessService.addToMap(sw2);
signedWitnessService.addToMap(sw3);
// Second account is banned, first account is still a signer but the other two are no longer signers
when(filterManager.isSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner2PubKey))).thenReturn(true);
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew1));
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew2));
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew3));
// First account is banned, no accounts in the tree below it are signers
when(filterManager.isSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner1PubKey))).thenReturn(true);
when(filterManager.isSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner2PubKey))).thenReturn(false);
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew1));
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew2));
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew3));
}
/* Signed witness trees
Each edge in the graph represents one signature
Arbitrator
| |
sw1 sw2
|
sw3
*/
@Test
public void testBanFilterTwoTrees() {
// Signer 2 is signed by arbitrator
signer2PubKey = arbitrator1Key.getPubKey();
signature2 = arbitrator1Key.signMessage(Utilities.encodeToHex(account2DataHash)).getBytes(Charsets.UTF_8);
SignedWitness sw1 = new SignedWitness(ARBITRATOR, account1DataHash, signature1, signer1PubKey, witnessOwner1PubKey, date1, tradeAmount1);
SignedWitness sw2 = new SignedWitness(ARBITRATOR, account2DataHash, signature2, signer2PubKey, witnessOwner2PubKey, date2, tradeAmount2);
SignedWitness sw3 = new SignedWitness(TRADE, account3DataHash, signature3, signer3PubKey, witnessOwner3PubKey, date3, tradeAmount3);
signedWitnessService.addToMap(sw1);
signedWitnessService.addToMap(sw2);
signedWitnessService.addToMap(sw3);
// Only second account is banned, first account is still a signer but the other two are no longer signers
when(filterManager.isSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner2PubKey))).thenReturn(true);
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew1));
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew2));
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew3));
// Only first account is banned, account2 and account3 are still signers
when(filterManager.isSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner1PubKey))).thenReturn(true);
when(filterManager.isSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner2PubKey))).thenReturn(false);
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew1));
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew2));
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew3));
}
/* Signed witness tree
Each edge in the graph represents one signature
Arbitrator
| |
sw1 sw2
\ /
sw3
*/
@Test
public void testBanFilterJoinedTrees() throws Exception {
// Signer 2 is signed by arbitrator
signer2PubKey = arbitrator1Key.getPubKey();
signature2 = arbitrator1Key.signMessage(Utilities.encodeToHex(account2DataHash)).getBytes(Charsets.UTF_8);
// Peer1 owns both account1 and account2
// witnessOwner2PubKey = witnessOwner1PubKey;
// peer2KeyPair = peer1KeyPair;
// signature3 = Sig.sign(peer2KeyPair.getPrivate(), Utilities.encodeToHex(account3DataHash).getBytes(Charsets.UTF_8));
// sw1 also signs sw3 (not supported yet but a possible addition for a more robust system)
var signature3p = Sig.sign(peer1KeyPair.getPrivate(), Utilities.encodeToHex(account3DataHash).getBytes(Charsets.UTF_8));
var signer3pPubKey = witnessOwner1PubKey;
var date3p = date3;
var tradeAmount3p = tradeAmount3;
SignedWitness sw1 = new SignedWitness(ARBITRATOR, account1DataHash, signature1, signer1PubKey, witnessOwner1PubKey, date1, tradeAmount1);
SignedWitness sw2 = new SignedWitness(ARBITRATOR, account2DataHash, signature2, signer2PubKey, witnessOwner2PubKey, date2, tradeAmount2);
SignedWitness sw3 = new SignedWitness(TRADE, account3DataHash, signature3, signer3PubKey, witnessOwner3PubKey, date3, tradeAmount3);
SignedWitness sw3p = new SignedWitness(TRADE, account3DataHash, signature3p, signer3pPubKey, witnessOwner3PubKey, date3p, tradeAmount3p);
signedWitnessService.addToMap(sw1);
signedWitnessService.addToMap(sw2);
signedWitnessService.addToMap(sw3);
signedWitnessService.addToMap(sw3p);
// First account is banned, the other two are still signers
when(filterManager.isSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner1PubKey))).thenReturn(true);
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew1));
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew2));
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew3));
// Second account is banned, the other two are still signers
when(filterManager.isSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner1PubKey))).thenReturn(false);
when(filterManager.isSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner2PubKey))).thenReturn(true);
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew1));
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew2));
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew3));
// First and second account is banned, the third is no longer a signer
when(filterManager.isSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner1PubKey))).thenReturn(true);
when(filterManager.isSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner2PubKey))).thenReturn(true);
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew1));
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew2));
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew3));
}
}

View File

@ -17,53 +17,92 @@
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.locale.CountryUtil;
import bisq.core.offer.OfferPayload;
import bisq.core.payment.ChargeBackRisk;
import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.payment.payload.PaymentMethod;
import bisq.core.payment.payload.SepaAccountPayload;
import bisq.core.support.SupportType;
import bisq.core.support.dispute.Dispute;
import bisq.core.support.dispute.DisputeResult;
import bisq.core.support.dispute.arbitration.TraderDataItem;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
import bisq.core.trade.Contract;
import bisq.network.p2p.P2PService;
import bisq.network.p2p.storage.persistence.AppendOnlyDataStoreService;
import bisq.common.crypto.CryptoException;
import bisq.common.crypto.KeyRing;
import bisq.common.crypto.KeyStorage;
import bisq.common.crypto.PubKeyRing;
import bisq.common.crypto.Sig;
import bisq.common.util.Utilities;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.ECKey;
import java.security.KeyPair;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
// Restricted default Java security policy on Travis does not allow long keys, so test fails.
// Using Utilities.removeCryptographyRestrictions(); did not work.
@Ignore
//@Ignore
public class AccountAgeWitnessServiceTest {
private PublicKey publicKey;
private KeyPair keypair;
private SignedWitnessService signedWitnessService;
private AccountAgeWitnessService service;
private ChargeBackRisk chargeBackRisk;
private FilterManager filterManager;
@Before
public void setup() throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, CryptoException {
SignedWitnessService signedWitnessService = mock(SignedWitnessService.class);
ChargeBackRisk chargeBackRisk = mock(ChargeBackRisk.class);
service = new AccountAgeWitnessService(null, null, null, signedWitnessService, chargeBackRisk, null, null, null);
public void setup() {
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);
AppendOnlyDataStoreService appendOnlyDataStoreService = mock(AppendOnlyDataStoreService.class);
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();
}
@After
public void tearDown() throws IOException {
public void tearDown() {
// Do teardown stuff
}
@Ignore
@Test
public void testIsTradeDateAfterReleaseDate() throws CryptoException {
Date ageWitnessReleaseDate = new GregorianCalendar(2017, 9, 23).getTime();
@ -84,6 +123,7 @@ public class AccountAgeWitnessServiceTest {
}));
}
@Ignore
@Test
public void testVerifySignatureOfNonce() throws CryptoException {
byte[] nonce = new byte[]{0x01};
@ -97,4 +137,113 @@ public class AccountAgeWitnessServiceTest {
assertFalse(service.verifySignature(publicKey, new byte[]{0x02}, new byte[]{0x04}, errorMessage -> {
}));
}
@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();
KeyRing buyerKeyRing = new KeyRing(new KeyStorage(dir1));
KeyRing sellerKeyRing = new KeyRing(new KeyStorage(dir2));
// Setup dispute for arbitrator to sign both sides
List<Dispute> disputes = new ArrayList<>();
PubKeyRing buyerPubKeyRing = buyerKeyRing.getPubKeyRing();
PubKeyRing sellerPubKeyRing = sellerKeyRing.getPubKeyRing();
PaymentAccountPayload buyerPaymentAccountPayload = new SepaAccountPayload(PaymentMethod.SEPA_ID, "1", CountryUtil.getAllSepaCountries());
PaymentAccountPayload sellerPaymentAccountPayload = new SepaAccountPayload(PaymentMethod.SEPA_ID, "2", CountryUtil.getAllSepaCountries());
AccountAgeWitness buyerAccountAgeWitness = service.getNewWitness(buyerPaymentAccountPayload, buyerPubKeyRing);
service.addToMap(buyerAccountAgeWitness);
AccountAgeWitness sellerAccountAgeWitness = service.getNewWitness(sellerPaymentAccountPayload, sellerPubKeyRing);
service.addToMap(sellerAccountAgeWitness);
long now = new Date().getTime() + 1000;
Contract contract = mock(Contract.class);
disputes.add(new Dispute(
"trade1",
0,
true,
true,
buyerPubKeyRing,
now - 1,
contract,
null,
null,
null,
null,
null,
"contractAsJson",
null,
null,
null,
true,
SupportType.ARBITRATION));
disputes.get(0).getIsClosedProperty().set(true);
disputes.get(0).getDisputeResultProperty().set(new DisputeResult(
"trade1",
1,
DisputeResult.Winner.BUYER,
DisputeResult.Reason.OTHER.ordinal(),
true,
true,
true,
"summary",
null,
null,
100000,
0,
null,
now - 1,
false));
// Filtermanager says nothing is filtered
when(filterManager.isNodeAddressBanned(any())).thenReturn(false);
when(filterManager.isCurrencyBanned(any())).thenReturn(false);
when(filterManager.isPaymentMethodBanned(any())).thenReturn(false);
when(filterManager.isPeersPaymentAccountDataAreBanned(any(), any())).thenReturn(false);
when(filterManager.isSignerPubKeyBanned(any())).thenReturn(false);
when(chargeBackRisk.hasChargebackRisk(any(), any())).thenReturn(true);
when(contract.getPaymentMethodId()).thenReturn(PaymentMethod.SEPA_ID);
when(contract.getTradeAmount()).thenReturn(Coin.parseCoin("0.01"));
when(contract.getBuyerPubKeyRing()).thenReturn(buyerPubKeyRing);
when(contract.getSellerPubKeyRing()).thenReturn(sellerPubKeyRing);
when(contract.getBuyerPaymentAccountPayload()).thenReturn(buyerPaymentAccountPayload);
when(contract.getSellerPaymentAccountPayload()).thenReturn(sellerPaymentAccountPayload);
when(contract.getOfferPayload()).thenReturn(mock(OfferPayload.class));
List<TraderDataItem> items = service.getTraderPaymentAccounts(now, PaymentMethod.SEPA, disputes);
assertEquals(items.size(), 2);
// Setup a mocked arbitrator key
ECKey arbitratorKey = mock(ECKey.class);
when(arbitratorKey.signMessage(any())).thenReturn("1");
when(arbitratorKey.signMessage(any())).thenReturn("2");
when(arbitratorKey.getPubKey()).thenReturn("1".getBytes());
// Arbitrator signs both trader accounts
items.forEach(item -> service.arbitratorSignAccountAgeWitness(
item.getTradeAmount(),
item.getAccountAgeWitness(),
arbitratorKey,
item.getPeersPubKey()));
// Check that both accountAgeWitnesses are signed
SignedWitness foundBuyerSignedWitness = signedWitnessService.getSignedWitnessSetByOwnerPubKey(
buyerPubKeyRing.getSignaturePubKeyBytes()).stream()
.findFirst()
.orElse(null);
assertEquals(Utilities.bytesAsHexString(foundBuyerSignedWitness.getAccountAgeWitnessHash()),
Utilities.bytesAsHexString(buyerAccountAgeWitness.getHash()));
SignedWitness foundSellerSignedWitness = signedWitnessService.getSignedWitnessSetByOwnerPubKey(
sellerPubKeyRing.getSignaturePubKeyBytes()).stream()
.findFirst()
.orElse(null);
assertEquals(Utilities.bytesAsHexString(foundSellerSignedWitness.getAccountAgeWitnessHash()),
Utilities.bytesAsHexString(sellerAccountAgeWitness.getHash()));
}
}

View File

@ -58,6 +58,7 @@ public class UserPayloadModelVOTest {
new byte[]{10, 0, 0},
null,
Lists.newArrayList(),
Lists.newArrayList(),
Lists.newArrayList()));
vo.setRegisteredArbitrator(ArbitratorTest.getArbitratorMock());
vo.setRegisteredMediator(MediatorTest.getMediatorMock());

View File

@ -39,9 +39,11 @@ import org.apache.commons.lang3.StringUtils;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
@ -58,6 +60,7 @@ import static bisq.desktop.util.FormBuilder.addTopLabelInputTextField;
public class FilterWindow extends Overlay<FilterWindow> {
private final FilterManager filterManager;
private final boolean useDevPrivilegeKeys;
private ScrollPane scrollPane;
@Inject
public FilterWindow(FilterManager filterManager,
@ -67,12 +70,26 @@ public class FilterWindow extends Overlay<FilterWindow> {
type = Type.Attention;
}
@Override
protected Region getRootContainer() {
return scrollPane;
}
public void show() {
if (headLine == null)
headLine = Res.get("filterWindow.headline");
width = 968;
createGridPane();
scrollPane = new ScrollPane();
scrollPane.setContent(gridPane);
scrollPane.setFitToWidth(true);
scrollPane.setFitToHeight(true);
scrollPane.setMaxHeight(1000);
scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
addHeadLine();
addContent();
applyStyles();
@ -108,6 +125,8 @@ public class FilterWindow extends Overlay<FilterWindow> {
InputTextField bannedCurrenciesInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.bannedCurrencies"));
InputTextField bannedPaymentMethodsInputTextField = addTopLabelInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.bannedPaymentMethods")).second;
bannedPaymentMethodsInputTextField.setPromptText("E.g. PERFECT_MONEY"); // Do not translate
InputTextField bannedSignerPubKeysInputTextField = addTopLabelInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.bannedSignerPubKeys")).second;
bannedSignerPubKeysInputTextField.setPromptText("E.g. 7f66117aa084e5a2c54fe17d29dd1fee2b241257"); // Do not translate
InputTextField arbitratorsInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.arbitrators"));
InputTextField mediatorsInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.mediators"));
InputTextField refundAgentsInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.refundAgents"));
@ -126,12 +145,14 @@ public class FilterWindow extends Overlay<FilterWindow> {
setupFieldFromPaymentAccountFiltersList(paymentAccountFilterInputTextField, filter.getBannedPaymentAccounts());
setupFieldFromList(bannedCurrenciesInputTextField, filter.getBannedCurrencies());
setupFieldFromList(bannedPaymentMethodsInputTextField, filter.getBannedPaymentMethods());
setupFieldFromList(bannedSignerPubKeysInputTextField, filter.getBannedSignerPubKeys());
setupFieldFromList(arbitratorsInputTextField, filter.getArbitrators());
setupFieldFromList(mediatorsInputTextField, filter.getMediators());
setupFieldFromList(refundAgentsInputTextField, filter.getRefundAgents());
setupFieldFromList(seedNodesInputTextField, filter.getSeedNodes());
setupFieldFromList(priceRelayNodesInputTextField, filter.getPriceRelayNodes());
setupFieldFromList(btcNodesInputTextField, filter.getBtcNodes());
preventPublicBtcNetworkCheckBox.setSelected(filter.isPreventPublicBtcNetwork());
disableDaoCheckBox.setSelected(filter.isDisableDao());
disableDaoBelowVersionInputTextField.setText(filter.getDisableDaoBelowVersion());
@ -155,7 +176,8 @@ public class FilterWindow extends Overlay<FilterWindow> {
disableDaoBelowVersionInputTextField.getText(),
disableTradeBelowVersionInputTextField.getText(),
readAsList(mediatorsInputTextField),
readAsList(refundAgentsInputTextField)
readAsList(refundAgentsInputTextField),
readAsList(bannedSignerPubKeysInputTextField)
),
keyInputTextField.getText())
)

View File

@ -39,6 +39,7 @@ import bisq.core.util.coin.CoinFormatter;
import bisq.network.p2p.NodeAddress;
import bisq.common.UserThread;
import bisq.common.util.Utilities;
import org.bitcoinj.core.Utils;
@ -232,6 +233,12 @@ public class TradeDetailsWindow extends Overlay<TradeDetailsWindow> {
addConfirmationLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, Res.get("tradeDetailsWindow.tradingPeersOnion"),
trade.getTradingPeerNodeAddress().getFullAddress());
addConfirmationLabelTextFieldWithCopyIcon(gridPane, ++rowIndex,
Res.get("tradeDetailsWindow.tradingPeersPubKeyHash"),
trade.getContract() != null ? Utils.HEX.encode(trade.getContract().getPeersPubKeyRing(
tradeManager.getKeyRing().getPubKeyRing()).getSignaturePubKeyBytes()) :
Res.get("shared.na"));
if (contract != null) {
if (buyerPaymentAccountPayload != null) {
String paymentDetails = buyerPaymentAccountPayload.getPaymentDetails();

View File

@ -227,8 +227,10 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
onBitcoinPeersToggleSelected(false);
bitcoinPeersToggleGroupListener = (observable, oldValue, newValue) -> {
if (newValue != null) {
selectedBitcoinNodesOption = (BtcNodes.BitcoinNodesOption) newValue.getUserData();
onBitcoinPeersToggleSelected(true);
}
};
btcNodesInputTextField.setPromptText(Res.get("settings.net.ips"));

View File

@ -628,6 +628,7 @@ message Filter {
string disable_trade_below_version = 16;
repeated string mediators = 17;
repeated string refundAgents = 18;
repeated string bannedSignerPubKeys = 19;
}
// not used anymore from v0.6 on. But leave it for receiving TradeStatistics objects from older