use of bisq.core.btc.data.RawTransactionInput in project bisq-core by bisq-network.
the class SellerAsTakerSignAndPublishDepositTx method run.
@Override
protected void run() {
try {
runInterceptHook();
log.debug("\n\n------------------------------------------------------------\n" + "Contract as json\n" + trade.getContractAsJson() + "\n------------------------------------------------------------\n");
byte[] contractHash = Hash.getSha256Hash(trade.getContractAsJson());
trade.setContractHash(contractHash);
List<RawTransactionInput> sellerInputs = checkNotNull(processModel.getRawTransactionInputs(), "sellerInputs must not be null");
BtcWalletService walletService = processModel.getBtcWalletService();
String id = processModel.getOffer().getId();
Optional<AddressEntry> addressEntryOptional = walletService.getAddressEntry(id, AddressEntry.Context.MULTI_SIG);
checkArgument(addressEntryOptional.isPresent(), "addressEntryOptional must be present");
AddressEntry sellerMultiSigAddressEntry = addressEntryOptional.get();
byte[] sellerMultiSigPubKey = processModel.getMyMultiSigPubKey();
checkArgument(Arrays.equals(sellerMultiSigPubKey, sellerMultiSigAddressEntry.getPubKey()), "sellerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id);
Coin sellerInput = Coin.valueOf(sellerInputs.stream().mapToLong(input -> input.value).sum());
sellerMultiSigAddressEntry.setCoinLockedInMultiSig(sellerInput.subtract(trade.getTxFee().multiply(2)));
walletService.saveAddressEntryList();
TradingPeer tradingPeer = processModel.getTradingPeer();
Transaction depositTx = processModel.getTradeWalletService().takerSignsAndPublishesDepositTx(true, contractHash, processModel.getPreparedDepositTx(), tradingPeer.getRawTransactionInputs(), sellerInputs, tradingPeer.getMultiSigPubKey(), sellerMultiSigPubKey, trade.getArbitratorBtcPubKey(), new FutureCallback<Transaction>() {
@Override
public void onSuccess(Transaction transaction) {
if (!completed) {
log.trace("takerSignAndPublishTx succeeded " + transaction);
trade.setState(Trade.State.TAKER_PUBLISHED_DEPOSIT_TX);
walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE);
complete();
} else {
log.warn("We got the onSuccess callback called after the timeout has been triggered a complete().");
}
}
@Override
public void onFailure(@NotNull Throwable t) {
if (!completed) {
failed(t);
} else {
log.warn("We got the onFailure callback called after the timeout has been triggered a complete().");
}
}
});
// We set the deposit tx in case we get the onFailure called.
trade.setDepositTx(depositTx);
} catch (Throwable t) {
final Contract contract = trade.getContract();
if (contract != null)
contract.printDiff(processModel.getTradingPeer().getContractAsJson());
failed(t);
}
}
use of bisq.core.btc.data.RawTransactionInput in project bisq-core by bisq-network.
the class BuyerAsMakerCreatesAndSignsDepositTx method run.
@Override
protected void run() {
try {
runInterceptHook();
checkNotNull(trade.getTradeAmount(), "trade.getTradeAmount() must not be null");
BtcWalletService walletService = processModel.getBtcWalletService();
String id = processModel.getOffer().getId();
TradingPeer tradingPeer = processModel.getTradingPeer();
final Offer offer = trade.getOffer();
// params
final boolean makerIsBuyer = true;
final byte[] contractHash = Hash.getSha256Hash(trade.getContractAsJson());
trade.setContractHash(contractHash);
log.debug("\n\n------------------------------------------------------------\n" + "Contract as json\n" + trade.getContractAsJson() + "\n------------------------------------------------------------\n");
final Coin makerInputAmount = offer.getBuyerSecurityDeposit();
Optional<AddressEntry> addressEntryOptional = walletService.getAddressEntry(id, AddressEntry.Context.MULTI_SIG);
checkArgument(addressEntryOptional.isPresent(), "addressEntryOptional must be present");
AddressEntry makerMultiSigAddressEntry = addressEntryOptional.get();
makerMultiSigAddressEntry.setCoinLockedInMultiSig(makerInputAmount);
walletService.saveAddressEntryList();
final Coin msOutputAmount = makerInputAmount.add(trade.getTxFee()).add(offer.getSellerSecurityDeposit()).add(trade.getTradeAmount());
final List<RawTransactionInput> takerRawTransactionInputs = tradingPeer.getRawTransactionInputs();
final long takerChangeOutputValue = tradingPeer.getChangeOutputValue();
final String takerChangeAddressString = tradingPeer.getChangeOutputAddress();
final Address makerAddress = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE).getAddress();
final Address makerChangeAddress = walletService.getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress();
final byte[] buyerPubKey = processModel.getMyMultiSigPubKey();
checkArgument(Arrays.equals(buyerPubKey, makerMultiSigAddressEntry.getPubKey()), "buyerPubKey from AddressEntry must match the one from the trade data. trade id =" + id);
final byte[] sellerPubKey = tradingPeer.getMultiSigPubKey();
final byte[] arbitratorBtcPubKey = trade.getArbitratorBtcPubKey();
PreparedDepositTxAndMakerInputs result = processModel.getTradeWalletService().makerCreatesAndSignsDepositTx(makerIsBuyer, contractHash, makerInputAmount, msOutputAmount, takerRawTransactionInputs, takerChangeOutputValue, takerChangeAddressString, makerAddress, makerChangeAddress, buyerPubKey, sellerPubKey, arbitratorBtcPubKey);
processModel.setPreparedDepositTx(result.depositTransaction);
processModel.setRawTransactionInputs(result.rawMakerInputs);
complete();
} catch (Throwable t) {
failed(t);
}
}
use of bisq.core.btc.data.RawTransactionInput in project bisq-core by bisq-network.
the class TradeWalletService method takerCreatesDepositsTxInputs.
// /////////////////////////////////////////////////////////////////////////////////////////
// Trade
// /////////////////////////////////////////////////////////////////////////////////////////
// We construct the deposit transaction in the way that the buyer is always the first entry (inputs, outputs, MS keys) and then the seller.
// In the creation of the deposit tx the taker/maker roles are the determining roles instead of buyer/seller.
// In the payout tx is is the buyer/seller role. We keep the buyer/seller ordering over all transactions to not get confusion with ordering,
// which is important to follow correctly specially for the order of the MS keys.
/**
* The taker creates a dummy transaction to get the input(s) and optional change output for the amount and the takersAddress for that trade.
* That will be used to send to the maker for creating the deposit transaction.
*
* @param inputAmount Amount of takers input
* @param txFee Mining fee
* @param takersAddress Address of taker
* @return A data container holding the inputs, the output value and address
* @throws TransactionVerificationException
* @throws WalletException
*/
public InputsAndChangeOutput takerCreatesDepositsTxInputs(Coin inputAmount, Coin txFee, Address takersAddress, Address takersChangeAddress) throws TransactionVerificationException, WalletException {
log.debug("takerCreatesDepositsTxInputs called");
log.debug("inputAmount " + inputAmount.toFriendlyString());
log.debug("txFee " + txFee.toFriendlyString());
log.debug("takersAddress " + takersAddress.toString());
// We add the mining fee 2 times to the deposit tx:
// 1. Will be spent when publishing the deposit tx (paid by buyer)
// 2. Will be added to the MS amount, so when publishing the payout tx the fee is already there and the outputs are not changed by fee reduction
// The fee for the payout will be paid by the seller.
/*
The tx we create has that structure:
IN[0] any input > inputAmount (including tx fee) (unsigned)
IN[1...n] optional inputs supported, but normally there is just 1 input (unsigned)
OUT[0] dummyOutputAmount (inputAmount - tx fee)
OUT[1] Optional Change = inputAmount - dummyOutputAmount - tx fee
We are only interested in the inputs and the optional change output.
*/
// inputAmount includes the tx fee. So we subtract the fee to get the dummyOutputAmount.
Coin dummyOutputAmount = inputAmount.subtract(txFee);
Transaction dummyTX = new Transaction(params);
// The output is just used to get the right inputs and change outputs, so we use an anonymous ECKey, as it will never be used for anything.
// We don't care about fee calculation differences between the real tx and that dummy tx as we use a static tx fee.
TransactionOutput dummyOutput = new TransactionOutput(params, dummyTX, dummyOutputAmount, new ECKey().toAddress(params));
dummyTX.addOutput(dummyOutput);
// Find the needed inputs to pay the output, optionally add 1 change output.
// Normally only 1 input and no change output is used, but we support multiple inputs and 1 change output.
// Our spending transaction output is from the create offer fee payment.
addAvailableInputsAndChangeOutputs(dummyTX, takersAddress, takersChangeAddress, txFee);
// The completeTx() call signs the input, but we don't want to pass over signed tx inputs so we remove the signature
WalletService.removeSignatures(dummyTX);
WalletService.verifyTransaction(dummyTX);
// WalletService.printTx("dummyTX", dummyTX);
List<RawTransactionInput> rawTransactionInputList = dummyTX.getInputs().stream().map(e -> {
checkNotNull(e.getConnectedOutput(), "e.getConnectedOutput() must not be null");
checkNotNull(e.getConnectedOutput().getParentTransaction(), "e.getConnectedOutput().getParentTransaction() must not be null");
checkNotNull(e.getValue(), "e.getValue() must not be null");
return getRawInputFromTransactionInput(e);
}).collect(Collectors.toList());
// We don't support more then 1 change outputs, so there are max. 2 outputs
checkArgument(dummyTX.getOutputs().size() < 3);
// Only interested in optional change output, the dummy output at index 0 is ignored (that's why we use index 1)
TransactionOutput changeOutput = dummyTX.getOutputs().size() == 2 ? dummyTX.getOutputs().get(1) : null;
long changeOutputValue = 0L;
String changeOutputAddress = null;
if (changeOutput != null) {
changeOutputValue = changeOutput.getValue().getValue();
Address addressFromP2PKHScript = changeOutput.getAddressFromP2PKHScript(params);
checkNotNull(addressFromP2PKHScript, "changeOutput.getAddressFromP2PKHScript(params) must not be null");
changeOutputAddress = addressFromP2PKHScript.toString();
}
return new InputsAndChangeOutput(new ArrayList<>(rawTransactionInputList), changeOutputValue, changeOutputAddress);
}
use of bisq.core.btc.data.RawTransactionInput in project bisq-core by bisq-network.
the class TradeWalletService method makerCreatesAndSignsDepositTx.
/**
* The maker creates the deposit transaction using the takers input(s) and optional output and signs his input(s).
*
* @param makerIsBuyer The flag indicating if we are in the maker as buyer role or the opposite.
* @param contractHash The hash of the contract to be added to the OP_RETURN output.
* @param makerInputAmount The input amount of the maker.
* @param msOutputAmount The output amount to our MS output.
* @param takerRawTransactionInputs Raw data for the connected outputs for all inputs of the taker (normally 1 input)
* @param takerChangeOutputValue Optional taker change output value
* @param takerChangeAddressString Optional taker change address
* @param makerAddress The maker's address.
* @param makerChangeAddress The maker's change address.
* @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 A data container holding the serialized transaction and the maker raw inputs
* @throws SigningException
* @throws TransactionVerificationException
* @throws WalletException
*/
public PreparedDepositTxAndMakerInputs makerCreatesAndSignsDepositTx(boolean makerIsBuyer, byte[] contractHash, Coin makerInputAmount, Coin msOutputAmount, List<RawTransactionInput> takerRawTransactionInputs, long takerChangeOutputValue, @Nullable String takerChangeAddressString, Address makerAddress, Address makerChangeAddress, byte[] buyerPubKey, byte[] sellerPubKey, byte[] arbitratorPubKey) throws SigningException, TransactionVerificationException, WalletException, AddressFormatException {
log.debug("makerCreatesAndSignsDepositTx called");
log.debug("makerIsBuyer " + makerIsBuyer);
log.debug("makerInputAmount " + makerInputAmount.toFriendlyString());
log.debug("msOutputAmount " + msOutputAmount.toFriendlyString());
log.debug("takerRawInputs " + takerRawTransactionInputs.toString());
log.debug("takerChangeOutputValue " + takerChangeOutputValue);
log.debug("takerChangeAddressString " + takerChangeAddressString);
log.debug("makerAddress " + makerAddress);
log.debug("makerChangeAddress " + makerChangeAddress);
log.debug("buyerPubKey " + ECKey.fromPublicOnly(buyerPubKey).toString());
log.debug("sellerPubKey " + ECKey.fromPublicOnly(sellerPubKey).toString());
log.debug("arbitratorPubKey " + ECKey.fromPublicOnly(arbitratorPubKey).toString());
checkArgument(!takerRawTransactionInputs.isEmpty());
// First we construct a dummy TX to get the inputs and outputs we want to use for the real deposit tx.
// Similar to the way we did in the createTakerDepositTxInputs method.
Transaction dummyTx = new Transaction(params);
TransactionOutput dummyOutput = new TransactionOutput(params, dummyTx, makerInputAmount, new ECKey().toAddress(params));
dummyTx.addOutput(dummyOutput);
addAvailableInputsAndChangeOutputs(dummyTx, makerAddress, makerChangeAddress, Coin.ZERO);
// Normally we have only 1 input but we support multiple inputs if the user has paid in with several transactions.
List<TransactionInput> makerInputs = dummyTx.getInputs();
TransactionOutput makerOutput = null;
// We don't support more then 1 optional change output
checkArgument(dummyTx.getOutputs().size() < 3, "dummyTx.getOutputs().size() >= 3");
// Only save change outputs, the dummy output is ignored (that's why we start with index 1)
if (dummyTx.getOutputs().size() > 1)
makerOutput = dummyTx.getOutput(1);
// Now we construct the real deposit tx
Transaction preparedDepositTx = new Transaction(params);
ArrayList<RawTransactionInput> makerRawTransactionInputs = new ArrayList<>();
if (makerIsBuyer) {
// Add buyer inputs
for (TransactionInput input : makerInputs) {
preparedDepositTx.addInput(input);
makerRawTransactionInputs.add(getRawInputFromTransactionInput(input));
}
// the sellers input is not signed so we attach empty script bytes
for (RawTransactionInput rawTransactionInput : takerRawTransactionInputs) preparedDepositTx.addInput(getTransactionInput(preparedDepositTx, new byte[] {}, rawTransactionInput));
} else {
// the sellers input is not signed so we attach empty script bytes
for (RawTransactionInput rawTransactionInput : takerRawTransactionInputs) preparedDepositTx.addInput(getTransactionInput(preparedDepositTx, new byte[] {}, rawTransactionInput));
// Add seller inputs
for (TransactionInput input : makerInputs) {
preparedDepositTx.addInput(input);
makerRawTransactionInputs.add(getRawInputFromTransactionInput(input));
}
}
// Add MultiSig output
Script p2SHMultiSigOutputScript = getP2SHMultiSigOutputScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
// Tx fee for deposit tx will be paid by buyer.
TransactionOutput p2SHMultiSigOutput = new TransactionOutput(params, preparedDepositTx, msOutputAmount, p2SHMultiSigOutputScript.getProgram());
preparedDepositTx.addOutput(p2SHMultiSigOutput);
// We add the hash ot OP_RETURN with a 0 amount output
TransactionOutput contractHashOutput = new TransactionOutput(params, preparedDepositTx, Coin.ZERO, ScriptBuilder.createOpReturnScript(contractHash).getProgram());
preparedDepositTx.addOutput(contractHashOutput);
TransactionOutput takerTransactionOutput = null;
if (takerChangeOutputValue > 0 && takerChangeAddressString != null)
takerTransactionOutput = new TransactionOutput(params, preparedDepositTx, Coin.valueOf(takerChangeOutputValue), Address.fromBase58(params, takerChangeAddressString));
if (makerIsBuyer) {
// Add optional buyer outputs
if (makerOutput != null)
preparedDepositTx.addOutput(makerOutput);
// Add optional seller outputs
if (takerTransactionOutput != null)
preparedDepositTx.addOutput(takerTransactionOutput);
} else {
// Add optional seller outputs
if (takerTransactionOutput != null)
preparedDepositTx.addOutput(takerTransactionOutput);
// Add optional buyer outputs
if (makerOutput != null)
preparedDepositTx.addOutput(makerOutput);
}
// Sign inputs
int start = makerIsBuyer ? 0 : takerRawTransactionInputs.size();
int end = makerIsBuyer ? makerInputs.size() : preparedDepositTx.getInputs().size();
for (int i = start; i < end; i++) {
TransactionInput input = preparedDepositTx.getInput(i);
signInput(preparedDepositTx, input, i);
WalletService.checkScriptSig(preparedDepositTx, input, i);
}
WalletService.printTx("prepared depositTx", preparedDepositTx);
WalletService.verifyTransaction(preparedDepositTx);
return new PreparedDepositTxAndMakerInputs(makerRawTransactionInputs, preparedDepositTx.bitcoinSerialize());
}
use of bisq.core.btc.data.RawTransactionInput in project bisq-core by bisq-network.
the class TradeWalletService method takerSignsAndPublishesDepositTx.
/**
* The taker signs the deposit transaction he received from the maker and publishes it.
*
* @param takerIsSeller The flag indicating if we are in the taker as seller role or the opposite.
* @param contractHash The hash of the contract to be added to the OP_RETURN output.
* @param makersDepositTxSerialized The prepared deposit transaction signed by the maker.
* @param buyerInputs The connected outputs for all inputs of the buyer.
* @param sellerInputs The connected outputs for all inputs of the seller.
* @param buyerPubKey The public key of the buyer.
* @param sellerPubKey The public key of the seller.
* @param arbitratorPubKey The public key of the arbitrator.
* @param callback Callback when transaction is broadcasted.
* @throws SigningException
* @throws TransactionVerificationException
* @throws WalletException
*/
public Transaction takerSignsAndPublishesDepositTx(boolean takerIsSeller, byte[] contractHash, byte[] makersDepositTxSerialized, List<RawTransactionInput> buyerInputs, List<RawTransactionInput> sellerInputs, byte[] buyerPubKey, byte[] sellerPubKey, byte[] arbitratorPubKey, FutureCallback<Transaction> callback) throws SigningException, TransactionVerificationException, WalletException {
Transaction makersDepositTx = new Transaction(params, makersDepositTxSerialized);
log.debug("signAndPublishDepositTx called");
log.debug("takerIsSeller " + takerIsSeller);
log.debug("makersDepositTx " + makersDepositTx.toString());
log.debug("buyerConnectedOutputsForAllInputs " + buyerInputs.toString());
log.debug("sellerConnectedOutputsForAllInputs " + sellerInputs.toString());
log.debug("buyerPubKey " + ECKey.fromPublicOnly(buyerPubKey).toString());
log.debug("sellerPubKey " + ECKey.fromPublicOnly(sellerPubKey).toString());
log.debug("arbitratorPubKey " + ECKey.fromPublicOnly(arbitratorPubKey).toString());
checkArgument(!buyerInputs.isEmpty());
checkArgument(!sellerInputs.isEmpty());
// Check if maker's Multisig script is identical to the takers
Script p2SHMultiSigOutputScript = getP2SHMultiSigOutputScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
if (!makersDepositTx.getOutput(0).getScriptPubKey().equals(p2SHMultiSigOutputScript))
throw new TransactionVerificationException("Maker's p2SHMultiSigOutputScript does not match to takers p2SHMultiSigOutputScript");
// The outpoints are not available from the serialized makersDepositTx, so we cannot use that tx directly, but we use it to construct a new
// depositTx
Transaction depositTx = new Transaction(params);
if (takerIsSeller) {
// We grab the signature from the makersDepositTx and apply it to the new tx input
for (int i = 0; i < buyerInputs.size(); i++) depositTx.addInput(getTransactionInput(depositTx, getScriptProgram(makersDepositTx, i), buyerInputs.get(i)));
// Add seller inputs
for (RawTransactionInput rawTransactionInput : sellerInputs) depositTx.addInput(getTransactionInput(depositTx, new byte[] {}, rawTransactionInput));
} else {
// Add buyer inputs and apply signature
for (RawTransactionInput rawTransactionInput : buyerInputs) depositTx.addInput(getTransactionInput(depositTx, new byte[] {}, rawTransactionInput));
// We grab the signature from the makersDepositTx and apply it to the new tx input
for (int i = buyerInputs.size(), k = 0; i < makersDepositTx.getInputs().size(); i++, k++) depositTx.addInput(getTransactionInput(depositTx, getScriptProgram(makersDepositTx, i), sellerInputs.get(k)));
}
// Check if OP_RETURN output with contract hash matches the one from the maker
TransactionOutput contractHashOutput = new TransactionOutput(params, makersDepositTx, Coin.ZERO, ScriptBuilder.createOpReturnScript(contractHash).getProgram());
log.debug("contractHashOutput " + contractHashOutput);
TransactionOutput makersContractHashOutput = makersDepositTx.getOutputs().get(1);
log.debug("makersContractHashOutput " + makersContractHashOutput);
if (!makersContractHashOutput.getScriptPubKey().equals(contractHashOutput.getScriptPubKey()))
throw new TransactionVerificationException("Maker's transaction output for the contract hash is not matching takers version.");
// Add all outputs from makersDepositTx to depositTx
makersDepositTx.getOutputs().forEach(depositTx::addOutput);
// WalletService.printTx("makersDepositTx", makersDepositTx);
// Sign inputs
int start = takerIsSeller ? buyerInputs.size() : 0;
int end = takerIsSeller ? depositTx.getInputs().size() : buyerInputs.size();
for (int i = start; i < end; i++) {
TransactionInput input = depositTx.getInput(i);
signInput(depositTx, input, i);
WalletService.checkScriptSig(depositTx, input, i);
}
WalletService.printTx("depositTx", depositTx);
WalletService.verifyTransaction(depositTx);
WalletService.checkWalletConsistency(wallet);
broadcastTx(depositTx, callback);
return depositTx;
}
Aggregations