Merge pull request #7152 from HenrikJannsen/apply-ExcludeForHash-annotation-to-filter

[3] Apply exclude for hash annotation to filter
This commit is contained in:
Alejandro García 2024-06-08 21:37:29 +00:00 committed by GitHub
commit ed4bfe6031
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 160 additions and 24 deletions

View File

@ -103,7 +103,7 @@ public interface ExcludeForHashAwareProto extends Proto {
default <B extends Message.Builder> B clearAnnotatedFields(B builder) { default <B extends Message.Builder> B clearAnnotatedFields(B builder) {
Set<String> excludedFields = getExcludedFields(); Set<String> excludedFields = getExcludedFields();
if (!excludedFields.isEmpty()) { if (!excludedFields.isEmpty()) {
getLogger().error("Clear fields in builder annotated with @ExcludeForHash: {}", excludedFields); getLogger().debug("Clear fields in builder annotated with @ExcludeForHash: {}", excludedFields);
} }
for (Descriptors.FieldDescriptor fieldDesc : builder.getAllFields().keySet()) { for (Descriptors.FieldDescriptor fieldDesc : builder.getAllFields().keySet()) {
if (excludedFields.contains(fieldDesc.getName())) { if (excludedFields.contains(fieldDesc.getName())) {

View File

@ -20,6 +20,8 @@ package bisq.core.filter;
import bisq.network.p2p.storage.payload.ExpirablePayload; import bisq.network.p2p.storage.payload.ExpirablePayload;
import bisq.network.p2p.storage.payload.ProtectedStoragePayload; import bisq.network.p2p.storage.payload.ProtectedStoragePayload;
import bisq.common.ExcludeForHash;
import bisq.common.ExcludeForHashAwareProto;
import bisq.common.crypto.Sig; import bisq.common.crypto.Sig;
import bisq.common.proto.ProtoUtil; import bisq.common.proto.ProtoUtil;
import bisq.common.proto.network.GetDataResponsePriority; import bisq.common.proto.network.GetDataResponsePriority;
@ -27,6 +29,8 @@ import bisq.common.util.CollectionUtils;
import bisq.common.util.ExtraDataMapValidator; import bisq.common.util.ExtraDataMapValidator;
import bisq.common.util.Utilities; import bisq.common.util.Utilities;
import protobuf.StoragePayload;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
@ -49,7 +53,7 @@ import javax.annotation.Nullable;
@Slf4j @Slf4j
@Getter @Getter
@EqualsAndHashCode @EqualsAndHashCode
public final class Filter implements ProtectedStoragePayload, ExpirablePayload { public final class Filter implements ProtectedStoragePayload, ExpirablePayload, ExcludeForHashAwareProto {
public static final long TTL = TimeUnit.DAYS.toMillis(180); public static final long TTL = TimeUnit.DAYS.toMillis(180);
private final List<String> bannedOfferIds; private final List<String> bannedOfferIds;
@ -124,8 +128,14 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
private final List<PaymentAccountFilter> delayedPayoutPaymentAccounts; private final List<PaymentAccountFilter> delayedPayoutPaymentAccounts;
// Added at v 1.9.16 // Added at v 1.9.16
@ExcludeForHash
private final List<String> addedBtcNodes; private final List<String> addedBtcNodes;
@ExcludeForHash
private final List<String> addedSeedNodes; private final List<String> addedSeedNodes;
// As we might add more ExcludeForHash we want to ensure to have a unique identifier.
// The hash of the data is not unique anymore if the only change have been at
// the ExcludeForHash annotated fields.
private final String uid;
// After we have created the signature from the filter data we clone it and apply the signature // After we have created the signature from the filter data we clone it and apply the signature
static Filter cloneWithSig(Filter filter, String signatureAsBase64) { static Filter cloneWithSig(Filter filter, String signatureAsBase64) {
@ -166,7 +176,8 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
filter.getTakerFeeBsq(), filter.getTakerFeeBsq(),
filter.getDelayedPayoutPaymentAccounts(), filter.getDelayedPayoutPaymentAccounts(),
filter.getAddedBtcNodes(), filter.getAddedBtcNodes(),
filter.getAddedSeedNodes()); filter.getAddedSeedNodes(),
filter.getUid());
} }
// Used for signature verification as we created the sig without the signatureAsBase64 field we set it to null again // Used for signature verification as we created the sig without the signatureAsBase64 field we set it to null again
@ -208,7 +219,8 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
filter.getTakerFeeBsq(), filter.getTakerFeeBsq(),
filter.getDelayedPayoutPaymentAccounts(), filter.getDelayedPayoutPaymentAccounts(),
filter.getAddedBtcNodes(), filter.getAddedBtcNodes(),
filter.getAddedSeedNodes()); filter.getAddedSeedNodes(),
filter.getUid());
} }
public Filter(List<String> bannedOfferIds, public Filter(List<String> bannedOfferIds,
@ -245,7 +257,8 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
long takerFeeBsq, long takerFeeBsq,
List<PaymentAccountFilter> delayedPayoutPaymentAccounts, List<PaymentAccountFilter> delayedPayoutPaymentAccounts,
List<String> addedBtcNodes, List<String> addedBtcNodes,
List<String> addedSeedNodes) { List<String> addedSeedNodes,
String uid) {
this(bannedOfferIds, this(bannedOfferIds,
nodeAddressesBannedFromTrading, nodeAddressesBannedFromTrading,
bannedPaymentAccounts, bannedPaymentAccounts,
@ -283,7 +296,8 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
takerFeeBsq, takerFeeBsq,
delayedPayoutPaymentAccounts, delayedPayoutPaymentAccounts,
addedBtcNodes, addedBtcNodes,
addedSeedNodes); addedSeedNodes,
uid);
} }
@ -329,7 +343,8 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
long takerFeeBsq, long takerFeeBsq,
List<PaymentAccountFilter> delayedPayoutPaymentAccounts, List<PaymentAccountFilter> delayedPayoutPaymentAccounts,
List<String> addedBtcNodes, List<String> addedBtcNodes,
List<String> addedSeedNodes) { List<String> addedSeedNodes,
String uid) {
this.bannedOfferIds = bannedOfferIds; this.bannedOfferIds = bannedOfferIds;
this.nodeAddressesBannedFromTrading = nodeAddressesBannedFromTrading; this.nodeAddressesBannedFromTrading = nodeAddressesBannedFromTrading;
this.bannedPaymentAccounts = bannedPaymentAccounts; this.bannedPaymentAccounts = bannedPaymentAccounts;
@ -368,6 +383,7 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
this.delayedPayoutPaymentAccounts = delayedPayoutPaymentAccounts; this.delayedPayoutPaymentAccounts = delayedPayoutPaymentAccounts;
this.addedBtcNodes = addedBtcNodes; this.addedBtcNodes = addedBtcNodes;
this.addedSeedNodes = addedSeedNodes; this.addedSeedNodes = addedSeedNodes;
this.uid = uid;
// ownerPubKeyBytes can be null when called from tests // ownerPubKeyBytes can be null when called from tests
if (ownerPubKeyBytes != null) { if (ownerPubKeyBytes != null) {
@ -379,6 +395,24 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
@Override @Override
public protobuf.StoragePayload toProtoMessage() { public protobuf.StoragePayload toProtoMessage() {
return toProto(false);
}
@Override
public protobuf.StoragePayload toProto(boolean serializeForHash) {
return resolveProto(serializeForHash);
}
@Override
public protobuf.StoragePayload.Builder getBuilder(boolean serializeForHash) {
return StoragePayload.newBuilder().setFilter(toFilterProto(serializeForHash));
}
private protobuf.Filter toFilterProto(boolean serializeForHash) {
return resolveBuilder(getFilterBuilder(serializeForHash), serializeForHash).build();
}
private protobuf.Filter.Builder getFilterBuilder(boolean serializeForHash) {
List<protobuf.PaymentAccountFilter> paymentAccountFilterList = bannedPaymentAccounts.stream() List<protobuf.PaymentAccountFilter> paymentAccountFilterList = bannedPaymentAccounts.stream()
.map(PaymentAccountFilter::toProtoMessage) .map(PaymentAccountFilter::toProtoMessage)
.collect(Collectors.toList()); .collect(Collectors.toList());
@ -421,12 +455,13 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
.setTakerFeeBsq(takerFeeBsq) .setTakerFeeBsq(takerFeeBsq)
.addAllDelayedPayoutPaymentAccounts(delayedPayoutPaymentAccountList) .addAllDelayedPayoutPaymentAccounts(delayedPayoutPaymentAccountList)
.addAllAddedBtcNodes(addedBtcNodes) .addAllAddedBtcNodes(addedBtcNodes)
.addAllAddedSeedNodes(addedSeedNodes); .addAllAddedSeedNodes(addedSeedNodes)
.setUid(uid);
Optional.ofNullable(signatureAsBase64).ifPresent(builder::setSignatureAsBase64); Optional.ofNullable(signatureAsBase64).ifPresent(builder::setSignatureAsBase64);
Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData); Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData);
return protobuf.StoragePayload.newBuilder().setFilter(builder).build(); return builder;
} }
public static Filter fromProto(protobuf.Filter proto) { public static Filter fromProto(protobuf.Filter proto) {
@ -474,7 +509,8 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
proto.getTakerFeeBsq(), proto.getTakerFeeBsq(),
delayedPayoutPaymentAccounts, delayedPayoutPaymentAccounts,
ProtoUtil.protocolStringListToList(proto.getAddedBtcNodesList()), ProtoUtil.protocolStringListToList(proto.getAddedBtcNodesList()),
ProtoUtil.protocolStringListToList(proto.getAddedSeedNodesList()) ProtoUtil.protocolStringListToList(proto.getAddedSeedNodesList()),
proto.getUid()
); );
} }
@ -534,6 +570,7 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
",\n takerFeeBsq=" + takerFeeBsq + ",\n takerFeeBsq=" + takerFeeBsq +
",\n addedBtcNodes=" + addedBtcNodes + ",\n addedBtcNodes=" + addedBtcNodes +
",\n addedSeedNodes=" + addedSeedNodes + ",\n addedSeedNodes=" + addedSeedNodes +
",\n uid=" + uid +
"\n}"; "\n}";
} }
} }

