Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V5 trade protocol #7105

Draft
wants to merge 58 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
2427f91
Implement WarningTransactionFactory
alvasw Aug 6, 2023
bd5379b
Implement RedirectionTransactionFactory
alvasw Aug 6, 2023
71d5f4a
Implement ClaimTransactionFactory
alvasw Aug 6, 2023
bce41a3
Refactoring: Rename BuyerProtocol to BaseBuyerProtocol and SellerProt…
HenrikJannsen Aug 11, 2023
b6f9cd8
Refactoring: Add BuyerProtocol and SellerProtocol interfaces
HenrikJannsen Aug 11, 2023
1b33d25
Refactoring: Use BuyerProtocol interfaces instead of BaseBuyerProtoco…
HenrikJannsen Aug 11, 2023
15d29c4
Refactoring: Use SellerProtocol interfaces instead of BaseSellerProto…
HenrikJannsen Aug 11, 2023
8896f01
Refactoring: Make BaseBuyerProtocol and BaseSellerProtocol package pr…
HenrikJannsen Aug 11, 2023
2abf282
Refactoring: Move protocol interfaces one level up
HenrikJannsen Aug 11, 2023
01bcc6e
Refactoring: Remove comments
HenrikJannsen Aug 11, 2023
25e2937
Refactoring: Add methods to implementation classes even they have not…
HenrikJannsen Aug 11, 2023
6314619
Refactoring: Use getTradeProtocolVersion getter instead of public TRA…
HenrikJannsen Aug 11, 2023
06d91be
Refactoring: Rename Protocol classes with `_v4` postfix and move to p…
HenrikJannsen Aug 11, 2023
e5eec52
Use new protocol version after activation date
HenrikJannsen Aug 11, 2023
3c87ea5
Add copies of protocol classes to bisq_v5 package. Those will serve a…
HenrikJannsen Aug 11, 2023
2ec3098
Use new protocol classes if version 5 is activated
HenrikJannsen Aug 11, 2023
c11c687
Add new address entries
HenrikJannsen Aug 11, 2023
6593b2e
Set v5 activation date in past for dev testing
HenrikJannsen Aug 11, 2023
6ffffc7
Change method signatures
HenrikJannsen Aug 11, 2023
8fd95b7
Add util method for calculating fee rate which was used for the depos…
HenrikJannsen Aug 11, 2023
a37c16d
Add StagedPayoutTxParameters class which holds relevant protocol para…
HenrikJannsen Aug 11, 2023
2800749
Add fields for new protocol.
HenrikJannsen Aug 11, 2023
299c05f
Add InputsForDepositTxResponse_v5 message
HenrikJannsen Aug 11, 2023
0f88863
Add new tasks for BuyerAsMakerProtocol_v5. We might generalize later …
HenrikJannsen Aug 11, 2023
d4f7bff
Add new fields
HenrikJannsen Aug 11, 2023
e4f0ed9
Add InputsForDepositTxResponse_v5 message
HenrikJannsen Aug 11, 2023
408d420
Add tasks for second phase
HenrikJannsen Aug 11, 2023
e1e19fc
Add BuyersRedirectSellerSignatureRequest
HenrikJannsen Aug 11, 2023
08b1640
Add tasks for 3rd phase at buyer
HenrikJannsen Aug 11, 2023
b0ce2d5
Add BuyersRedirectSellerSignatureResponse
HenrikJannsen Aug 11, 2023
228e49e
Add tasks for 4th phase at seller
HenrikJannsen Aug 11, 2023
f07a923
Comment out correctlySpends checks at claim and redirect txs.
HenrikJannsen Aug 11, 2023
282f81e
Temp changes
stejbac May 17, 2024
668a7f8
More temp changes
stejbac Jul 2, 2024
acd711a
Fix the warning, redirect & claim txs
stejbac Jul 6, 2024
a95d4cf
More changes the get the trade start working
stejbac Jul 6, 2024
b168100
Fix warning/redirect/claim tx fee calculations
stejbac Jul 17, 2024
99b69f2
Make further improvements to the redirect tx fee precision
stejbac Jul 24, 2024
4291dea
Provide missing persistence for warning/redirect/claim txs
stejbac Jul 24, 2024
888b258
Fix UI+log errors/warnings in happy path of v5 trade protocol
stejbac Jul 24, 2024
fcbd833
Add some missing @Nullable annotations
stejbac Aug 1, 2024
4753c58
Ensure v5 staged txs are linked to trade in Transactions view
stejbac Sep 6, 2024
3e6b4cf
Use watched scripts to pick up broadcast of staged txs
stejbac Sep 8, 2024
dc5284a
Make Transactions view display correct types & amounts for staged txs
stejbac Sep 11, 2024
89af696
Use LowRSigningKey for warning, redirect & claim txs
stejbac Sep 11, 2024
315e7be
Add extra dispute states for v5 protocol
stejbac Sep 14, 2024
af33eca
Add preliminary code to publish warning tx if mediation fails
stejbac Sep 14, 2024
2a6ee91
Add listener to pick up warning tx broadcast
stejbac Sep 22, 2024
2c73fb0
Pick up WARNING_SENT* dispute states in TradeStepView
stejbac Sep 22, 2024
bdaa03b
Add downstream listeners to SetupWarningTxListener & rename
stejbac Sep 25, 2024
1ffc914
Make Transactions view tolerate missing tx witness data
stejbac Sep 28, 2024
ab3502a
Add code to redirect or claim after warning tx published
stejbac Oct 1, 2024
0f626d4
Refactor replay detection logic in DisputeValidation
stejbac Oct 3, 2024
b7330a2
Tidy up DisputeView & fix broken comparator
stejbac Oct 5, 2024
da05ebf
Add warning & redirect txIds to dispute details & filter
stejbac Oct 9, 2024
360b8a4
Adapt refund agent tx chain validation to v5 protocol
stejbac Oct 9, 2024
cdc61ea
Sanity check deposit tx output & receiver total before refunding
stejbac Oct 11, 2024
f2d3273
Restructure redeem script to reduce size of redirect & claim txs
stejbac Dec 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 16 additions & 7 deletions common/src/main/java/bisq/common/app/Version.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@

