Skip to content

Commit

Permalink
Merge pull request #4280 from sqrrm/arbitrator-witness-signing
Browse files Browse the repository at this point in the history
Arbitrator witness signing
  • Loading branch information
ripcurlx authored Jul 3, 2020
2 parents ac2f37f + 9a8622c commit c190f42
Show file tree
Hide file tree
Showing 13 changed files with 910 additions and 102 deletions.
18 changes: 14 additions & 4 deletions common/src/main/java/bisq/common/util/Utilities.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,6 @@
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;

Expand All @@ -56,15 +52,19 @@
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;
Expand Down Expand Up @@ -330,6 +330,10 @@ public static boolean isAltPressed(KeyCode keyCode, KeyEvent keyEvent) {
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);
}
Expand Down Expand Up @@ -452,4 +456,10 @@ public static int byteArrayToInteger(byte[] bytes) {
}
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ public boolean isSignedByArbitrator() {
// Getters
///////////////////////////////////////////////////////////////////////////////////////////

P2PDataStorage.ByteArray getHashAsByteArray() {
public P2PDataStorage.ByteArray getHashAsByteArray() {
return new P2PDataStorage.ByteArray(hash);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

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;
Expand All @@ -52,25 +53,28 @@
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;

Expand Down Expand Up @@ -123,6 +127,8 @@ public void onUpdatedDataReceived() {
}
});
}
// TODO: Enable cleaning of signed witness list when necessary
// cleanSignedWitnesses();
}

private void onBootstrapComplete() {
Expand Down Expand Up @@ -176,7 +182,14 @@ public boolean isFilteredWitness(AccountAgeWitness accountAgeWitness) {
.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()
Expand All @@ -195,9 +208,42 @@ public void signAccountAgeWitness(Coin tradeAmount,
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());
Expand All @@ -206,11 +252,12 @@ public void signAccountAgeWitness(Coin tradeAmount,
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 {
Expand Down Expand Up @@ -306,6 +353,24 @@ public Set<SignedWitness> getTrustedPeerSignedWitnessSet(AccountAgeWitness accou
.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,
Expand Down Expand Up @@ -405,7 +470,6 @@ private boolean verifyDate(SignedWitness signedWitness, long childSignedWitnessD

@VisibleForTesting
void addToMap(SignedWitness signedWitness) {
// TODO: Perhaps filter out all but one signedwitness per accountagewitness
signedWitnessMap.putIfAbsent(signedWitness.getHashAsByteArray(), signedWitness);
}

Expand All @@ -420,4 +484,19 @@ private void publishSignedWitness(SignedWitness signedWitness) {
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());
// }
// }));
// }
}
Loading

0 comments on commit c190f42

Please sign in to comment.