View File

@ -701,8 +701,7 @@ public class FilterManager {
} }
private Sha256Hash getSha256Hash(Filter filter) { private Sha256Hash getSha256Hash(Filter filter) {
byte[] filterData = filter.serializeForHash(); return Sha256Hash.of(filter.serializeForHash());
return Sha256Hash.of(filterData);
} }
private String getPubKeyAsHex(ECKey ecKey) { private String getPubKeyAsHex(ECKey ecKey) {

View File

@ -0,0 +1,63 @@
/*
* 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.filter;
import bisq.common.crypto.Sig;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.Security;
import java.util.Arrays;
import org.mockito.junit.jupiter.MockitoExtension;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ExtendWith(MockitoExtension.class)
public class FilterWithExcludeForHashFieldTests {
static {
Security.addProvider(new BouncyCastleProvider());
}
private final PublicKey ownerPublicKey;
public FilterWithExcludeForHashFieldTests() throws NoSuchAlgorithmException, NoSuchProviderException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(Sig.KEY_ALGO, "BC");
KeyPair ownerKeyPair = keyPairGenerator.generateKeyPair();
ownerPublicKey = ownerKeyPair.getPublic();
}
@Test
void testSerializeForHashAnnotation() {
Filter filterWithoutSig = TestFilter.createFilter(ownerPublicKey, "invalidPubKeyAsHex");
byte[] serializeForHashBytes = filterWithoutSig.serializeForHash();
byte[] serializeBytes = filterWithoutSig.serialize();
assertFalse(Arrays.equals(serializeForHashBytes, serializeBytes));
assertTrue(serializeBytes.length > serializeForHashBytes.length);
}
}

View File

@ -32,6 +32,7 @@ import java.nio.charset.StandardCharsets;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.UUID;
import static org.bitcoinj.core.Utils.HEX; import static org.bitcoinj.core.Utils.HEX;
@ -103,8 +104,9 @@ public class TestFilter {
1, 1,
1, 1,
Collections.emptyList(), Collections.emptyList(),
Collections.emptyList(), List.of("test1.onion:1221"),
Collections.emptyList() List.of("test2.onion:1221"),
UUID.randomUUID().toString()
); );
} }

View File

@ -26,6 +26,7 @@ import bisq.core.proto.CoreProtoResolver;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import java.util.HashSet; import java.util.HashSet;
import java.util.UUID;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Disabled;
@ -79,7 +80,8 @@ public class UserPayloadModelVOTest {
0, 0,
Lists.newArrayList(), Lists.newArrayList(),
Lists.newArrayList(), Lists.newArrayList(),
Lists.newArrayList())); Lists.newArrayList(),
UUID.randomUUID().toString()));
vo.setRegisteredArbitrator(ArbitratorTest.getArbitratorMock()); vo.setRegisteredArbitrator(ArbitratorTest.getArbitratorMock());
vo.setRegisteredMediator(MediatorTest.getMediatorMock()); vo.setRegisteredMediator(MediatorTest.getMediatorMock());

View File

@ -60,6 +60,7 @@ import javafx.geometry.Insets;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static bisq.desktop.util.FormBuilder.addInputTextField; import static bisq.desktop.util.FormBuilder.addInputTextField;
@ -129,6 +130,8 @@ public class FilterWindow extends Overlay<FilterWindow> {
InputTextField keyTF = addInputTextField(gridPane, ++rowIndex, InputTextField keyTF = addInputTextField(gridPane, ++rowIndex,
Res.get("shared.unlock"), 10); Res.get("shared.unlock"), 10);
InputTextField uidTF = addInputTextField(gridPane, ++rowIndex,
"UID", 10);
if (useDevPrivilegeKeys) { if (useDevPrivilegeKeys) {
keyTF.setText(DevEnv.getDEV_PRIVILEGE_PRIV_KEY()); keyTF.setText(DevEnv.getDEV_PRIVILEGE_PRIV_KEY());
} }
@ -212,6 +215,7 @@ public class FilterWindow extends Overlay<FilterWindow> {
Filter filter = filterManager.getDevFilter(); Filter filter = filterManager.getDevFilter();
if (filter != null) { if (filter != null) {
uidTF.setText(filter.getUid());
setupFieldFromList(offerIdsTF, filter.getBannedOfferIds()); setupFieldFromList(offerIdsTF, filter.getBannedOfferIds());
setupFieldFromList(bannedFromTradingTF, filter.getNodeAddressesBannedFromTrading()); setupFieldFromList(bannedFromTradingTF, filter.getNodeAddressesBannedFromTrading());
setupFieldFromList(bannedFromNetworkTF, filter.getNodeAddressesBannedFromNetwork()); setupFieldFromList(bannedFromNetworkTF, filter.getNodeAddressesBannedFromNetwork());
@ -247,6 +251,8 @@ public class FilterWindow extends Overlay<FilterWindow> {
makerFeeBsqTF.setText(bsqFormatter.formatBSQSatoshis(filter.getMakerFeeBsq())); makerFeeBsqTF.setText(bsqFormatter.formatBSQSatoshis(filter.getMakerFeeBsq()));
takerFeeBsqTF.setText(bsqFormatter.formatBSQSatoshis(filter.getTakerFeeBsq())); takerFeeBsqTF.setText(bsqFormatter.formatBSQSatoshis(filter.getTakerFeeBsq()));
setupFieldFromPaymentAccountFiltersList(delayedPayoutTF, filter.getDelayedPayoutPaymentAccounts()); setupFieldFromPaymentAccountFiltersList(delayedPayoutTF, filter.getDelayedPayoutPaymentAccounts());
} else {
uidTF.setText(UUID.randomUUID().toString());
} }
Button removeFilterMessageButton = new AutoTooltipButton(Res.get("filterWindow.remove")); Button removeFilterMessageButton = new AutoTooltipButton(Res.get("filterWindow.remove"));
@ -292,7 +298,8 @@ public class FilterWindow extends Overlay<FilterWindow> {
ParsingUtils.parseToCoin(takerFeeBsqTF.getText(), bsqFormatter).value, ParsingUtils.parseToCoin(takerFeeBsqTF.getText(), bsqFormatter).value,
readAsPaymentAccountFiltersList(delayedPayoutTF), readAsPaymentAccountFiltersList(delayedPayoutTF),
readAsList(addedBtcNodesTF), readAsList(addedBtcNodesTF),
readAsList(addedSeedNodesTF) readAsList(addedSeedNodesTF),
uidTF.getText()
); );
// We remove first the old filter // We remove first the old filter

View File

@ -55,6 +55,7 @@ import bisq.network.p2p.storage.persistence.RemovedPayloadsService;
import bisq.network.p2p.storage.persistence.ResourceDataStoreService; import bisq.network.p2p.storage.persistence.ResourceDataStoreService;
import bisq.network.p2p.storage.persistence.SequenceNumberMap; import bisq.network.p2p.storage.persistence.SequenceNumberMap;
import bisq.common.ExcludeForHashAwareProto;
import bisq.common.Timer; import bisq.common.Timer;
import bisq.common.UserThread; import bisq.common.UserThread;
import bisq.common.app.Capabilities; import bisq.common.app.Capabilities;
@ -71,6 +72,8 @@ import bisq.common.util.Hex;
import bisq.common.util.Tuple2; import bisq.common.util.Tuple2;
import bisq.common.util.Utilities; import bisq.common.util.Utilities;
import protobuf.StoragePayload;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import com.google.inject.name.Named; import com.google.inject.name.Named;
@ -1253,8 +1256,8 @@ public class P2PDataStorage implements MessageListener, ConnectionListener, Pers
*/ */
@EqualsAndHashCode @EqualsAndHashCode
@ToString @ToString
public static final class DataAndSeqNrPair implements NetworkPayload { public static final class DataAndSeqNrPair implements NetworkPayload, ExcludeForHashAwareProto {
// data are only used for calculating cryptographic hash from both values so they are kept private // data are only used for calculating cryptographic hash from both values, so they are kept private
private final ProtectedStoragePayload protectedStoragePayload; private final ProtectedStoragePayload protectedStoragePayload;
private final int sequenceNumber; private final int sequenceNumber;
@ -1263,13 +1266,31 @@ public class P2PDataStorage implements MessageListener, ConnectionListener, Pers
this.sequenceNumber = sequenceNumber; this.sequenceNumber = sequenceNumber;
} }
// Used only for calculating hash of byte array from PB object
@Override @Override
public com.google.protobuf.Message toProtoMessage() { public protobuf.DataAndSeqNrPair toProtoMessage() {
return toProto(false);
}
@Override
public protobuf.DataAndSeqNrPair toProto(boolean serializeForHash) {
return resolveProto(serializeForHash);
}
@Override
public protobuf.DataAndSeqNrPair.Builder getBuilder(boolean serializeForHash) {
return protobuf.DataAndSeqNrPair.newBuilder() return protobuf.DataAndSeqNrPair.newBuilder()
.setPayload((protobuf.StoragePayload) protectedStoragePayload.toProtoMessage()) .setPayload(toStoragePayloadProto(serializeForHash))
.setSequenceNumber(sequenceNumber) .setSequenceNumber(sequenceNumber);
.build(); }
private protobuf.StoragePayload toStoragePayloadProto(boolean serializeForHash) {
if (protectedStoragePayload instanceof ExcludeForHashAwareProto) {
ExcludeForHashAwareProto proto = (ExcludeForHashAwareProto) protectedStoragePayload;
StoragePayload.Builder builder = (StoragePayload.Builder) proto.getBuilder(serializeForHash);
return resolveBuilder(builder, serializeForHash).build();
} else {
return (StoragePayload) protectedStoragePayload.toProtoMessage();
}
} }
} }