package bisq.common.app;

import bisq.common.util.Utilities;

import java.net.URL;

import java.util.Arrays;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
Expand Down Expand Up @@ -98,16 +102,21 @@ private static int getSubVersion(String version, int index) {
// VERSION = 0.5.0 -> LOCAL_DB_VERSION = 1
public static final int LOCAL_DB_VERSION = 1;

// The version no. of the current protocol. The offer holds that version.
// A taker will check the version of the offers to see if his version is compatible.
// For the switch to version 2, offers created with the old version will become invalid and have to be canceled.
// For the switch to version 3, offers created with the old version can be migrated to version 3 just by opening
// the Bisq app.
// VERSION = 0.5.0 -> TRADE_PROTOCOL_VERSION = 1
// Version 1.2.2 -> TRADE_PROTOCOL_VERSION = 2
// Version 1.5.0 -> TRADE_PROTOCOL_VERSION = 3
// Version 1.7.0 -> TRADE_PROTOCOL_VERSION = 4
public static final int TRADE_PROTOCOL_VERSION = 4;
// Version 1.9.13 and after activation date -> TRADE_PROTOCOL_VERSION = 5
public static final Date PROTOCOL_5_ACTIVATION_DATE = Utilities.getUTCDate(2023, GregorianCalendar.AUGUST, 1);

public static boolean isTradeProtocolVersion5Activated() {
return new Date().after(PROTOCOL_5_ACTIVATION_DATE);
}

public static int getTradeProtocolVersion() {
return isTradeProtocolVersion5Activated() ? 5 : 4;
}

private static int p2pMessageVersion;

public static final String BSQ_TX_VERSION = "1";
Expand Down Expand Up @@ -136,7 +145,7 @@ public static void printVersion() {
"VERSION=" + VERSION +
", P2P_NETWORK_VERSION=" + P2P_NETWORK_VERSION +
", LOCAL_DB_VERSION=" + LOCAL_DB_VERSION +
", TRADE_PROTOCOL_VERSION=" + TRADE_PROTOCOL_VERSION +
", TRADE_PROTOCOL_VERSION=" + getTradeProtocolVersion() +
", BASE_CURRENCY_NETWORK=" + BASE_CURRENCY_NETWORK +
", getP2PNetworkId()=" + getP2PMessageVersion() +
'}');
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/java/bisq/core/api/CoreTradesService.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@
import bisq.core.trade.model.TradeModel;
import bisq.core.trade.model.bisq_v1.Trade;
import bisq.core.trade.model.bsq_swap.BsqSwapTrade;
import bisq.core.trade.protocol.bisq_v1.BuyerProtocol;
import bisq.core.trade.protocol.bisq_v1.SellerProtocol;
import bisq.core.trade.protocol.BuyerProtocol;
import bisq.core.trade.protocol.SellerProtocol;
import bisq.core.user.User;
import bisq.core.util.validation.BtcAddressValidator;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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.btc.listeners;

import org.bitcoinj.core.TransactionConfidence;
import org.bitcoinj.core.TransactionOutput;

import lombok.Getter;

public abstract class OutputSpendConfidenceListener {
@Getter
private final TransactionOutput output;

public OutputSpendConfidenceListener(TransactionOutput output) {
this.output = output;
}

public abstract void onOutputSpendConfidenceChanged(TransactionConfidence confidence);
}
4 changes: 3 additions & 1 deletion core/src/main/java/bisq/core/btc/model/AddressEntry.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ public enum Context {
OFFER_FUNDING,
RESERVED_FOR_TRADE,
MULTI_SIG,
TRADE_PAYOUT
TRADE_PAYOUT,
WARNING_TX_FEE_BUMP,
REDIRECT_TX_FEE_BUMP
}

// keyPair can be null in case the object is created from deserialization as it is transient.
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/bisq/core/btc/setup/WalletConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,7 @@ public File directory() {
return directory;
}

public void maybeAddSegwitKeychain(Wallet wallet, KeyParameter aesKey, boolean isBsqWallet) {
public void maybeAddSegwitKeychain(Wallet wallet, @Nullable KeyParameter aesKey, boolean isBsqWallet) {
var nonSegwitAccountPath = isBsqWallet
? BisqKeyChainGroupStructure.BIP44_BSQ_NON_SEGWIT_ACCOUNT_PATH
: BisqKeyChainGroupStructure.BIP44_BTC_NON_SEGWIT_ACCOUNT_PATH;
Expand Down
19 changes: 11 additions & 8 deletions core/src/main/java/bisq/core/btc/wallet/BisqRiskAnalysis.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,14 @@

// Copied from DefaultRiskAnalysis as DefaultRiskAnalysis has mostly private methods and constructor so we cannot
// override it.
// The changes to DefaultRiskAnalysis are: removal of the RBF check and accept as standard an OP_RETURN outputs
// with 0 value.
// The changes to DefaultRiskAnalysis are: removal of the RBF check and removal of the relative lock-time check.
// For Bisq's use cases RBF is not considered risky. Requiring a confirmation for RBF payments from a user's
// external wallet to Bisq would hurt usability. The trade transaction requires anyway a confirmation and we don't see
// a use case where a Bisq user accepts unconfirmed payment from untrusted peers and would not wait anyway for at least
// one confirmation.
// Relative lock-times are used by claim txs for the v5 trade protocol. It's doubtful that they would realistically
// show up in any other context (maybe forced lightning channel closures spending straight to Bisq) or would ever be
// replaced once broadcast, so we deem them non-risky.

/**
* <p>The default risk analysis. Currently, it only is concerned with whether a tx/dependency is non-final or not, and
Expand Down Expand Up @@ -122,12 +124,13 @@ private Result analyzeIsFinal() {
// return Result.NON_FINAL;
// }

// Relative time-locked transactions are risky too. We can't check the locks because usually we don't know the
// spent outputs (to know when they were created).
if (tx.hasRelativeLockTime()) {
nonFinal = tx;
return Result.NON_FINAL;
}
// Commented out to accept claim txs for v5 trade protocol.
// // Relative time-locked transactions are risky too. We can't check the locks because usually we don't know the
// // spent outputs (to know when they were created).
// if (tx.hasRelativeLockTime()) {
// nonFinal = tx;
// return Result.NON_FINAL;
// }

if (wallet == null)
return null;
Expand Down
145 changes: 145 additions & 0 deletions core/src/main/java/bisq/core/btc/wallet/ClaimTransactionFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
* 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.btc.wallet;

import bisq.core.btc.exceptions.TransactionVerificationException;
import bisq.core.crypto.LowRSigningKey;

import org.bitcoinj.core.Address;
import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionInput;
import org.bitcoinj.core.TransactionOutput;
import org.bitcoinj.core.TransactionWitness;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.crypto.TransactionSignature;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptBuilder;

import org.bouncycastle.crypto.params.KeyParameter;

import javax.annotation.Nullable;

import static com.google.common.base.Preconditions.checkArgument;

public class ClaimTransactionFactory {
private final NetworkParameters params;

public ClaimTransactionFactory(NetworkParameters params) {
this.params = params;
}

public Transaction createSignedClaimTransaction(TransactionOutput warningTxOutput,
boolean isBuyer,
long claimDelay,
Address payoutAddress,
long miningFee,
byte[] peersMultiSigPubKey,
DeterministicKey myMultiSigKeyPair,
@Nullable KeyParameter aesKey)
throws AddressFormatException, TransactionVerificationException {

Transaction claimTx = createUnsignedClaimTransaction(warningTxOutput, claimDelay, payoutAddress, miningFee);
byte[] buyerPubKey = isBuyer ? myMultiSigKeyPair.getPubKey() : peersMultiSigPubKey;
byte[] sellerPubKey = isBuyer ? peersMultiSigPubKey : myMultiSigKeyPair.getPubKey();
ECKey.ECDSASignature mySignature = signClaimTransaction(claimTx, warningTxOutput, isBuyer, claimDelay,
buyerPubKey, sellerPubKey, myMultiSigKeyPair, aesKey);
return finalizeClaimTransaction(claimTx, warningTxOutput, isBuyer, claimDelay, buyerPubKey, sellerPubKey, mySignature);
}

private Transaction createUnsignedClaimTransaction(TransactionOutput warningTxOutput,
long claimDelay,
Address payoutAddress,
long miningFee)
throws AddressFormatException, TransactionVerificationException {

Transaction claimTx = new Transaction(params);
claimTx.setVersion(2); // needed to enable relative lock time

claimTx.addInput(warningTxOutput);
claimTx.getInput(0).setSequenceNumber(claimDelay);

Coin amountWithoutMiningFee = warningTxOutput.getValue().subtract(Coin.valueOf(miningFee));
claimTx.addOutput(amountWithoutMiningFee, payoutAddress);

WalletService.printTx("Unsigned claimTx", claimTx);
WalletService.verifyTransaction(claimTx);
return claimTx;
}

private ECKey.ECDSASignature signClaimTransaction(Transaction claimTx,
TransactionOutput warningTxOutput,
boolean isBuyer,
long claimDelay,
byte[] buyerPubKey,
byte[] sellerPubKey,
DeterministicKey myMultiSigKeyPair,
@Nullable KeyParameter aesKey)
throws TransactionVerificationException {

Script redeemScript = WarningTransactionFactory.createRedeemScript(isBuyer, buyerPubKey, sellerPubKey, claimDelay);
checkArgument(ScriptBuilder.createP2WSHOutputScript(redeemScript).equals(warningTxOutput.getScriptPubKey()),
"Redeem script does not hash to expected ScriptPubKey");

Coin claimTxInputValue = warningTxOutput.getValue();
Sha256Hash sigHash = claimTx.hashForWitnessSignature(0, redeemScript, claimTxInputValue,
Transaction.SigHash.ALL, false);

ECKey.ECDSASignature mySignature = LowRSigningKey.from(myMultiSigKeyPair).sign(sigHash, aesKey);
WalletService.printTx("claimTx for sig creation", claimTx);
WalletService.verifyTransaction(claimTx);
return mySignature;
}

private Transaction finalizeClaimTransaction(Transaction claimTx,
TransactionOutput warningTxOutput,
boolean isBuyer,
long claimDelay,
byte[] buyerPubKey,
byte[] sellerPubKey,
ECKey.ECDSASignature mySignature)
throws TransactionVerificationException {

Script redeemScript = WarningTransactionFactory.createRedeemScript(isBuyer, buyerPubKey, sellerPubKey, claimDelay);
TransactionSignature myTxSig = new TransactionSignature(mySignature, Transaction.SigHash.ALL, false);

TransactionInput input = claimTx.getInput(0);
TransactionWitness witness = redeemP2WSH(redeemScript, myTxSig);
input.setWitness(witness);

WalletService.printTx("finalizeClaimTransaction", claimTx);
WalletService.verifyTransaction(claimTx);

Coin inputValue = warningTxOutput.getValue();
Script scriptPubKey = warningTxOutput.getScriptPubKey();
input.getScriptSig().correctlySpends(claimTx, 0, witness, inputValue, scriptPubKey, Script.ALL_VERIFY_FLAGS);
return claimTx;
}

private static TransactionWitness redeemP2WSH(Script witnessScript, TransactionSignature mySignature) {
var witness = new TransactionWitness(3);
witness.setPush(0, mySignature.encodeToBitcoin());
witness.setPush(1, new byte[]{});
witness.setPush(2, witnessScript.getProgram());
return witness;
}
}
Loading
Loading