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

Use 2of2 multisig deposit tx version for manual payout #3694

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
146 changes: 8 additions & 138 deletions core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ public KeyParameter getAesKey() {
* @param reservedForTradeAddress From where we want to spend the transaction fee. Used also as change reservedForTradeAddress.
* @param useSavingsWallet
* @param tradingFee The amount of the trading fee.
* @param feeReceiverAddresses The reservedForTradeAddress of the receiver of the trading fee (arbitrator). @return The broadcasted transaction
* @param feeReceiverAddresses The reservedForTradeAddress of the receiver of the trading fee. @return The broadcasted transaction
* @throws InsufficientMoneyException
* @throws AddressFormatException
*/
Expand Down Expand Up @@ -809,7 +809,7 @@ public Transaction sellerSignsAndFinalizesPayoutTx(Transaction depositTx,
TransactionSignature buyerTxSig = new TransactionSignature(ECKey.ECDSASignature.decodeFromDER(buyerSignature),
Transaction.SigHash.ALL, false);
TransactionSignature sellerTxSig = new TransactionSignature(sellerSignature, Transaction.SigHash.ALL, false);
// Take care of order of signatures. Need to be reversed here. See comment below at getMultiSigRedeemScript (arbitrator, seller, buyer)
// Take care of order of signatures. Need to be reversed here. See comment below at getMultiSigRedeemScript (seller, buyer)
Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(ImmutableList.of(sellerTxSig, buyerTxSig),
redeemScript);
TransactionInput input = payoutTx.getInput(0);
Expand Down Expand Up @@ -872,7 +872,7 @@ public Transaction finalizeMediatedPayoutTx(Transaction depositTx,
Transaction.SigHash.ALL, false);
TransactionSignature sellerTxSig = new TransactionSignature(ECKey.ECDSASignature.decodeFromDER(sellerSignature),
Transaction.SigHash.ALL, false);
// Take care of order of signatures. Need to be reversed here. See comment below at getMultiSigRedeemScript (arbitrator, seller, buyer)
// Take care of order of signatures. Need to be reversed here. See comment below at getMultiSigRedeemScript (seller, buyer)
Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(ImmutableList.of(sellerTxSig, buyerTxSig), redeemScript);
TransactionInput input = payoutTx.getInput(0);
input.setScriptSig(inputScript);
Expand All @@ -890,56 +890,8 @@ public Transaction finalizeMediatedPayoutTx(Transaction depositTx,
// Arbitrated payoutTx
///////////////////////////////////////////////////////////////////////////////////////////

/**
* That arbitrator signs the payout transaction
*
* @param depositTxSerialized Serialized deposit tx
* @param buyerPayoutAmount The payout amount of the buyer.
* @param sellerPayoutAmount The payout amount of the seller.
* @param buyerAddressString The address of the buyer.
* @param sellerAddressString The address of the seller.
* @param arbitratorKeyPair The keypair of the arbitrator.
* @param buyerPubKey The public key of the buyer.
* @param sellerPubKey The public key of the seller.
* @param arbitratorPubKey The public key of the arbitrator.
* @return DER encoded canonical signature of arbitrator
* @throws AddressFormatException
* @throws TransactionVerificationException
*/
public byte[] arbitratorSignsDisputedPayoutTx(byte[] depositTxSerialized,
Coin buyerPayoutAmount,
Coin sellerPayoutAmount,
String buyerAddressString,
String sellerAddressString,
DeterministicKey arbitratorKeyPair,
byte[] buyerPubKey,
byte[] sellerPubKey,
byte[] arbitratorPubKey)
throws AddressFormatException, TransactionVerificationException {
Transaction depositTx = new Transaction(params, depositTxSerialized);
// Our MS is index 0
TransactionOutput p2SHMultiSigOutput = depositTx.getOutput(0);
Transaction preparedPayoutTx = new Transaction(params);
preparedPayoutTx.addInput(p2SHMultiSigOutput);
if (buyerPayoutAmount.isGreaterThan(Coin.ZERO)) {
preparedPayoutTx.addOutput(buyerPayoutAmount, Address.fromBase58(params, buyerAddressString));
}
if (sellerPayoutAmount.isGreaterThan(Coin.ZERO)) {
preparedPayoutTx.addOutput(sellerPayoutAmount, Address.fromBase58(params, sellerAddressString));
}

// take care of sorting!
Script redeemScript = get2of3MultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
Sha256Hash sigHash = preparedPayoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
checkNotNull(arbitratorKeyPair, "arbitratorKeyPair must not be null");
if (arbitratorKeyPair.isEncrypted()) {
checkNotNull(aesKey);
}
ECKey.ECDSASignature arbitratorSignature = arbitratorKeyPair.sign(sigHash, aesKey).toCanonicalised();
WalletService.verifyTransaction(preparedPayoutTx);
WalletService.printTx("preparedPayoutTx", preparedPayoutTx);
return arbitratorSignature.encodeToDER();
}
// TODO: Once we have removed legacy arbitrator from dispute domain we can remove that method as well.
// Atm it is still used by ArbitrationManager.

/**
* A trader who got the signed tx from the arbitrator finalizes the payout tx
Expand Down Expand Up @@ -1011,87 +963,6 @@ public Transaction traderSignAndFinalizeDisputedPayoutTx(byte[] depositTxSeriali
// Emergency payoutTx
///////////////////////////////////////////////////////////////////////////////////////////


// Emergency payout tool. Used only in cased when the payout from the arbitrator does not work because some data
// in the trade/dispute are messed up.
// We keep here arbitratorPayoutAmount just in case (requires cooperation from peer anyway)
public Transaction emergencySignAndPublishPayoutTxFrom2of3MultiSig(String depositTxHex,
Coin buyerPayoutAmount,
Coin sellerPayoutAmount,
Coin arbitratorPayoutAmount,
Coin txFee,
String buyerAddressString,
String sellerAddressString,
String arbitratorAddressString,
@Nullable String buyerPrivateKeyAsHex,
@Nullable String sellerPrivateKeyAsHex,
String arbitratorPrivateKeyAsHex,
String buyerPubKeyAsHex,
String sellerPubKeyAsHex,
String arbitratorPubKeyAsHex,
TxBroadcaster.Callback callback)
throws AddressFormatException, TransactionVerificationException, WalletException {
checkNotNull((buyerPrivateKeyAsHex != null || sellerPrivateKeyAsHex != null),
"either buyerPrivateKeyAsHex or sellerPrivateKeyAsHex must not be null");

byte[] buyerPubKey = ECKey.fromPublicOnly(Utils.HEX.decode(buyerPubKeyAsHex)).getPubKey();
byte[] sellerPubKey = ECKey.fromPublicOnly(Utils.HEX.decode(sellerPubKeyAsHex)).getPubKey();
byte[] arbitratorPubKey = ECKey.fromPublicOnly(Utils.HEX.decode(arbitratorPubKeyAsHex)).getPubKey();

Script p2SHMultiSigOutputScript = get2of3MultiSigOutputScript(buyerPubKey, sellerPubKey, arbitratorPubKey);

Coin msOutput = buyerPayoutAmount.add(sellerPayoutAmount).add(arbitratorPayoutAmount).add(txFee);
TransactionOutput p2SHMultiSigOutput = new TransactionOutput(params, null, msOutput, p2SHMultiSigOutputScript.getProgram());
Transaction depositTx = new Transaction(params);
depositTx.addOutput(p2SHMultiSigOutput);

Transaction payoutTx = new Transaction(params);
Sha256Hash spendTxHash = Sha256Hash.wrap(depositTxHex);
payoutTx.addInput(new TransactionInput(params, depositTx, p2SHMultiSigOutputScript.getProgram(), new TransactionOutPoint(params, 0, spendTxHash), msOutput));

if (buyerPayoutAmount.isGreaterThan(Coin.ZERO)) {
payoutTx.addOutput(buyerPayoutAmount, Address.fromBase58(params, buyerAddressString));
}
if (sellerPayoutAmount.isGreaterThan(Coin.ZERO)) {
payoutTx.addOutput(sellerPayoutAmount, Address.fromBase58(params, sellerAddressString));
}
if (arbitratorPayoutAmount.isGreaterThan(Coin.ZERO)) {
payoutTx.addOutput(arbitratorPayoutAmount, Address.fromBase58(params, arbitratorAddressString));
}

// take care of sorting!
Script redeemScript = get2of3MultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
Sha256Hash sigHash = payoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);

ECKey.ECDSASignature tradersSignature;
if (buyerPrivateKeyAsHex != null && !buyerPrivateKeyAsHex.isEmpty()) {
final ECKey buyerPrivateKey = ECKey.fromPrivate(Utils.HEX.decode(buyerPrivateKeyAsHex));
checkNotNull(buyerPrivateKey, "buyerPrivateKey must not be null");
tradersSignature = buyerPrivateKey.sign(sigHash, aesKey).toCanonicalised();
} else {
checkNotNull(sellerPrivateKeyAsHex, "sellerPrivateKeyAsHex must not be null");
final ECKey sellerPrivateKey = ECKey.fromPrivate(Utils.HEX.decode(sellerPrivateKeyAsHex));
checkNotNull(sellerPrivateKey, "sellerPrivateKey must not be null");
tradersSignature = sellerPrivateKey.sign(sigHash, aesKey).toCanonicalised();
}
ECKey key = ECKey.fromPrivate(Utils.HEX.decode(arbitratorPrivateKeyAsHex));
checkNotNull(key, "key must not be null");
ECKey.ECDSASignature arbitratorSignature = key.sign(sigHash, aesKey).toCanonicalised();
TransactionSignature tradersTxSig = new TransactionSignature(tradersSignature, Transaction.SigHash.ALL, false);
TransactionSignature arbitratorTxSig = new TransactionSignature(arbitratorSignature, Transaction.SigHash.ALL, false);
// Take care of order of signatures. See comment below at getMultiSigRedeemScript (sort order needed here: arbitrator, seller, buyer)
Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(ImmutableList.of(arbitratorTxSig, tradersTxSig), redeemScript);

TransactionInput input = payoutTx.getInput(0);
input.setScriptSig(inputScript);
WalletService.printTx("payoutTx", payoutTx);
WalletService.verifyTransaction(payoutTx);
WalletService.checkWalletConsistency(wallet);
broadcastTx(payoutTx, callback, 20);
return payoutTx;
}

//todo add window tool for usage
public Transaction emergencySignAndPublishPayoutTxFrom2of2MultiSig(String depositTxHex,
Coin buyerPayoutAmount,
Coin sellerPayoutAmount,
Expand Down Expand Up @@ -1223,6 +1094,9 @@ rawTransactionInput.index, new Transaction(params, rawTransactionInput.parentTra
}


// 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.

// Don't use ScriptBuilder.createRedeemScript and ScriptBuilder.createP2SHOutputScript as they use a sorting
// (Collections.sort(pubKeys, ECKey.PUBKEY_COMPARATOR);) which can lead to a non-matching list of signatures with pubKeys and the executeMultiSig does
// not iterate all possible combinations of sig/pubKeys leading to a verification fault. That nasty bug happens just randomly as the list after sorting
Expand All @@ -1249,10 +1123,6 @@ private Script get2of2MultiSigRedeemScript(byte[] buyerPubKey, byte[] sellerPubK
return ScriptBuilder.createMultiSigOutputScript(2, keys);
}

private Script get2of3MultiSigOutputScript(byte[] buyerPubKey, byte[] sellerPubKey, byte[] arbitratorPubKey) {
return ScriptBuilder.createP2SHOutputScript(get2of3MultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey));
}

private Script get2of2MultiSigOutputScript(byte[] buyerPubKey, byte[] sellerPubKey) {
return ScriptBuilder.createP2SHOutputScript(get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@

import static bisq.desktop.util.FormBuilder.addInputTextField;

// We dont translate here as it is for dev only purpose
// We don't translate here as it is for dev only purpose
public class ManualPayoutTxWindow extends Overlay<ManualPayoutTxWindow> {
private static final Logger log = LoggerFactory.getLogger(ManualPayoutTxWindow.class);
private final TradeWalletService tradeWalletService;
Expand Down Expand Up @@ -105,34 +105,28 @@ private void addContent() {

InputTextField buyerPayoutAmount = addInputTextField(gridPane, ++rowIndex, "buyerPayoutAmount");
InputTextField sellerPayoutAmount = addInputTextField(gridPane, ++rowIndex, "sellerPayoutAmount");
InputTextField arbitratorPayoutAmount = addInputTextField(gridPane, ++rowIndex, "arbitratorPayoutAmount");
InputTextField txFee = addInputTextField(gridPane, ++rowIndex, "Tx fee");

InputTextField buyerAddressString = addInputTextField(gridPane, ++rowIndex, "buyerAddressString");
InputTextField sellerAddressString = addInputTextField(gridPane, ++rowIndex, "sellerAddressString");
InputTextField arbitratorAddressString = addInputTextField(gridPane, ++rowIndex, "arbitratorAddressString");

InputTextField buyerPrivateKeyAsHex = addInputTextField(gridPane, ++rowIndex, "buyerPrivateKeyAsHex");
InputTextField sellerPrivateKeyAsHex = addInputTextField(gridPane, ++rowIndex, "sellerPrivateKeyAsHex");
InputTextField arbitratorPrivateKeyAsHex = addInputTextField(gridPane, ++rowIndex, "arbitratorPrivateKeyAsHex");

InputTextField buyerPubKeyAsHex = addInputTextField(gridPane, ++rowIndex, "buyerPubKeyAsHex");
InputTextField sellerPubKeyAsHex = addInputTextField(gridPane, ++rowIndex, "sellerPubKeyAsHex");
InputTextField arbitratorPubKeyAsHex = addInputTextField(gridPane, ++rowIndex, "arbitratorPubKeyAsHex");

// Notes:
// Open with alt+g and enable DEV mode
// Open with alt+g
// Priv key is only visible if pw protection is removed (wallet details data (alt+j))
// Take missing buyerPubKeyAsHex and sellerPubKeyAsHex from contract data!
// Lookup sellerPrivateKeyAsHex associated with sellerPubKeyAsHex (or buyers) in wallet details data
// sellerPubKeys/buyerPubKeys are auto generated if used the fields below
// Never set the priv arbitr. key here!

depositTxHex.setText("");

buyerPayoutAmount.setText("");
sellerPayoutAmount.setText("");
arbitratorPayoutAmount.setText("0");

buyerAddressString.setText("");
buyerPubKeyAsHex.setText("");
Expand All @@ -142,9 +136,6 @@ private void addContent() {
sellerPubKeyAsHex.setText("");
sellerPrivateKeyAsHex.setText("");

arbitratorAddressString.setText("");
arbitratorPubKeyAsHex.setText("");

actionButtonText("Sign and publish transaction");

TxBroadcaster.Callback callback = new TxBroadcaster.Callback() {
Expand All @@ -166,20 +157,16 @@ public void onFailure(TxBroadcastException exception) {
onAction(() -> {
if (GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, walletsSetup)) {
try {
tradeWalletService.emergencySignAndPublishPayoutTxFrom2of3MultiSig(depositTxHex.getText(),
tradeWalletService.emergencySignAndPublishPayoutTxFrom2of2MultiSig(depositTxHex.getText(),
Coin.parseCoin(buyerPayoutAmount.getText()),
Coin.parseCoin(sellerPayoutAmount.getText()),
Coin.parseCoin(arbitratorPayoutAmount.getText()),
Coin.parseCoin(txFee.getText()),
buyerAddressString.getText(),
sellerAddressString.getText(),
arbitratorAddressString.getText(),
buyerPrivateKeyAsHex.getText(),
sellerPrivateKeyAsHex.getText(),
arbitratorPrivateKeyAsHex.getText(),
buyerPubKeyAsHex.getText(),
sellerPubKeyAsHex.getText(),
arbitratorPubKeyAsHex.getText(),
callback);
} catch (AddressFormatException | WalletException | TransactionVerificationException e) {
log.error(e.toString());
Expand Down