View File

@ -32,6 +32,8 @@ import com.google.protobuf.Message;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import org.bouncycastle.util.encoders.Hex;
import java.security.PublicKey; import java.security.PublicKey;
import java.time.Clock; import java.time.Clock;
@ -215,7 +217,9 @@ public class ProtectedStorageEntry implements NetworkPayload, PersistablePayload
boolean result = Sig.verify(this.ownerPubKey, hashOfDataAndSeqNr, this.signature); boolean result = Sig.verify(this.ownerPubKey, hashOfDataAndSeqNr, this.signature);
if (!result) if (!result)
log.warn("ProtectedStorageEntry::isSignatureValid() failed.\n{}}", this); log.warn("Invalid signature for {}.\nSerialized data as hex={}}",
protectedStoragePayload.getClass().getSimpleName(),
Hex.toHexString(protectedStoragePayload.toProtoMessage().toByteArray()));
return result; return result;
} catch (CryptoException e) { } catch (CryptoException e) {

View File

@ -769,6 +769,7 @@ message Filter {
repeated PaymentAccountFilter delayedPayoutPaymentAccounts = 36; repeated PaymentAccountFilter delayedPayoutPaymentAccounts = 36;
repeated string addedBtcNodes = 37; repeated string addedBtcNodes = 37;
repeated string addedSeedNodes = 38; repeated string addedSeedNodes = 38;
string uid = 39;
} }
/* Deprecated */ /* Deprecated */