diff --git a/core/src/main/java/bisq/core/account/sign/SignedWitnessService.java b/core/src/main/java/bisq/core/account/sign/SignedWitnessService.java index 61050581760..8ac0f61cdf2 100644 --- a/core/src/main/java/bisq/core/account/sign/SignedWitnessService.java +++ b/core/src/main/java/bisq/core/account/sign/SignedWitnessService.java @@ -61,6 +61,7 @@ public class SignedWitnessService { public static final long SIGNER_AGE_DAYS = 30; public 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; private final P2PService p2PService; @@ -190,6 +191,11 @@ public void signAccountAgeWitness(Coin tradeAmount, return; } + if (!isSufficientTradeAmountForSigning(tradeAmount)) { + log.warn("Trader tried to sign account with too little trade amount"); + return; + } + byte[] signature = Sig.sign(keyRing.getSignatureKeyPair().getPrivate(), accountAgeWitness.getHash()); SignedWitness signedWitness = new SignedWitness(SignedWitness.VerificationMethod.TRADE, accountAgeWitness.getHash(), @@ -281,6 +287,10 @@ public boolean isSignerAccountAgeWitness(AccountAgeWitness accountAgeWitness) { return isSignerAccountAgeWitness(accountAgeWitness, new Date().getTime()); } + public boolean isSufficientTradeAmountForSigning(Coin tradeAmount) { + return !tradeAmount.isLessThan(MINIMUM_TRADE_AMOUNT_FOR_SIGNING); + } + /** * Checks whether the accountAgeWitness has a valid signature from a peer/arbitrator and is allowed to sign * other accounts. diff --git a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java index e4beb2d7583..ace09f21943 100644 --- a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java +++ b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java @@ -703,6 +703,10 @@ public boolean accountIsSigner(AccountAgeWitness accountAgeWitness) { return signedWitnessService.isSignerAccountAgeWitness(accountAgeWitness); } + public boolean tradeAmountIsSufficient(Coin tradeAmount) { + return signedWitnessService.isSufficientTradeAmountForSigning(tradeAmount); + } + public SignState getSignState(Offer offer) { return findWitness(offer) .map(this::getSignState) diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 405932fbaf3..68c3f993fdf 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -736,8 +736,6 @@ portfolio.pending.step5_buyer.takersMiningFee=Total mining fees portfolio.pending.step5_buyer.refunded=Refunded security deposit portfolio.pending.step5_buyer.withdrawBTC=Withdraw your bitcoin portfolio.pending.step5_buyer.amount=Amount to withdraw -portfolio.pending.step5_buyer.signer=By withdrawing your bitcoins, you verify that the \ - counterparty has acted according to the trade protocol. portfolio.pending.step5_buyer.withdrawToAddress=Withdraw to address portfolio.pending.step5_buyer.moveToBisqWallet=Move funds to Bisq wallet portfolio.pending.step5_buyer.withdrawExternal=Withdraw to external wallet diff --git a/core/src/test/java/bisq/core/account/sign/SignedWitnessServiceTest.java b/core/src/test/java/bisq/core/account/sign/SignedWitnessServiceTest.java index d9c7f5ad1dc..ca5e950ff3d 100644 --- a/core/src/test/java/bisq/core/account/sign/SignedWitnessServiceTest.java +++ b/core/src/test/java/bisq/core/account/sign/SignedWitnessServiceTest.java @@ -21,11 +21,16 @@ import bisq.core.account.witness.AccountAgeWitness; import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; +import bisq.network.p2p.P2PService; +import bisq.network.p2p.storage.payload.PersistableNetworkPayload; import bisq.network.p2p.storage.persistence.AppendOnlyDataStoreService; +import bisq.common.crypto.CryptoException; +import bisq.common.crypto.KeyRing; import bisq.common.crypto.Sig; import bisq.common.util.Utilities; +import org.bitcoinj.core.Coin; import org.bitcoinj.core.ECKey; import com.google.common.base.Charsets; @@ -44,9 +49,7 @@ import static bisq.core.account.sign.SignedWitness.VerificationMethod.TRADE; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; public class SignedWitnessServiceTest { private SignedWitnessService signedWitnessService; @@ -74,6 +77,8 @@ public class SignedWitnessServiceTest { private long SIGN_AGE_1 = SignedWitnessService.SIGNER_AGE_DAYS * 3 + 5; private long SIGN_AGE_2 = SignedWitnessService.SIGNER_AGE_DAYS * 2 + 4; private long SIGN_AGE_3 = SignedWitnessService.SIGNER_AGE_DAYS + 3; + private KeyRing keyRing; + private P2PService p2pService; @Before @@ -81,7 +86,9 @@ public void setup() throws Exception { AppendOnlyDataStoreService appendOnlyDataStoreService = mock(AppendOnlyDataStoreService.class); ArbitratorManager arbitratorManager = mock(ArbitratorManager.class); when(arbitratorManager.isPublicKeyInList(any())).thenReturn(true); - signedWitnessService = new SignedWitnessService(null, null, arbitratorManager, null, appendOnlyDataStoreService, null); + keyRing = mock(KeyRing.class); + p2pService = mock(P2PService.class); + signedWitnessService = new SignedWitnessService(keyRing, p2pService, arbitratorManager, null, appendOnlyDataStoreService, null); 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}); @@ -331,5 +338,35 @@ private long getTodayMinusNDays(long days) { return Instant.ofEpochMilli(new Date().getTime()).minus(days, ChronoUnit.DAYS).toEpochMilli(); } + @Test + public void testSignAccountAgeWitness_withTooLowTradeAmount() throws CryptoException { + long accountCreationTime = getTodayMinusNDays(SIGN_AGE_1 + 1); + + KeyPair peerKeyPair = Sig.generateKeyPair(); + KeyPair signerKeyPair = Sig.generateKeyPair(); + + when(keyRing.getSignatureKeyPair()).thenReturn(signerKeyPair); + + AccountAgeWitness accountAgeWitness = new AccountAgeWitness(account1DataHash, accountCreationTime); + signedWitnessService.signAccountAgeWitness(Coin.ZERO, accountAgeWitness, peerKeyPair.getPublic()); + + verify(p2pService, never()).addPersistableNetworkPayload(any(PersistableNetworkPayload.class), anyBoolean()); + } + + @Test + public void testSignAccountAgeWitness_withSufficientTradeAmount() throws CryptoException { + long accountCreationTime = getTodayMinusNDays(SIGN_AGE_1 + 1); + + KeyPair peerKeyPair = Sig.generateKeyPair(); + KeyPair signerKeyPair = Sig.generateKeyPair(); + + when(keyRing.getSignatureKeyPair()).thenReturn(signerKeyPair); + + + AccountAgeWitness accountAgeWitness = new AccountAgeWitness(account1DataHash, accountCreationTime); + signedWitnessService.signAccountAgeWitness(SignedWitnessService.MINIMUM_TRADE_AMOUNT_FOR_SIGNING, accountAgeWitness, peerKeyPair.getPublic()); + + verify(p2pService, times(1)).addPersistableNetworkPayload(any(PersistableNetworkPayload.class), anyBoolean()); + } } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java index cac5f553379..97b1426a951 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java @@ -32,8 +32,8 @@ import bisq.core.trade.Trade; import bisq.core.trade.closed.ClosedTradableManager; import bisq.core.user.User; -import bisq.core.util.coin.BsqFormatter; import bisq.core.util.FormattingUtils; +import bisq.core.util.coin.BsqFormatter; import bisq.core.util.coin.CoinFormatter; import bisq.core.util.validation.BtcAddressValidator; @@ -352,19 +352,18 @@ public int getNumPastTrades(Trade trade) { /////////////////////////////////////////////////////////////////////////////////////////// - public boolean isSignWitnessTrade(boolean asSeller) { + public boolean isSignWitnessTrade() { checkNotNull(trade, "trade must not be null"); checkNotNull(trade.getOffer(), "offer must not be null"); - AccountAgeWitness myWitness = accountAgeWitnessService.getMyWitness(asSeller ? - dataModel.getSellersPaymentAccountPayload() : - dataModel.getBuyersPaymentAccountPayload()); + AccountAgeWitness myWitness = accountAgeWitnessService.getMyWitness(dataModel.getSellersPaymentAccountPayload()); return accountAgeWitnessService.accountIsSigner(myWitness) && - !accountAgeWitnessService.peerHasSignedWitness(trade); + !accountAgeWitnessService.peerHasSignedWitness(trade) && + accountAgeWitnessService.tradeAmountIsSufficient(trade.getTradeAmount()); } - public void maybeSignWitness(boolean asSeller) { - if (isSignWitnessTrade(asSeller)) { + public void maybeSignWitness() { + if (isSignWitnessTrade()) { accountAgeWitnessService.traderSignPeersAccountAgeWitness(trade); } } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java index 036b3e68f46..1f3a05ed6e9 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java @@ -120,12 +120,6 @@ protected void addContent() { withdrawAddressTextField.setManaged(false); withdrawAddressTextField.setVisible(false); - if (model.isSignWitnessTrade(false)) { - Label signLabel = new Label(Res.get("portfolio.pending.step5_buyer.signer")); - GridPane.setRowIndex(signLabel, ++gridRow); - gridPane.getChildren().add(signLabel); - } - HBox hBox = new HBox(); hBox.setSpacing(10); useSavingsWalletButton = new AutoTooltipButton(Res.get("portfolio.pending.step5_buyer.moveToBisqWallet")); @@ -141,12 +135,10 @@ protected void addContent() { gridPane.getChildren().add(hBox); useSavingsWalletButton.setOnAction(e -> { - model.maybeSignWitness(false); handleTradeCompleted(); model.dataModel.tradeManager.addTradeToClosedTrades(trade); }); withdrawToExternalWalletButton.setOnAction(e -> { - model.maybeSignWitness(false); onWithdrawal(); }); diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java index 08330b2bde7..07ab8774ed4 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java @@ -294,7 +294,7 @@ private void onPaymentReceived() { } } message += Res.get("portfolio.pending.step3_seller.onPaymentReceived.note"); - if (model.isSignWitnessTrade(true)) { + if (model.isSignWitnessTrade()) { message += Res.get("portfolio.pending.step3_seller.onPaymentReceived.signer"); } new Popup() @@ -351,7 +351,7 @@ else if (paymentAccountPayload instanceof F2FAccountPayload) message += Res.get("portfolio.pending.step3_seller.bankCheck", optionalHolderName.get(), part); } - if (model.isSignWitnessTrade(true)) { + if (model.isSignWitnessTrade()) { message += Res.get("portfolio.pending.step3_seller.onPaymentReceived.signer"); } } @@ -370,7 +370,7 @@ private void confirmPaymentReceived() { if (!trade.isPayoutPublished()) trade.setState(Trade.State.SELLER_CONFIRMED_IN_UI_FIAT_PAYMENT_RECEIPT); - model.maybeSignWitness(true); + model.maybeSignWitness(); model.dataModel.onFiatPaymentReceived(() -> { // In case the first send failed we got the support button displayed.