Skip to content

Commit

Permalink
Implement ClaimTransactionFactory
Browse files Browse the repository at this point in the history
The factory creates, signs, and finalizes the claim transaction.
  • Loading branch information
alvasw committed Aug 9, 2023
1 parent 5bd4f50 commit beb85cf
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 0 deletions.
129 changes: 129 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,129 @@
/*
* 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 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.script.Script;
import org.bitcoinj.script.ScriptBuilder;

import org.bouncycastle.crypto.params.KeyParameter;

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

public class ClaimTransactionFactory {
private final NetworkParameters params;

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

public Transaction createSignedClaimTransaction(Transaction warningTx,
long nSequence,
Address payoutAddress,
long miningFee,
DeterministicKey myMultiSigKeyPair,
KeyParameter aesKey) throws TransactionVerificationException {
Transaction claimTx = createUnsignedClaimTransaction(warningTx, nSequence, payoutAddress, miningFee);
byte[] mySignature = signClaimTransaction(claimTx, warningTx, myMultiSigKeyPair, aesKey);
return finalizeClaimTransaction(warningTx, claimTx, mySignature);
}

private Transaction createUnsignedClaimTransaction(Transaction warningTx,
long nSequence,
Address payoutAddress,
long miningFee)
throws AddressFormatException, TransactionVerificationException {

Transaction claimTx = new Transaction(params);

TransactionOutput warningTxOutput = warningTx.getOutput(0);
claimTx.addInput(warningTxOutput);
claimTx.getInput(0).setSequenceNumber(nSequence);

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

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

private byte[] signClaimTransaction(Transaction claimTx,
Transaction warningTx,
DeterministicKey myMultiSigKeyPair,
KeyParameter aesKey)
throws AddressFormatException, TransactionVerificationException {

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

Sha256Hash sigHash = claimTx.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("claimTx for sig creation", claimTx);
WalletService.verifyTransaction(claimTx);
return mySignature.encodeToDER();
}

private Transaction finalizeClaimTransaction(Transaction warningTx,
Transaction claimTx,
byte[] mySignature)
throws AddressFormatException, TransactionVerificationException {

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

Script redeemScript = createRedeemScript(mySignature);
TransactionWitness witness = TransactionWitness.redeemP2WSH(redeemScript);
input.setWitness(witness);

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

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

private Script createRedeemScript(byte[] mySignature) {
return new ScriptBuilder()
.data(mySignature)
.number(0)
.build();
}
}
14 changes: 14 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 @@ -901,6 +901,20 @@ public Transaction finalizeRedirectionTx(Transaction warningTx,
);
}

///////////////////////////////////////////////////////////////////////////////////////////
// Claim tx
///////////////////////////////////////////////////////////////////////////////////////////

public Transaction createSignedClaimTx(Transaction warningTx,
long nSequence,
Address payoutAddress,
long miningFee,
DeterministicKey myMultiSigKeyPair,
KeyParameter aesKey) throws TransactionVerificationException {
return new ClaimTransactionFactory(params)
.createSignedClaimTransaction(warningTx, nSequence, payoutAddress, miningFee, myMultiSigKeyPair, aesKey);
}

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

0 comments on commit beb85cf

Please sign in to comment.