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 52 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
bd94857
Implement WarningTransactionFactory
alvasw Aug 6, 2023
6affded
Implement RedirectionTransactionFactory
alvasw Aug 6, 2023
e314188
Implement ClaimTransactionFactory
alvasw Aug 6, 2023
6eeb92b
Refactoring: Rename BuyerProtocol to BaseBuyerProtocol and SellerProt…
HenrikJannsen Aug 11, 2023
bbdf1ac
Refactoring: Add BuyerProtocol and SellerProtocol interfaces
HenrikJannsen Aug 11, 2023
9292815
Refactoring: Use BuyerProtocol interfaces instead of BaseBuyerProtoco…
HenrikJannsen Aug 11, 2023
feca78b
Refactoring: Use SellerProtocol interfaces instead of BaseSellerProto…
HenrikJannsen Aug 11, 2023
f7f3b37
Refactoring: Make BaseBuyerProtocol and BaseSellerProtocol package pr…
HenrikJannsen Aug 11, 2023
65a140a
Refactoring: Move protocol interfaces one level up
HenrikJannsen Aug 11, 2023
3ac758d
Refactoring: Remove comments
HenrikJannsen Aug 11, 2023
5d35a98
Refactoring: Add methods to implementation classes even they have not…
HenrikJannsen Aug 11, 2023
7a8f5c2
Refactoring: Use getTradeProtocolVersion getter instead of public TRA…
HenrikJannsen Aug 11, 2023
83b1f22
Refactoring: Rename Protocol classes with `_v4` postfix and move to p…
HenrikJannsen Aug 11, 2023
15d25d0
Use new protocol version after activation date
HenrikJannsen Aug 11, 2023
8ed3f7a
Add copies of protocol classes to bisq_v5 package. Those will serve a…
HenrikJannsen Aug 11, 2023
3393e48
Use new protocol classes if version 5 is activated
HenrikJannsen Aug 11, 2023
c5a2137
Add new address entries
HenrikJannsen Aug 11, 2023
29f6d8f
Set v5 activation date in past for dev testing
HenrikJannsen Aug 11, 2023
96ee168
Change method signatures
HenrikJannsen Aug 11, 2023
1f1753b
Add util method for calculating fee rate which was used for the depos…
HenrikJannsen Aug 11, 2023
965764a
Add StagedPayoutTxParameters class which holds relevant protocol para…
HenrikJannsen Aug 11, 2023
d1bae86
Add fields for new protocol.
HenrikJannsen Aug 11, 2023
e586b57
Add InputsForDepositTxResponse_v5 message
HenrikJannsen Aug 11, 2023
76f7e28
Add new tasks for BuyerAsMakerProtocol_v5. We might generalize later …
HenrikJannsen Aug 11, 2023
0971d9b
Add new fields
HenrikJannsen Aug 11, 2023
0380f2e
Add InputsForDepositTxResponse_v5 message
HenrikJannsen Aug 11, 2023
ed729f9
Add tasks for second phase
HenrikJannsen Aug 11, 2023
2c3ca21
Add BuyersRedirectSellerSignatureRequest
HenrikJannsen Aug 11, 2023
c029b87
Add tasks for 3rd phase at buyer
HenrikJannsen Aug 11, 2023
bf1e6de
Add BuyersRedirectSellerSignatureResponse
HenrikJannsen Aug 11, 2023
bf64b1e
Add tasks for 4th phase at seller
HenrikJannsen Aug 11, 2023
fc7932e
Comment out correctlySpends checks at claim and redirect txs.
HenrikJannsen Aug 11, 2023
20290f7
Temp changes
stejbac May 17, 2024
8e63921
More temp changes
stejbac Jul 2, 2024
c257b70
Fix the warning, redirect & claim txs
stejbac Jul 6, 2024
8de1468
More changes the get the trade start working
stejbac Jul 6, 2024
f7dc741
Fix warning/redirect/claim tx fee calculations
stejbac Jul 17, 2024
8f72f91
Make further improvements to the redirect tx fee precision
stejbac Jul 24, 2024
4c780aa
Provide missing persistence for warning/redirect/claim txs
stejbac Jul 24, 2024
0c915e6
Fix UI+log errors/warnings in happy path of v5 trade protocol
stejbac Jul 24, 2024
8d0e8bd
Add some missing @Nullable annotations
stejbac Aug 1, 2024
2e86bdd
Ensure v5 staged txs are linked to trade in Transactions view
stejbac Sep 6, 2024
dfce59e
Use watched scripts to pick up broadcast of staged txs
stejbac Sep 8, 2024
810fd34
Make Transactions view display correct types & amounts for staged txs
stejbac Sep 11, 2024
370b855
Use LowRSigningKey for warning, redirect & claim txs
stejbac Sep 11, 2024
cf4016e
Add extra dispute states for v5 protocol
stejbac Sep 14, 2024
ce5087c
Add preliminary code to publish warning tx if mediation fails
stejbac Sep 14, 2024
21b0c35
Add listener to pick up warning tx broadcast
stejbac Sep 22, 2024
463ab45
Pick up WARNING_SENT* dispute states in TradeStepView
stejbac Sep 22, 2024
d5ce7bc
Add downstream listeners to SetupWarningTxListener & rename
stejbac Sep 25, 2024
97f761b
Make Transactions view tolerate missing tx witness data
stejbac Sep 28, 2024
4230a38
Add code to redirect or claim after warning tx published
stejbac Oct 1, 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