Skip to content

Commit

Permalink
Implement RedirectionTransactionFactory
Browse files Browse the repository at this point in the history
The factory can create, sign, and finalize the redirection transaction.
  • Loading branch information
alvasw committed Aug 9, 2023
1 parent 4938188 commit 5bd4f50
Show file tree
Hide file tree
Showing 2 changed files with 178 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/*
* 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.common.util.Tuple2;

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.SignatureDecodeException;
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 java.util.List;

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

public class RedirectionTransactionFactory {

private final NetworkParameters params;

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

public Transaction createUnsignedRedirectionTransaction(Transaction warningTx,
List<Tuple2<Long, String>> receivers,
Tuple2<Long, String> feeBumpOutputAmountAndAddress)
throws AddressFormatException, TransactionVerificationException {

TransactionOutput warningTxOutput = warningTx.getOutput(0);

Transaction redirectionTx = new Transaction(params);
redirectionTx.addInput(warningTxOutput);

checkArgument(!receivers.isEmpty(), "receivers must not be empty");
receivers.forEach(receiver -> redirectionTx.addOutput(Coin.valueOf(receiver.first), Address.fromString(params, receiver.second)));

redirectionTx.addOutput(
Coin.valueOf(feeBumpOutputAmountAndAddress.first),
Address.fromString(params, feeBumpOutputAmountAndAddress.second)
);

WalletService.printTx("Unsigned redirectionTx", redirectionTx);
WalletService.verifyTransaction(redirectionTx);

return redirectionTx;
}

public byte[] signRedirectionTransaction(Transaction redirectionTx,
Transaction warningTx,
DeterministicKey myMultiSigKeyPair,
KeyParameter aesKey)
throws AddressFormatException, TransactionVerificationException {

TransactionOutput warningTxPayoutOutput = warningTx.getOutput(0);
Script redeemScript = warningTxPayoutOutput.getScriptPubKey();
Coin redirectionTxInputValue = warningTxPayoutOutput.getValue();

Sha256Hash sigHash = redirectionTx.hashForWitnessSignature(0, redeemScript,
redirectionTxInputValue, Transaction.SigHash.ALL, false);

checkNotNull(myMultiSigKeyPair, "myMultiSigKeyPair must not be null");
if (myMultiSigKeyPair.isEncrypted()) {
checkNotNull(aesKey);
}

ECKey.ECDSASignature mySignature = myMultiSigKeyPair.sign(sigHash, aesKey).toCanonicalised();
WalletService.printTx("redirectionTx for sig creation", redirectionTx);
WalletService.verifyTransaction(redirectionTx);
return mySignature.encodeToDER();
}

public Transaction finalizeRedirectionTransaction(Transaction warningTx,
Transaction redirectionTx,
byte[] buyerSignature,
byte[] sellerSignature,
Coin inputValue)
throws AddressFormatException, TransactionVerificationException {

TransactionInput input = redirectionTx.getInput(0);
input.setScriptSig(ScriptBuilder.createEmpty());

Script redeemScript = createRedeemScript(buyerSignature, sellerSignature);
TransactionWitness witness = TransactionWitness.redeemP2WSH(redeemScript);
input.setWitness(witness);

WalletService.printTx("finalizeRedirectionTransaction", redirectionTx);
WalletService.verifyTransaction(redirectionTx);

Script scriptPubKey = warningTx.getOutput(0).getScriptPubKey();
input.getScriptSig().correctlySpends(redirectionTx, 0, witness, inputValue, scriptPubKey, Script.ALL_VERIFY_FLAGS);
return redirectionTx;
}

private Script createRedeemScript(byte[] buyerSignature, byte[] sellerSignature) {
return new ScriptBuilder()
.number(0)
.data(buyerSignature)
.data(sellerSignature)
.number(1)
.build();
}
}
45 changes: 45 additions & 0 deletions core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ public class TradeWalletService {
private final NetworkParameters params;

private final WarningTransactionFactory warningTransactionFactory;
private final RedirectionTransactionFactory redirectionTransactionFactory;

@Nullable
private Wallet wallet;
Expand All @@ -102,6 +103,7 @@ public TradeWalletService(WalletsSetup walletsSetup, Preferences preferences) {
this.preferences = preferences;
this.params = Config.baseCurrencyNetworkParameters();
this.warningTransactionFactory = new WarningTransactionFactory(params);
this.redirectionTransactionFactory = new RedirectionTransactionFactory(params);
walletsSetup.addSetupCompletedHandler(() -> {
walletConfig = walletsSetup.getWalletConfig();
wallet = walletsSetup.getBtcWallet();
Expand Down Expand Up @@ -856,6 +858,49 @@ public Transaction finalizeWarningTx(Transaction warningTx,
);
}

///////////////////////////////////////////////////////////////////////////////////////////
// Redirection tx
///////////////////////////////////////////////////////////////////////////////////////////

public Transaction createUnsignedRedirectionTx(Transaction warningTx,
List<Tuple2<Long, String>> receivers,
Tuple2<Long, String> feeBumpOutputAmountAndAddress)
throws AddressFormatException, TransactionVerificationException {
return redirectionTransactionFactory.createUnsignedRedirectionTransaction(
warningTx,
receivers,
feeBumpOutputAmountAndAddress
);
}

public byte[] signRedirectionTx(Transaction redirectionTx,
Transaction warningTx,
DeterministicKey myMultiSigKeyPair,
KeyParameter aesKey)
throws AddressFormatException, TransactionVerificationException {
return redirectionTransactionFactory.signRedirectionTransaction(
redirectionTx,
warningTx,
myMultiSigKeyPair,
aesKey
);
}

public Transaction finalizeRedirectionTx(Transaction warningTx,
Transaction redirectionTx,
byte[] buyerSignature,
byte[] sellerSignature,
Coin inputValue)
throws AddressFormatException, TransactionVerificationException, SignatureDecodeException {
return redirectionTransactionFactory.finalizeRedirectionTransaction(
warningTx,
redirectionTx,
buyerSignature,
sellerSignature,
inputValue
);
}

///////////////////////////////////////////////////////////////////////////////////////////
// Standard payout tx
///////////////////////////////////////////////////////////////////////////////////////////
Expand Down

0 comments on commit 5bd4f50

Please sign in to comment.