mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-20 02:12:00 +01:00
Merge pull request #4789 from stejbac/fix-remaining-blackmail-vulnerabilities
Fix remaining blackmail vulnerabilities
This commit is contained in:
commit
741425fad8
@ -76,6 +76,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
public class TradeWalletService {
|
||||
private static final Logger log = LoggerFactory.getLogger(TradeWalletService.class);
|
||||
private static final Coin MIN_DELAYED_PAYOUT_TX_FEE = Coin.valueOf(1000);
|
||||
|
||||
private final WalletsSetup walletsSetup;
|
||||
private final Preferences preferences;
|
||||
@ -569,8 +570,9 @@ public class TradeWalletService {
|
||||
* @param buyerPubKey the public key of the buyer
|
||||
* @param sellerPubKey the public key of the seller
|
||||
* @throws SigningException if (one of) the taker input(s) was of an unrecognized type for signing
|
||||
* @throws TransactionVerificationException if a maker input wasn't signed, their MultiSig script or contract hash
|
||||
* doesn't match the taker's, or there was an unexpected problem with the final deposit tx or its signatures
|
||||
* @throws TransactionVerificationException if a non-P2WH maker-as-buyer input wasn't signed, the maker's MultiSig
|
||||
* script or contract hash doesn't match the taker's, or there was an unexpected problem with the final deposit tx
|
||||
* or its signatures
|
||||
* @throws WalletException if the taker's wallet is null or structurally inconsistent
|
||||
*/
|
||||
public Transaction takerSignsDepositTx(boolean takerIsSeller,
|
||||
@ -602,10 +604,11 @@ public class TradeWalletService {
|
||||
for (int i = 0; i < buyerInputs.size(); i++) {
|
||||
TransactionInput makersInput = makersDepositTx.getInputs().get(i);
|
||||
byte[] makersScriptSigProgram = makersInput.getScriptSig().getProgram();
|
||||
if (makersScriptSigProgram.length == 0 && TransactionWitness.EMPTY.equals(makersInput.getWitness())) {
|
||||
throw new TransactionVerificationException("Inputs from maker not signed.");
|
||||
}
|
||||
TransactionInput input = getTransactionInput(depositTx, makersScriptSigProgram, buyerInputs.get(i));
|
||||
Script scriptPubKey = checkNotNull(input.getConnectedOutput()).getScriptPubKey();
|
||||
if (makersScriptSigProgram.length == 0 && !ScriptPattern.isP2WH(scriptPubKey)) {
|
||||
throw new TransactionVerificationException("Non-segwit inputs from maker not signed.");
|
||||
}
|
||||
if (!TransactionWitness.EMPTY.equals(makersInput.getWitness())) {
|
||||
input.setWitness(makersInput.getWitness());
|
||||
}
|
||||
@ -686,6 +689,21 @@ public class TradeWalletService {
|
||||
}
|
||||
|
||||
|
||||
public void sellerAddsBuyerWitnessesToDepositTx(Transaction myDepositTx,
|
||||
Transaction buyersDepositTxWithWitnesses) {
|
||||
int numberInputs = myDepositTx.getInputs().size();
|
||||
for (int i = 0; i < numberInputs; i++) {
|
||||
var txInput = myDepositTx.getInput(i);
|
||||
var witnessFromBuyer = buyersDepositTxWithWitnesses.getInput(i).getWitness();
|
||||
|
||||
if (TransactionWitness.EMPTY.equals(txInput.getWitness()) &&
|
||||
!TransactionWitness.EMPTY.equals(witnessFromBuyer)) {
|
||||
txInput.setWitness(witnessFromBuyer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Delayed payout tx
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -729,12 +747,14 @@ public class TradeWalletService {
|
||||
return mySignature.encodeToDER();
|
||||
}
|
||||
|
||||
public Transaction finalizeDelayedPayoutTx(Transaction delayedPayoutTx,
|
||||
byte[] buyerPubKey,
|
||||
byte[] sellerPubKey,
|
||||
byte[] buyerSignature,
|
||||
byte[] sellerSignature)
|
||||
throws AddressFormatException, TransactionVerificationException, WalletException, SignatureDecodeException {
|
||||
public Transaction finalizeUnconnectedDelayedPayoutTx(Transaction delayedPayoutTx,
|
||||
byte[] buyerPubKey,
|
||||
byte[] sellerPubKey,
|
||||
byte[] buyerSignature,
|
||||
byte[] sellerSignature,
|
||||
Coin inputValue)
|
||||
throws AddressFormatException, TransactionVerificationException, SignatureDecodeException {
|
||||
|
||||
Script redeemScript = get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey);
|
||||
ECKey.ECDSASignature buyerECDSASignature = ECKey.ECDSASignature.decodeFromDER(buyerSignature);
|
||||
ECKey.ECDSASignature sellerECDSASignature = ECKey.ECDSASignature.decodeFromDER(sellerSignature);
|
||||
@ -746,8 +766,26 @@ public class TradeWalletService {
|
||||
input.setWitness(witness);
|
||||
WalletService.printTx("finalizeDelayedPayoutTx", delayedPayoutTx);
|
||||
WalletService.verifyTransaction(delayedPayoutTx);
|
||||
|
||||
if (checkNotNull(inputValue).isLessThan(delayedPayoutTx.getOutputSum().add(MIN_DELAYED_PAYOUT_TX_FEE))) {
|
||||
throw new TransactionVerificationException("Delayed payout tx is paying less than the minimum allowed tx fee");
|
||||
}
|
||||
Script scriptPubKey = get2of2MultiSigOutputScript(buyerPubKey, sellerPubKey, false);
|
||||
input.getScriptSig().correctlySpends(delayedPayoutTx, 0, witness, inputValue, scriptPubKey, Script.ALL_VERIFY_FLAGS);
|
||||
return delayedPayoutTx;
|
||||
}
|
||||
|
||||
public Transaction finalizeDelayedPayoutTx(Transaction delayedPayoutTx,
|
||||
byte[] buyerPubKey,
|
||||
byte[] sellerPubKey,
|
||||
byte[] buyerSignature,
|
||||
byte[] sellerSignature)
|
||||
throws AddressFormatException, TransactionVerificationException, WalletException, SignatureDecodeException {
|
||||
|
||||
TransactionInput input = delayedPayoutTx.getInput(0);
|
||||
finalizeUnconnectedDelayedPayoutTx(delayedPayoutTx, buyerPubKey, sellerPubKey, buyerSignature, sellerSignature, input.getValue());
|
||||
|
||||
WalletService.checkWalletConsistency(wallet);
|
||||
WalletService.checkScriptSig(delayedPayoutTx, input, 0);
|
||||
checkNotNull(input.getConnectedOutput(), "input.getConnectedOutput() must not be null");
|
||||
input.verify(input.getConnectedOutput());
|
||||
return delayedPayoutTx;
|
||||
@ -1185,11 +1223,20 @@ public class TradeWalletService {
|
||||
private TransactionInput getTransactionInput(Transaction depositTx,
|
||||
byte[] scriptProgram,
|
||||
RawTransactionInput rawTransactionInput) {
|
||||
return new TransactionInput(params, depositTx, scriptProgram, new TransactionOutPoint(params,
|
||||
rawTransactionInput.index, new Transaction(params, rawTransactionInput.parentTransaction)),
|
||||
return new TransactionInput(params, depositTx, scriptProgram, getConnectedOutPoint(rawTransactionInput),
|
||||
Coin.valueOf(rawTransactionInput.value));
|
||||
}
|
||||
|
||||
private TransactionOutPoint getConnectedOutPoint(RawTransactionInput rawTransactionInput) {
|
||||
return new TransactionOutPoint(params, rawTransactionInput.index,
|
||||
new Transaction(params, rawTransactionInput.parentTransaction));
|
||||
}
|
||||
|
||||
public boolean isP2WH(RawTransactionInput rawTransactionInput) {
|
||||
return ScriptPattern.isP2WH(
|
||||
checkNotNull(getConnectedOutPoint(rawTransactionInput).getConnectedOutput()).getScriptPubKey());
|
||||
}
|
||||
|
||||
|
||||
// TODO: Once we have removed legacy arbitrator from dispute domain we can remove that method as well.
|
||||
// Atm it is still used by traderSignAndFinalizeDisputedPayoutTx which is used by ArbitrationManager.
|
||||
|
@ -33,16 +33,19 @@ import lombok.Value;
|
||||
public final class DelayedPayoutTxSignatureRequest extends TradeMessage implements DirectMessage {
|
||||
private final NodeAddress senderNodeAddress;
|
||||
private final byte[] delayedPayoutTx;
|
||||
private final byte[] delayedPayoutTxSellerSignature;
|
||||
|
||||
public DelayedPayoutTxSignatureRequest(String uid,
|
||||
String tradeId,
|
||||
NodeAddress senderNodeAddress,
|
||||
byte[] delayedPayoutTx) {
|
||||
byte[] delayedPayoutTx,
|
||||
byte[] delayedPayoutTxSellerSignature) {
|
||||
this(Version.getP2PMessageVersion(),
|
||||
uid,
|
||||
tradeId,
|
||||
senderNodeAddress,
|
||||
delayedPayoutTx);
|
||||
delayedPayoutTx,
|
||||
delayedPayoutTxSellerSignature);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -53,10 +56,12 @@ public final class DelayedPayoutTxSignatureRequest extends TradeMessage implemen
|
||||
String uid,
|
||||
String tradeId,
|
||||
NodeAddress senderNodeAddress,
|
||||
byte[] delayedPayoutTx) {
|
||||
byte[] delayedPayoutTx,
|
||||
byte[] delayedPayoutTxSellerSignature) {
|
||||
super(messageVersion, tradeId, uid);
|
||||
this.senderNodeAddress = senderNodeAddress;
|
||||
this.delayedPayoutTx = delayedPayoutTx;
|
||||
this.delayedPayoutTxSellerSignature = delayedPayoutTxSellerSignature;
|
||||
}
|
||||
|
||||
|
||||
@ -67,16 +72,19 @@ public final class DelayedPayoutTxSignatureRequest extends TradeMessage implemen
|
||||
.setUid(uid)
|
||||
.setTradeId(tradeId)
|
||||
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
|
||||
.setDelayedPayoutTx(ByteString.copyFrom(delayedPayoutTx)))
|
||||
.setDelayedPayoutTx(ByteString.copyFrom(delayedPayoutTx))
|
||||
.setDelayedPayoutTxSellerSignature(ByteString.copyFrom(delayedPayoutTxSellerSignature)))
|
||||
.build();
|
||||
}
|
||||
|
||||
public static DelayedPayoutTxSignatureRequest fromProto(protobuf.DelayedPayoutTxSignatureRequest proto, int messageVersion) {
|
||||
public static DelayedPayoutTxSignatureRequest fromProto(protobuf.DelayedPayoutTxSignatureRequest proto,
|
||||
int messageVersion) {
|
||||
return new DelayedPayoutTxSignatureRequest(messageVersion,
|
||||
proto.getUid(),
|
||||
proto.getTradeId(),
|
||||
NodeAddress.fromProto(proto.getSenderNodeAddress()),
|
||||
proto.getDelayedPayoutTx().toByteArray());
|
||||
proto.getDelayedPayoutTx().toByteArray(),
|
||||
proto.getDelayedPayoutTxSellerSignature().toByteArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -84,6 +92,7 @@ public final class DelayedPayoutTxSignatureRequest extends TradeMessage implemen
|
||||
return "DelayedPayoutTxSignatureRequest{" +
|
||||
"\n senderNodeAddress=" + senderNodeAddress +
|
||||
",\n delayedPayoutTx=" + Utilities.bytesAsHexString(delayedPayoutTx) +
|
||||
",\n delayedPayoutTxSellerSignature=" + Utilities.bytesAsHexString(delayedPayoutTxSellerSignature) +
|
||||
"\n} " + super.toString();
|
||||
}
|
||||
}
|
||||
|
@ -32,17 +32,20 @@ import lombok.Value;
|
||||
@Value
|
||||
public final class DelayedPayoutTxSignatureResponse extends TradeMessage implements DirectMessage {
|
||||
private final NodeAddress senderNodeAddress;
|
||||
private final byte[] delayedPayoutTxSignature;
|
||||
private final byte[] delayedPayoutTxBuyerSignature;
|
||||
private final byte[] depositTx;
|
||||
|
||||
public DelayedPayoutTxSignatureResponse(String uid,
|
||||
String tradeId,
|
||||
NodeAddress senderNodeAddress,
|
||||
byte[] delayedPayoutTxSignature) {
|
||||
byte[] delayedPayoutTxBuyerSignature,
|
||||
byte[] depositTx) {
|
||||
this(Version.getP2PMessageVersion(),
|
||||
uid,
|
||||
tradeId,
|
||||
senderNodeAddress,
|
||||
delayedPayoutTxSignature);
|
||||
delayedPayoutTxBuyerSignature,
|
||||
depositTx);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -53,10 +56,12 @@ public final class DelayedPayoutTxSignatureResponse extends TradeMessage impleme
|
||||
String uid,
|
||||
String tradeId,
|
||||
NodeAddress senderNodeAddress,
|
||||
byte[] delayedPayoutTxSignature) {
|
||||
byte[] delayedPayoutTxBuyerSignature,
|
||||
byte[] depositTx) {
|
||||
super(messageVersion, tradeId, uid);
|
||||
this.senderNodeAddress = senderNodeAddress;
|
||||
this.delayedPayoutTxSignature = delayedPayoutTxSignature;
|
||||
this.delayedPayoutTxBuyerSignature = delayedPayoutTxBuyerSignature;
|
||||
this.depositTx = depositTx;
|
||||
}
|
||||
|
||||
|
||||
@ -67,24 +72,28 @@ public final class DelayedPayoutTxSignatureResponse extends TradeMessage impleme
|
||||
.setUid(uid)
|
||||
.setTradeId(tradeId)
|
||||
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
|
||||
.setDelayedPayoutTxSignature(ByteString.copyFrom(delayedPayoutTxSignature))
|
||||
.setDelayedPayoutTxBuyerSignature(ByteString.copyFrom(delayedPayoutTxBuyerSignature))
|
||||
.setDepositTx(ByteString.copyFrom(depositTx))
|
||||
)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static DelayedPayoutTxSignatureResponse fromProto(protobuf.DelayedPayoutTxSignatureResponse proto, int messageVersion) {
|
||||
public static DelayedPayoutTxSignatureResponse fromProto(protobuf.DelayedPayoutTxSignatureResponse proto,
|
||||
int messageVersion) {
|
||||
return new DelayedPayoutTxSignatureResponse(messageVersion,
|
||||
proto.getUid(),
|
||||
proto.getTradeId(),
|
||||
NodeAddress.fromProto(proto.getSenderNodeAddress()),
|
||||
proto.getDelayedPayoutTxSignature().toByteArray());
|
||||
proto.getDelayedPayoutTxBuyerSignature().toByteArray(),
|
||||
proto.getDepositTx().toByteArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DelayedPayoutTxSignatureResponse{" +
|
||||
"\n senderNodeAddress=" + senderNodeAddress +
|
||||
",\n delayedPayoutTxSignature=" + Utilities.bytesAsHexString(delayedPayoutTxSignature) +
|
||||
",\n delayedPayoutTxBuyerSignature=" + Utilities.bytesAsHexString(delayedPayoutTxBuyerSignature) +
|
||||
",\n depositTx=" + Utilities.bytesAsHexString(depositTx) +
|
||||
"\n} " + super.toString();
|
||||
}
|
||||
}
|
||||
|
@ -34,17 +34,17 @@ import lombok.Value;
|
||||
@Value
|
||||
public final class DepositTxMessage extends TradeMessage implements DirectMessage {
|
||||
private final NodeAddress senderNodeAddress;
|
||||
private final byte[] depositTx;
|
||||
private final byte[] depositTxWithoutWitnesses;
|
||||
|
||||
public DepositTxMessage(String uid,
|
||||
String tradeId,
|
||||
NodeAddress senderNodeAddress,
|
||||
byte[] depositTx) {
|
||||
byte[] depositTxWithoutWitnesses) {
|
||||
this(Version.getP2PMessageVersion(),
|
||||
uid,
|
||||
tradeId,
|
||||
senderNodeAddress,
|
||||
depositTx);
|
||||
depositTxWithoutWitnesses);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -55,10 +55,10 @@ public final class DepositTxMessage extends TradeMessage implements DirectMessag
|
||||
String uid,
|
||||
String tradeId,
|
||||
NodeAddress senderNodeAddress,
|
||||
byte[] depositTx) {
|
||||
byte[] depositTxWithoutWitnesses) {
|
||||
super(messageVersion, tradeId, uid);
|
||||
this.senderNodeAddress = senderNodeAddress;
|
||||
this.depositTx = depositTx;
|
||||
this.depositTxWithoutWitnesses = depositTxWithoutWitnesses;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -68,7 +68,7 @@ public final class DepositTxMessage extends TradeMessage implements DirectMessag
|
||||
.setUid(uid)
|
||||
.setTradeId(tradeId)
|
||||
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
|
||||
.setDepositTx(ByteString.copyFrom(depositTx)))
|
||||
.setDepositTxWithoutWitnesses(ByteString.copyFrom(depositTxWithoutWitnesses)))
|
||||
.build();
|
||||
}
|
||||
|
||||
@ -77,14 +77,14 @@ public final class DepositTxMessage extends TradeMessage implements DirectMessag
|
||||
proto.getUid(),
|
||||
proto.getTradeId(),
|
||||
NodeAddress.fromProto(proto.getSenderNodeAddress()),
|
||||
proto.getDepositTx().toByteArray());
|
||||
proto.getDepositTxWithoutWitnesses().toByteArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DepositTxMessage{" +
|
||||
"\n senderNodeAddress=" + senderNodeAddress +
|
||||
",\n depositTx=" + Utilities.bytesAsHexString(depositTx) +
|
||||
",\n depositTxWithoutWitnesses=" + Utilities.bytesAsHexString(depositTxWithoutWitnesses) +
|
||||
"\n} " + super.toString();
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import bisq.core.trade.messages.PayoutTxPublishedMessage;
|
||||
import bisq.core.trade.protocol.tasks.ApplyFilter;
|
||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerFinalizesDelayedPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDelayedPayoutTxSignatureRequest;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerSendsDelayedPayoutTxSignatureResponse;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerSetupDepositTxListener;
|
||||
@ -103,6 +104,7 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
|
||||
MakerRemovesOpenOffer.class,
|
||||
BuyerVerifiesPreparedDelayedPayoutTx.class,
|
||||
BuyerSignsDelayedPayoutTx.class,
|
||||
BuyerFinalizesDelayedPayoutTx.class,
|
||||
BuyerSendsDelayedPayoutTxSignatureResponse.class)
|
||||
.withTimeout(30))
|
||||
.executeTasks();
|
||||
|
@ -29,6 +29,7 @@ import bisq.core.trade.messages.TradeMessage;
|
||||
import bisq.core.trade.protocol.tasks.ApplyFilter;
|
||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerFinalizesDelayedPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDelayedPayoutTxSignatureRequest;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerSendsDelayedPayoutTxSignatureResponse;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerSetupDepositTxListener;
|
||||
@ -119,6 +120,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
|
||||
BuyerProcessDelayedPayoutTxSignatureRequest.class,
|
||||
BuyerVerifiesPreparedDelayedPayoutTx.class,
|
||||
BuyerSignsDelayedPayoutTx.class,
|
||||
BuyerFinalizesDelayedPayoutTx.class,
|
||||
BuyerSendsDelayedPayoutTxSignatureResponse.class)
|
||||
.withTimeout(30))
|
||||
.executeTasks();
|
||||
|
@ -35,6 +35,7 @@ import bisq.core.trade.protocol.tasks.maker.MakerSetsLockTime;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerSignsDelayedPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerCreatesUnsignedDepositTx;
|
||||
import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerFinalizesDepositTx;
|
||||
import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerProcessDepositTxMessage;
|
||||
@ -103,6 +104,7 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
|
||||
MakerRemovesOpenOffer.class,
|
||||
SellerAsMakerFinalizesDepositTx.class,
|
||||
SellerCreatesDelayedPayoutTx.class,
|
||||
SellerSignsDelayedPayoutTx.class,
|
||||
SellerSendDelayedPayoutTxSignatureRequest.class)
|
||||
.withTimeout(30))
|
||||
.executeTasks();
|
||||
|
@ -30,6 +30,7 @@ import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerSignsDelayedPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerCreatesDepositTxInputs;
|
||||
import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerSignsDepositTx;
|
||||
import bisq.core.trade.protocol.tasks.taker.CreateTakerFeeTx;
|
||||
@ -98,6 +99,7 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
|
||||
TakerPublishFeeTx.class,
|
||||
SellerAsTakerSignsDepositTx.class,
|
||||
SellerCreatesDelayedPayoutTx.class,
|
||||
SellerSignsDelayedPayoutTx.class,
|
||||
SellerSendDelayedPayoutTxSignatureRequest.class)
|
||||
.withTimeout(30))
|
||||
.executeTasks();
|
||||
|
@ -33,7 +33,6 @@ import bisq.core.trade.protocol.tasks.seller.SellerPublishesTradeStatistics;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerSendPayoutTxPublishedMessage;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerSendsDepositTxAndDelayedPayoutTxMessage;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerSignAndFinalizePayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerSignsDelayedPayoutTx;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
||||
@ -77,7 +76,6 @@ public abstract class SellerProtocol extends DisputeProtocol {
|
||||
.with(message)
|
||||
.from(peer))
|
||||
.setup(tasks(SellerProcessDelayedPayoutTxSignatureResponse.class,
|
||||
SellerSignsDelayedPayoutTx.class,
|
||||
SellerFinalizesDelayedPayoutTx.class,
|
||||
SellerSendsDepositTxAndDelayedPayoutTxMessage.class,
|
||||
SellerPublishesDepositTx.class,
|
||||
|
@ -0,0 +1,61 @@
|
||||
package bisq.core.trade.protocol.tasks.buyer;
|
||||
|
||||
import bisq.core.btc.model.AddressEntry;
|
||||
import bisq.core.btc.wallet.BtcWalletService;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
|
||||
import bisq.common.taskrunner.TaskRunner;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import org.bitcoinj.core.Transaction;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
public class BuyerFinalizesDelayedPayoutTx extends TradeTask {
|
||||
public BuyerFinalizesDelayedPayoutTx(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
BtcWalletService btcWalletService = processModel.getBtcWalletService();
|
||||
String id = processModel.getOffer().getId();
|
||||
Transaction preparedDepositTx = btcWalletService.getTxFromSerializedTx(processModel.getPreparedDepositTx());
|
||||
Transaction preparedDelayedPayoutTx = checkNotNull(processModel.getPreparedDelayedPayoutTx());
|
||||
|
||||
byte[] buyerMultiSigPubKey = processModel.getMyMultiSigPubKey();
|
||||
checkArgument(Arrays.equals(buyerMultiSigPubKey,
|
||||
btcWalletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG).getPubKey()),
|
||||
"buyerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id);
|
||||
byte[] sellerMultiSigPubKey = processModel.getTradingPeer().getMultiSigPubKey();
|
||||
|
||||
byte[] buyerSignature = processModel.getDelayedPayoutTxSignature();
|
||||
byte[] sellerSignature = processModel.getTradingPeer().getDelayedPayoutTxSignature();
|
||||
|
||||
Transaction signedDelayedPayoutTx = processModel.getTradeWalletService().finalizeUnconnectedDelayedPayoutTx(
|
||||
preparedDelayedPayoutTx,
|
||||
buyerMultiSigPubKey,
|
||||
sellerMultiSigPubKey,
|
||||
buyerSignature,
|
||||
sellerSignature,
|
||||
preparedDepositTx.getOutput(0).getValue());
|
||||
|
||||
trade.applyDelayedPayoutTxBytes(signedDelayedPayoutTx.bitcoinSerialize());
|
||||
log.info("DelayedPayoutTxBytes = {}", Utilities.bytesAsHexString(trade.getDelayedPayoutTxBytes()));
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
@ -46,6 +46,7 @@ public class BuyerProcessDelayedPayoutTxSignatureRequest extends TradeTask {
|
||||
byte[] delayedPayoutTxAsBytes = checkNotNull(request.getDelayedPayoutTx());
|
||||
Transaction preparedDelayedPayoutTx = processModel.getBtcWalletService().getTxFromSerializedTx(delayedPayoutTxAsBytes);
|
||||
processModel.setPreparedDelayedPayoutTx(preparedDelayedPayoutTx);
|
||||
processModel.getTradingPeer().setDelayedPayoutTxSignature(checkNotNull(request.getDelayedPayoutTxSellerSignature()));
|
||||
|
||||
// When we receive that message the taker has published the taker fee, so we apply it to the trade.
|
||||
// The takerFeeTx was sent in the first message. It should be part of DelayedPayoutTxSignatureRequest
|
||||
|
@ -26,12 +26,16 @@ import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
import bisq.core.util.Validator;
|
||||
|
||||
import bisq.common.taskrunner.TaskRunner;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.wallet.Wallet;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
@ -59,9 +63,11 @@ public class BuyerProcessDepositTxAndDelayedPayoutTxMessage extends TradeTask {
|
||||
|
||||
// To access tx confidence we need to add that tx into our wallet.
|
||||
byte[] delayedPayoutTxBytes = checkNotNull(message.getDelayedPayoutTx());
|
||||
checkArgument(Arrays.equals(delayedPayoutTxBytes, trade.getDelayedPayoutTxBytes()),
|
||||
"mismatch between delayedPayoutTx received from peer and our one." +
|
||||
"\n Expected: " + Utilities.bytesAsHexString(trade.getDelayedPayoutTxBytes()) +
|
||||
"\n Received: " + Utilities.bytesAsHexString(delayedPayoutTxBytes));
|
||||
trade.applyDelayedPayoutTxBytes(delayedPayoutTxBytes);
|
||||
BtcWalletService.printTx("delayedPayoutTx received from peer",
|
||||
checkNotNull(trade.getDelayedPayoutTx()));
|
||||
|
||||
trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
|
||||
|
||||
|
@ -44,10 +44,15 @@ public class BuyerSendsDelayedPayoutTxSignatureResponse extends TradeTask {
|
||||
runInterceptHook();
|
||||
|
||||
byte[] delayedPayoutTxSignature = checkNotNull(processModel.getDelayedPayoutTxSignature());
|
||||
byte[] depositTxBytes = processModel.getDepositTx() != null
|
||||
? processModel.getDepositTx().bitcoinSerialize() // set in BuyerAsTakerSignsDepositTx task
|
||||
: processModel.getPreparedDepositTx(); // set in BuyerAsMakerCreatesAndSignsDepositTx task
|
||||
|
||||
DelayedPayoutTxSignatureResponse message = new DelayedPayoutTxSignatureResponse(UUID.randomUUID().toString(),
|
||||
processModel.getOfferId(),
|
||||
processModel.getMyNodeAddress(),
|
||||
delayedPayoutTxSignature);
|
||||
delayedPayoutTxSignature,
|
||||
depositTxBytes);
|
||||
|
||||
NodeAddress peersNodeAddress = trade.getTradingPeerNodeAddress();
|
||||
log.info("Send {} to peer {}. tradeId={}, uid={}",
|
||||
|
@ -25,6 +25,8 @@ import bisq.common.taskrunner.TaskRunner;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
public class BuyerVerifiesPreparedDelayedPayoutTx extends TradeTask {
|
||||
public BuyerVerifiesPreparedDelayedPayoutTx(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
@ -36,11 +38,22 @@ public class BuyerVerifiesPreparedDelayedPayoutTx extends TradeTask {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
var preparedDelayedPayoutTx = processModel.getPreparedDelayedPayoutTx();
|
||||
TradeDataValidation.validateDelayedPayoutTx(trade,
|
||||
processModel.getPreparedDelayedPayoutTx(),
|
||||
preparedDelayedPayoutTx,
|
||||
processModel.getDaoFacade(),
|
||||
processModel.getBtcWalletService());
|
||||
|
||||
// If the deposit tx is non-malleable, we already know its final ID, so should check that now
|
||||
// before sending any further data to the seller, to provide extra protection for the buyer.
|
||||
if (isDepositTxNonMalleable()) {
|
||||
var preparedDepositTx = processModel.getBtcWalletService().getTxFromSerializedTx(
|
||||
processModel.getPreparedDepositTx());
|
||||
TradeDataValidation.validatePayoutTxInput(preparedDepositTx, checkNotNull(preparedDelayedPayoutTx));
|
||||
} else {
|
||||
log.info("Deposit tx is malleable, so we skip preparedDelayedPayoutTx input validation.");
|
||||
}
|
||||
|
||||
complete();
|
||||
} catch (TradeDataValidation.ValidationException e) {
|
||||
failed(e.getMessage());
|
||||
@ -48,4 +61,12 @@ public class BuyerVerifiesPreparedDelayedPayoutTx extends TradeTask {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isDepositTxNonMalleable() {
|
||||
var buyerInputs = checkNotNull(processModel.getRawTransactionInputs());
|
||||
var sellerInputs = checkNotNull(processModel.getTradingPeer().getRawTransactionInputs());
|
||||
|
||||
return buyerInputs.stream().allMatch(processModel.getTradeWalletService()::isP2WH) &&
|
||||
sellerInputs.stream().allMatch(processModel.getTradeWalletService()::isP2WH);
|
||||
}
|
||||
}
|
||||
|
@ -72,6 +72,8 @@ public class BuyerAsMakerCreatesAndSignsDepositTx extends TradeTask {
|
||||
.add(tradeAmount);
|
||||
|
||||
List<RawTransactionInput> takerRawTransactionInputs = checkNotNull(tradingPeer.getRawTransactionInputs());
|
||||
checkArgument(takerRawTransactionInputs.stream().allMatch(processModel.getTradeWalletService()::isP2WH),
|
||||
"all takerRawTransactionInputs must be P2WH");
|
||||
long takerChangeOutputValue = tradingPeer.getChangeOutputValue();
|
||||
@Nullable String takerChangeAddressString = tradingPeer.getChangeOutputAddress();
|
||||
Address makerAddress = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE).getAddress();
|
||||
|
@ -22,6 +22,8 @@ import bisq.core.trade.protocol.tasks.maker.MakerSendsInputsForDepositTxResponse
|
||||
|
||||
import bisq.common.taskrunner.TaskRunner;
|
||||
|
||||
import org.bitcoinj.core.Transaction;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@ -32,6 +34,9 @@ public class BuyerAsMakerSendsInputsForDepositTxResponse extends MakerSendsInput
|
||||
|
||||
@Override
|
||||
protected byte[] getPreparedDepositTx() {
|
||||
return processModel.getPreparedDepositTx();
|
||||
Transaction preparedDepositTx = processModel.getBtcWalletService().getTxFromSerializedTx(processModel.getPreparedDepositTx());
|
||||
// Remove witnesses from preparedDepositTx, so that the seller can still compute the final
|
||||
// tx id, but cannot publish it before providing the buyer with a signed delayed payout tx.
|
||||
return preparedDepositTx.bitcoinSerialize(false);
|
||||
}
|
||||
}
|
||||
|
@ -41,10 +41,12 @@ public class BuyerAsTakerSendsDepositTxMessage extends TradeTask {
|
||||
try {
|
||||
runInterceptHook();
|
||||
if (processModel.getDepositTx() != null) {
|
||||
// Remove witnesses from the sent depositTx, so that the seller can still compute the final
|
||||
// tx id, but cannot publish it before providing the buyer with a signed delayed payout tx.
|
||||
DepositTxMessage message = new DepositTxMessage(UUID.randomUUID().toString(),
|
||||
processModel.getOfferId(),
|
||||
processModel.getMyNodeAddress(),
|
||||
processModel.getDepositTx().bitcoinSerialize());
|
||||
processModel.getDepositTx().bitcoinSerialize(false));
|
||||
|
||||
NodeAddress peersNodeAddress = trade.getTradingPeerNodeAddress();
|
||||
log.info("Send {} to peer {}. tradeId={}, uid={}",
|
||||
|
@ -20,6 +20,7 @@ package bisq.core.trade.protocol.tasks.seller;
|
||||
import bisq.core.btc.wallet.TradeWalletService;
|
||||
import bisq.core.dao.governance.param.Param;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.TradeDataValidation;
|
||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
|
||||
import bisq.common.taskrunner.TaskRunner;
|
||||
@ -53,6 +54,10 @@ public class SellerCreatesDelayedPayoutTx extends TradeTask {
|
||||
donationAddressString,
|
||||
minerFee,
|
||||
lockTime);
|
||||
TradeDataValidation.validateDelayedPayoutTx(trade,
|
||||
preparedDelayedPayoutTx,
|
||||
processModel.getDaoFacade(),
|
||||
processModel.getBtcWalletService());
|
||||
|
||||
processModel.setPreparedDelayedPayoutTx(preparedDelayedPayoutTx);
|
||||
|
||||
|
@ -42,7 +42,12 @@ public class SellerProcessDelayedPayoutTxSignatureResponse extends TradeTask {
|
||||
checkNotNull(response);
|
||||
checkTradeId(processModel.getOfferId(), response);
|
||||
|
||||
processModel.getTradingPeer().setDelayedPayoutTxSignature(checkNotNull(response.getDelayedPayoutTxSignature()));
|
||||
processModel.getTradingPeer().setDelayedPayoutTxSignature(checkNotNull(response.getDelayedPayoutTxBuyerSignature()));
|
||||
|
||||
processModel.getTradeWalletService().sellerAddsBuyerWitnessesToDepositTx(
|
||||
processModel.getDepositTx(),
|
||||
processModel.getBtcWalletService().getTxFromSerializedTx(response.getDepositTx())
|
||||
);
|
||||
|
||||
// update to the latest peer address of our peer if the message is correct
|
||||
trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
|
||||
|
@ -47,10 +47,13 @@ public class SellerSendDelayedPayoutTxSignatureRequest extends TradeTask {
|
||||
|
||||
Transaction preparedDelayedPayoutTx = checkNotNull(processModel.getPreparedDelayedPayoutTx(),
|
||||
"processModel.getPreparedDelayedPayoutTx() must not be null");
|
||||
byte[] delayedPayoutTxSignature = checkNotNull(processModel.getDelayedPayoutTxSignature(),
|
||||
"processModel.getDelayedPayoutTxSignature() must not be null");
|
||||
DelayedPayoutTxSignatureRequest message = new DelayedPayoutTxSignatureRequest(UUID.randomUUID().toString(),
|
||||
processModel.getOfferId(),
|
||||
processModel.getMyNodeAddress(),
|
||||
preparedDelayedPayoutTx.bitcoinSerialize());
|
||||
preparedDelayedPayoutTx.bitcoinSerialize(),
|
||||
delayedPayoutTxSignature);
|
||||
|
||||
NodeAddress peersNodeAddress = trade.getTradingPeerNodeAddress();
|
||||
log.info("Send {} to peer {}. tradeId={}, uid={}",
|
||||
|
@ -78,6 +78,8 @@ public class SellerAsMakerCreatesUnsignedDepositTx extends TradeTask {
|
||||
.add(offer.getBuyerSecurityDeposit());
|
||||
|
||||
List<RawTransactionInput> takerRawTransactionInputs = checkNotNull(tradingPeer.getRawTransactionInputs());
|
||||
checkArgument(takerRawTransactionInputs.stream().allMatch(processModel.getTradeWalletService()::isP2WH),
|
||||
"all takerRawTransactionInputs must be P2WH");
|
||||
long takerChangeOutputValue = tradingPeer.getChangeOutputValue();
|
||||
String takerChangeAddressString = tradingPeer.getChangeOutputAddress();
|
||||
Address makerAddress = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE).getAddress();
|
||||
|
@ -43,7 +43,7 @@ public class SellerAsMakerProcessDepositTxMessage extends TradeTask {
|
||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||
checkNotNull(message);
|
||||
|
||||
processModel.getTradingPeer().setPreparedDepositTx(checkNotNull(message.getDepositTx()));
|
||||
processModel.getTradingPeer().setPreparedDepositTx(checkNotNull(message.getDepositTxWithoutWitnesses()));
|
||||
trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
|
||||
|
||||
// When we receive that message the taker has published the taker fee, so we apply it to the trade.
|
||||
|
@ -44,6 +44,7 @@ public class SellerAsMakerSendsInputsForDepositTxResponse extends MakerSendsInpu
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
return preparedDepositTx.bitcoinSerialize();
|
||||
// Make sure witnesses are removed as well before sending, to cover the segwit case.
|
||||
return preparedDepositTx.bitcoinSerialize(false);
|
||||
}
|
||||
}
|
||||
|
@ -271,13 +271,15 @@ message DelayedPayoutTxSignatureRequest {
|
||||
string trade_id = 2;
|
||||
NodeAddress sender_node_address = 3;
|
||||
bytes delayed_payout_tx = 4;
|
||||
bytes delayed_payout_tx_seller_signature = 5;
|
||||
}
|
||||
|
||||
message DelayedPayoutTxSignatureResponse {
|
||||
string uid = 1;
|
||||
string trade_id = 2;
|
||||
NodeAddress sender_node_address = 3;
|
||||
bytes delayed_payout_tx_signature = 4;
|
||||
bytes delayed_payout_tx_buyer_signature = 4;
|
||||
bytes deposit_tx = 5;
|
||||
}
|
||||
|
||||
message DepositTxAndDelayedPayoutTxMessage {
|
||||
@ -292,7 +294,7 @@ message DepositTxMessage {
|
||||
string uid = 1;
|
||||
string trade_id = 2;
|
||||
NodeAddress sender_node_address = 3;
|
||||
bytes deposit_tx = 4;
|
||||
bytes deposit_tx_without_witnesses = 4;
|
||||
}
|
||||
|
||||
message PeerPublishedDelayedPayoutTxMessage {
|
||||
|
Loading…
Reference in New Issue
Block a user