From 9a290042519fac3e6788d57e3d0642bd35689a93 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Wed, 7 May 2014 18:09:08 +0200 Subject: [PATCH] pay out from MS fund --- README.md | 2 +- TODO.txt | 5 +- .../java/io/bitsquare/btc/WalletFacade.java | 541 +++++++++++------- 3 files changed, 348 insertions(+), 200 deletions(-) diff --git a/README.md b/README.md index b85f5d5494..ff6dedeb54 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,9 @@ git clone --recursive git://github.com/bitsquare/bitsquare * Setup with account registration and tx with OP_RETURN + embedded and blinded bank account data * Offer fee payment with a OP_RETURN tx and fees to miners * Pay in to MS fund +* Payout from MS fund ### Next steps: -* Payout from MS fund * Arbitrator integration * Messaging system * Other trade variants (Buy BTC taker, Sell BTC offerer, Sell BTC offerer) diff --git a/TODO.txt b/TODO.txt index 2f9cc5dcbf..d7537c35f8 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,7 +1,6 @@ -- pay out from MS in payment process - arbitration integration - -Messaging! +- Messaging! +- settings low prio: - tx confirm. not working correct and reliable diff --git a/src/main/java/io/bitsquare/btc/WalletFacade.java b/src/main/java/io/bitsquare/btc/WalletFacade.java index f0988347b4..4f56a96dfd 100644 --- a/src/main/java/io/bitsquare/btc/WalletFacade.java +++ b/src/main/java/io/bitsquare/btc/WalletFacade.java @@ -16,6 +16,7 @@ import com.google.inject.Inject; import io.bitsquare.crypto.CryptoFacade; import io.bitsquare.gui.util.Popups; import javafx.application.Platform; +import javafx.util.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,9 +38,9 @@ public class WalletFacade implements WalletEventListener // for testing trade process between offerer and taker //public static final String WALLET_PREFIX = "offerer"; // offerer - // public static final String WALLET_PREFIX = "taker"; // offerer + public static final String WALLET_PREFIX = "taker"; // offerer - public static final String WALLET_PREFIX = "bitsquare"; + //public static final String WALLET_PREFIX = "bitsquare"; private static final Logger log = LoggerFactory.getLogger(WalletFacade.class); @@ -112,200 +113,8 @@ public class WalletFacade implements WalletEventListener log.info(wallet.toString()); - // testTradeProcess(); - } - - // TODO only temp. for testing trade process between offerer and taker - private void testTradeProcess() - { - //wallet.allowSpendingUnconfirmedTransactions(); - - try - { - String tx1AsHex, tx2AsHex, tx2ScriptSigAsHex, tx2ConnOutAsHex; - - BigInteger offererAmount = Utils.toNanoCoins("0.01"); - BigInteger takerAmount = Utils.toNanoCoins("0.02"); - - String takerPubKey = "0207cf5fb65d6923d5d41db21ceac9567a0fc3eb92c6137f274018381ced7b6568"; - String offererPubKey = "0352f2e34760514099f90b03aab91239466924c3b06047d3cf0e011f26ef96ceb7"; - String arbitratorPubKey = ""; - - // 1 offerer creates MS TX and pay in - /* Transaction tx1 = offererCreatesMSTxAndAddPayment(offererAmount, offererPubKey, takerPubKey, arbitratorPubKey); - tx1AsHex = Utils.bytesToHexString(tx1.bitcoinSerialize()); - */ - - tx1AsHex = "01000000014378dfcd19add18eb6f118a1e35ced127ff23c9dc5034eee1cda5b9caeb814f0000000006b4830450221008e599dd7bb7223c7b036869198b14f08009f9bc117709d23c249d0bdd6b483be022047be181f467782ea277b36890feb2f6de3ceddcedf8730a9f505bac36b3b015b01210352f2e34760514099f90b03aab91239466924c3b06047d3cf0e011f26ef96ceb7ffffffff0240420f00000000004852210352f2e34760514099f90b03aab91239466924c3b06047d3cf0e011f26ef96ceb7210207cf5fb65d6923d5d41db21ceac9567a0fc3eb92c6137f274018381ced7b65680053aeb077e605000000001976a9149fc3d8e0371b6eab89a8c3c015839f9e493ccf6588ac00000000"; - - // 2. taker pay in and sign - /* Transaction tx2 = takerAddPaymentAndSign(takerAmount, msOutputAmount, offererPubKey, takerPubKey, arbitratorPubKey, tx1AsHex); - tx2AsHex = Utils.bytesToHexString(tx2.bitcoinSerialize()); - tx2ScriptSigAsHex = Utils.bytesToHexString(tx2.getInput(1).getScriptBytes()); - tx2ConnOutAsHex = Utils.bytesToHexString(tx2.getInput(1).getConnectedOutput().getParentTransaction().bitcoinSerialize()); - */ - - tx2AsHex = "01000000024378dfcd19add18eb6f118a1e35ced127ff23c9dc5034eee1cda5b9caeb814f0000000006b4830450221008e599dd7bb7223c7b036869198b14f08009f9bc117709d23c249d0bdd6b483be022047be181f467782ea277b36890feb2f6de3ceddcedf8730a9f505bac36b3b015b01210352f2e34760514099f90b03aab91239466924c3b06047d3cf0e011f26ef96ceb7ffffffffa58b22a93a0fcf99ba48aa3b96d842284b2b3d24f72d045cc192ea8a6b89435c010000006a47304402207f4beeb1a86432be0b4c3d4f4db7416b52b66c84383d1980d39e21d547a1762f02200405d0d4b80d1094e3a08cb39ef6f1161be163026d417af08d54c5a1cfdbbbeb01210207cf5fb65d6923d5d41db21ceac9567a0fc3eb92c6137f274018381ced7b6568ffffffff03c0c62d00000000004852210352f2e34760514099f90b03aab91239466924c3b06047d3cf0e011f26ef96ceb7210207cf5fb65d6923d5d41db21ceac9567a0fc3eb92c6137f274018381ced7b65680053aeb077e605000000001976a9149fc3d8e0371b6eab89a8c3c015839f9e493ccf6588ac7035d705000000001976a914e5175c1f71c28218306d4a27c8cec0269dddbbde88ac00000000"; - tx2ScriptSigAsHex = "47304402207f4beeb1a86432be0b4c3d4f4db7416b52b66c84383d1980d39e21d547a1762f02200405d0d4b80d1094e3a08cb39ef6f1161be163026d417af08d54c5a1cfdbbbeb01210207cf5fb65d6923d5d41db21ceac9567a0fc3eb92c6137f274018381ced7b6568"; - tx2ConnOutAsHex = "01000000014378dfcd19add18eb6f118a1e35ced127ff23c9dc5034eee1cda5b9caeb814f0010000006a473044022011431387fc19b093b26a6d2371995c828179aae68e94ad5804e5d0986a6b471302206abc2b698375620e65fc9970b7781da0af2179d1bdc4ebc82a13e285359a3ce7012103c7b9e9ef657705522c85b8429bb2b42c04f0fd4a09e0605cd7dd62ffecb57944ffffffff02c0ce823e000000001976a9142d1b4347ae850805f3badbb4b2949674f46c4ccd88ac00e1f505000000001976a914e5175c1f71c28218306d4a27c8cec0269dddbbde88ac00000000"; - - // 3. offerer sign and send - Transaction tx3 = offererSignAndSendTx(tx1AsHex, tx2AsHex, tx2ConnOutAsHex, tx2ScriptSigAsHex); - - log.info(tx3.toString()); - - } catch (AddressFormatException | InsufficientMoneyException | InterruptedException | ExecutionException e) - { - e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. - } catch (Exception e) - { - e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. - } - } - - // 1. offerer - private Transaction offererCreatesMSTxAndAddPayment(BigInteger offererAmount, String offererPubKey, String takerPubKey, String arbitratorPubKey) throws InsufficientMoneyException - { - // use that to use the convenient api for getting the best coin selection and fee calculation - // TODO should be constructed manually - Transaction tx = new Transaction(params); - Script multiSigOutputScript = getMultiSigScript(offererPubKey, takerPubKey, arbitratorPubKey); - tx.addOutput(offererAmount, multiSigOutputScript); - Wallet.SendRequest request = Wallet.SendRequest.forTx(tx); - wallet.completeTx(request); - // TODO remove sig or use SigHash.NONE - //tx.getInput(0).setScriptSig(null); - - /* - IN[0] offerer - OUT[0] MS - OUT[1] offerer change - */ - return tx; - } - - // 2. taker - public Transaction takerAddPaymentAndSign(BigInteger takerAmount, - BigInteger msOutputAmount, - String offererPubKey, - String takerPubKey, - String arbitratorPubKey, - String tx1AsHex - ) throws InsufficientMoneyException, ExecutionException, InterruptedException, AddressFormatException - { - Script multiSigOutputScript = getMultiSigScript(offererPubKey, takerPubKey, arbitratorPubKey); - - // use that to use the convenient api for getting the best coin selection and fee calculation - // TODO should be constructed manually - Transaction dummyTx = new Transaction(params); - dummyTx.addOutput(takerAmount, multiSigOutputScript); - wallet.completeTx(Wallet.SendRequest.forTx(dummyTx)); - - Transaction tx = new Transaction(params, Utils.parseAsHexOrBase58(tx1AsHex)); - tx.addInput(dummyTx.getInput(0)); - tx.addOutput(dummyTx.getOutput(1)); - tx.getOutput(0).setValue(msOutputAmount); - - TransactionInput input = tx.getInput(1); - Script scriptPubKey = input.getConnectedOutput().getScriptPubKey(); - ECKey sigKey = input.getOutpoint().getConnectedKey(wallet); - Sha256Hash hash = tx.hashForSignature(1, scriptPubKey, Transaction.SigHash.ALL, false); - ECKey.ECDSASignature ecSig = sigKey.sign(hash); - TransactionSignature txSig = new TransactionSignature(ecSig, Transaction.SigHash.ALL, false); - if (scriptPubKey.isSentToRawPubKey()) - input.setScriptSig(ScriptBuilder.createInputScript(txSig)); - else if (scriptPubKey.isSentToAddress()) - input.setScriptSig(ScriptBuilder.createInputScript(txSig, sigKey)); - else - throw new ScriptException("Don't know how to sign for this kind of scriptPubKey: " + scriptPubKey); - - input.getScriptSig().correctlySpends(tx, 1, scriptPubKey, false); - - /* - IN[0] offerer - IN[1] taker signed - OUT[0] MS - OUT[1] offerer change - OUT[2] taker change - */ - - return tx; - } - - public Transaction offererSignAndSendTx(String tx1AsHex, - String tx2AsHex, - String tx2ConnOutAsHex, - String tx2ScriptSigAsHex) throws Exception - { - Transaction tx = new Transaction(params); - - Transaction tx1 = new Transaction(params, Utils.parseAsHexOrBase58(tx1AsHex)); - Transaction tx1ConnOut = wallet.getTransaction(tx1.getInput(0).getOutpoint().getHash()); - TransactionOutPoint tx1OutPoint = new TransactionOutPoint(params, 0, tx1ConnOut); - TransactionInput tx1Input = new TransactionInput(params, tx, tx1.getInput(0).getScriptBytes(), tx1OutPoint); - tx1Input.setParent(tx); - tx.addInput(tx1Input); - - Transaction tx2 = new Transaction(params, Utils.parseAsHexOrBase58(tx2AsHex)); - Transaction tx2ConnOut = new Transaction(params, Utils.parseAsHexOrBase58(tx2ConnOutAsHex)); - TransactionOutPoint tx2OutPoint = new TransactionOutPoint(params, 1, tx2ConnOut); - TransactionInput tx2Input = new TransactionInput(params, tx, Utils.parseAsHexOrBase58(tx2ScriptSigAsHex), tx2OutPoint); - tx2Input.setParent(tx); - tx.addInput(tx2Input); - - tx.addOutput(tx2.getOutput(0)); - tx.addOutput(tx2.getOutput(1)); - tx.addOutput(tx2.getOutput(2)); - - TransactionInput input = tx.getInput(0); - Script scriptPubKey = input.getConnectedOutput().getScriptPubKey(); - ECKey sigKey = input.getOutpoint().getConnectedKey(wallet); - Sha256Hash hash = tx.hashForSignature(0, scriptPubKey, Transaction.SigHash.ALL, false); - ECKey.ECDSASignature ecSig = sigKey.sign(hash); - TransactionSignature txSig = new TransactionSignature(ecSig, Transaction.SigHash.ALL, false); - if (scriptPubKey.isSentToRawPubKey()) - input.setScriptSig(ScriptBuilder.createInputScript(txSig)); - else if (scriptPubKey.isSentToAddress()) - input.setScriptSig(ScriptBuilder.createInputScript(txSig, sigKey)); - else - throw new ScriptException("Don't know how to sign for this kind of scriptPubKey: " + scriptPubKey); - - input.getScriptSig().correctlySpends(tx, 0, scriptPubKey, false); - - input = tx.getInput(1); - scriptPubKey = input.getConnectedOutput().getScriptPubKey(); - input.getScriptSig().correctlySpends(tx, 1, scriptPubKey, false); - - /* - IN[0] offerer signed - IN[1] taker signed - OUT[0] MS - OUT[1] offerer change - OUT[2] taker change - */ - - tx.verify(); - - ListenableFuture broadcastComplete = walletAppKit.peerGroup().broadcastTransaction(tx); - - FutureCallback callback = new FutureCallback() - { - @Override - public void onSuccess(Transaction transaction) - { - log.info("sendResult onSuccess:" + transaction.toString()); - } - - @Override - public void onFailure(Throwable t) - { - log.warn("sendResult onFailure:" + t.toString()); - Popups.openErrorPopup("Fee payment failed", "Fee payment failed. " + t.toString()); - } - }; - Futures.addCallback(broadcastComplete, callback); - - return tx; + // testTradeProcessDepositTx(); + // testTradeProcessPayOutTx(); } public void shutDown() @@ -501,6 +310,346 @@ public class WalletFacade implements WalletEventListener } + /////////////////////////////////////////////////////////////////////////////////////////// + // testTradeProcess methods + /////////////////////////////////////////////////////////////////////////////////////////// + + // TODO only temp. for testing trade process between offerer and taker + // deposit + private void testTradeProcessDepositTx() + { + try + { + String tx1AsHex, tx2AsHex, tx2ScriptSigAsHex, tx2ConnOutAsHex; + + BigInteger offererAmount = Utils.toNanoCoins("0.01"); + BigInteger takerAmount = Utils.toNanoCoins("0.02"); + + String takerPubKey = "0207cf5fb65d6923d5d41db21ceac9567a0fc3eb92c6137f274018381ced7b6568"; + String offererPubKey = "0352f2e34760514099f90b03aab91239466924c3b06047d3cf0e011f26ef96ceb7"; + String arbitratorPubKey = ""; + + // 1 offerer creates MS TX and pay in + /* Transaction tx1 = offererCreatesMSTxAndAddPayment(offererAmount, offererPubKey, takerPubKey, arbitratorPubKey); + tx1AsHex = Utils.bytesToHexString(tx1.bitcoinSerialize()); + */ + + tx1AsHex = "01000000014378dfcd19add18eb6f118a1e35ced127ff23c9dc5034eee1cda5b9caeb814f0000000006b4830450221008e599dd7bb7223c7b036869198b14f08009f9bc117709d23c249d0bdd6b483be022047be181f467782ea277b36890feb2f6de3ceddcedf8730a9f505bac36b3b015b01210352f2e34760514099f90b03aab91239466924c3b06047d3cf0e011f26ef96ceb7ffffffff0240420f00000000004852210352f2e34760514099f90b03aab91239466924c3b06047d3cf0e011f26ef96ceb7210207cf5fb65d6923d5d41db21ceac9567a0fc3eb92c6137f274018381ced7b65680053aeb077e605000000001976a9149fc3d8e0371b6eab89a8c3c015839f9e493ccf6588ac00000000"; + + // 2. taker pay in and sign + /* Transaction tx2 = takerAddPaymentAndSign(takerAmount, msOutputAmount, offererPubKey, takerPubKey, arbitratorPubKey, tx1AsHex); + tx2AsHex = Utils.bytesToHexString(tx2.bitcoinSerialize()); + tx2ScriptSigAsHex = Utils.bytesToHexString(tx2.getInput(1).getScriptBytes()); + tx2ConnOutAsHex = Utils.bytesToHexString(tx2.getInput(1).getConnectedOutput().getParentTransaction().bitcoinSerialize()); + */ + + tx2AsHex = "01000000024378dfcd19add18eb6f118a1e35ced127ff23c9dc5034eee1cda5b9caeb814f0000000006b4830450221008e599dd7bb7223c7b036869198b14f08009f9bc117709d23c249d0bdd6b483be022047be181f467782ea277b36890feb2f6de3ceddcedf8730a9f505bac36b3b015b01210352f2e34760514099f90b03aab91239466924c3b06047d3cf0e011f26ef96ceb7ffffffffa58b22a93a0fcf99ba48aa3b96d842284b2b3d24f72d045cc192ea8a6b89435c010000006a47304402207f4beeb1a86432be0b4c3d4f4db7416b52b66c84383d1980d39e21d547a1762f02200405d0d4b80d1094e3a08cb39ef6f1161be163026d417af08d54c5a1cfdbbbeb01210207cf5fb65d6923d5d41db21ceac9567a0fc3eb92c6137f274018381ced7b6568ffffffff03c0c62d00000000004852210352f2e34760514099f90b03aab91239466924c3b06047d3cf0e011f26ef96ceb7210207cf5fb65d6923d5d41db21ceac9567a0fc3eb92c6137f274018381ced7b65680053aeb077e605000000001976a9149fc3d8e0371b6eab89a8c3c015839f9e493ccf6588ac7035d705000000001976a914e5175c1f71c28218306d4a27c8cec0269dddbbde88ac00000000"; + tx2ScriptSigAsHex = "47304402207f4beeb1a86432be0b4c3d4f4db7416b52b66c84383d1980d39e21d547a1762f02200405d0d4b80d1094e3a08cb39ef6f1161be163026d417af08d54c5a1cfdbbbeb01210207cf5fb65d6923d5d41db21ceac9567a0fc3eb92c6137f274018381ced7b6568"; + tx2ConnOutAsHex = "01000000014378dfcd19add18eb6f118a1e35ced127ff23c9dc5034eee1cda5b9caeb814f0010000006a473044022011431387fc19b093b26a6d2371995c828179aae68e94ad5804e5d0986a6b471302206abc2b698375620e65fc9970b7781da0af2179d1bdc4ebc82a13e285359a3ce7012103c7b9e9ef657705522c85b8429bb2b42c04f0fd4a09e0605cd7dd62ffecb57944ffffffff02c0ce823e000000001976a9142d1b4347ae850805f3badbb4b2949674f46c4ccd88ac00e1f505000000001976a914e5175c1f71c28218306d4a27c8cec0269dddbbde88ac00000000"; + + // 3. offerer sign and send + Transaction tx3 = offererSignAndSendTx(tx1AsHex, tx2AsHex, tx2ConnOutAsHex, tx2ScriptSigAsHex); + + log.info(tx3.toString()); // tx has 453 Bytes + + } catch (AddressFormatException | InsufficientMoneyException | InterruptedException | ExecutionException e) + { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } catch (Exception e) + { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } + } + + // payout + private void testTradeProcessPayOutTx() + { + String depositTxAsHex, offererSignatureR, offererSignatureS; + BigInteger offererPaybackAmount = Utils.toNanoCoins("0.029"); + BigInteger takerPaybackAmount = Utils.toNanoCoins("0.001"); + + ECKey takerKey = new ECKey(null, Utils.parseAsHexOrBase58("0207cf5fb65d6923d5d41db21ceac9567a0fc3eb92c6137f274018381ced7b6568")); + String takerAddress = takerKey.toAddress(params).toString(); + + ECKey offererKey = new ECKey(null, Utils.parseAsHexOrBase58("0352f2e34760514099f90b03aab91239466924c3b06047d3cf0e011f26ef96ceb7")); + String offererAddress = offererKey.toAddress(params).toString(); + + String depositTxID = "4142ee4877eb116abf955a7ec6ef2dc38133b793df762b76d75e3d7d4d8badc9"; + + try + { + // 1. offerer create and sign payout tx + /* Pair result = offererCreateAndSignPayoutTx(depositTxID, + offererPaybackAmount, + takerPaybackAmount, + takerAddress); + + ECKey.ECDSASignature offererSignature = result.getKey(); + Transaction depositTx = result.getValue(); + offererSignatureR = offererSignature.r.toString(); + offererSignatureS = offererSignature.s.toString(); + depositTxAsHex = Utils.bytesToHexString(depositTx.bitcoinSerialize()); */ + + + depositTxAsHex = "01000000024378dfcd19add18eb6f118a1e35ced127ff23c9dc5034eee1cda5b9caeb814f0000000006a473044022008addf33a37f8f058e629020d73c3873953e1eca559fcb59d9db651f2c9b524f02203cbfd319b2675974adfbff2cb605fc9b89a92f2f3197ee634b39c892fb57d1be01210352f2e34760514099f90b03aab91239466924c3b06047d3cf0e011f26ef96ceb7ffffffffa58b22a93a0fcf99ba48aa3b96d842284b2b3d24f72d045cc192ea8a6b89435c010000006a47304402207f4beeb1a86432be0b4c3d4f4db7416b52b66c84383d1980d39e21d547a1762f02200405d0d4b80d1094e3a08cb39ef6f1161be163026d417af08d54c5a1cfdbbbeb01210207cf5fb65d6923d5d41db21ceac9567a0fc3eb92c6137f274018381ced7b6568ffffffff03c0c62d00000000004852210352f2e34760514099f90b03aab91239466924c3b06047d3cf0e011f26ef96ceb7210207cf5fb65d6923d5d41db21ceac9567a0fc3eb92c6137f274018381ced7b65680053aeb077e605000000001976a9149fc3d8e0371b6eab89a8c3c015839f9e493ccf6588ac7035d705000000001976a914e5175c1f71c28218306d4a27c8cec0269dddbbde88ac00000000"; + offererSignatureR = "64562406184784382000330465585294975882418886289667123258365244289311131050102"; + offererSignatureS = "9282449224979858852843663340827968850695102089943078462704407873774672151491"; + + // 2. taker sign and publish tx + Transaction takerTx = takerSignAndSendTx(depositTxAsHex, + offererSignatureR, + offererSignatureS, + offererPaybackAmount, + takerPaybackAmount, + offererAddress); + + log.info(takerTx.toString()); // tx has 265 Bytes + + /* + 6606c366a487bff9e412d0b6c09c14916319932db5954bf5d8719f43f828a3ba: Unknown confidence level. + in [] [30450221008ebd06e53ce1ab7b599098b3117f2073b1d224baae21190ef7839b70a638207602201485ae19965f2d954398f691aa40fecb4d08d625ae96037a4fc5eecb7a4803c301] [30440220521c485045a46fbb61c634ebf456c470f9c2f7307a743e3fce10b50f7a69115d0220249ff5f9796a9fcd12984afcfd3f7d16d2f8c49ddcef245db7c7b02b909af9a601] + outpoint:4142ee4877eb116abf955a7ec6ef2dc38133b793df762b76d75e3d7d4d8badc9:0 hash160:[exception: Script not in the standard scriptPubKey form] + out DUP HASH160 [9fc3d8e0371b6eab89a8c3c015839f9e493ccf65] EQUALVERIFY CHECKSIG 0.0289 BTC + out DUP HASH160 [e5175c1f71c28218306d4a27c8cec0269dddbbde] EQUALVERIFY CHECKSIG 0.0009 BTC + + 2 [0352f2e34760514099f90b03aab91239466924c3b06047d3cf0e011f26ef96ceb7] [0207cf5fb65d6923d5d41db21ceac9567a0fc3eb92c6137f274018381ced7b6568] [] 3 CHECKMULTISIG + */ + + } catch (InsufficientMoneyException | AddressFormatException e) + { + e.printStackTrace(); + } + + } + + // deposit 1. offerer + private Transaction offererCreatesMSTxAndAddPayment(BigInteger offererAmount, String offererPubKey, String takerPubKey, String arbitratorPubKey) throws InsufficientMoneyException + { + // use that to use the convenient api for getting the best coin selection and fee calculation + // TODO should be constructed manually + Transaction tx = new Transaction(params); + Script multiSigOutputScript = getMultiSigScript(offererPubKey, takerPubKey, arbitratorPubKey); + tx.addOutput(offererAmount, multiSigOutputScript); + Wallet.SendRequest request = Wallet.SendRequest.forTx(tx); + wallet.completeTx(request); + // TODO remove sig or use SigHash.NONE + //tx.getInput(0).setScriptSig(null); + + /* + IN[0] offerer + OUT[0] MS + OUT[1] offerer change + */ + return tx; + } + + // deposit 2. taker + public Transaction takerAddPaymentAndSign(BigInteger takerAmount, + BigInteger msOutputAmount, + String offererPubKey, + String takerPubKey, + String arbitratorPubKey, + String tx1AsHex + ) throws InsufficientMoneyException, ExecutionException, InterruptedException, AddressFormatException + { + Script multiSigOutputScript = getMultiSigScript(offererPubKey, takerPubKey, arbitratorPubKey); + + // use that to use the convenient api for getting the best coin selection and fee calculation + // TODO should be constructed manually + Transaction dummyTx = new Transaction(params); + dummyTx.addOutput(takerAmount, multiSigOutputScript); + wallet.completeTx(Wallet.SendRequest.forTx(dummyTx)); + + Transaction tx = new Transaction(params, Utils.parseAsHexOrBase58(tx1AsHex)); + tx.addInput(dummyTx.getInput(0)); + tx.addOutput(dummyTx.getOutput(1)); + tx.getOutput(0).setValue(msOutputAmount); + + TransactionInput input = tx.getInput(1); + Script scriptPubKey = input.getConnectedOutput().getScriptPubKey(); + ECKey sigKey = input.getOutpoint().getConnectedKey(wallet); + Sha256Hash hash = tx.hashForSignature(1, scriptPubKey, Transaction.SigHash.ALL, false); + ECKey.ECDSASignature ecSig = sigKey.sign(hash); + TransactionSignature txSig = new TransactionSignature(ecSig, Transaction.SigHash.ALL, false); + if (scriptPubKey.isSentToRawPubKey()) + input.setScriptSig(ScriptBuilder.createInputScript(txSig)); + else if (scriptPubKey.isSentToAddress()) + input.setScriptSig(ScriptBuilder.createInputScript(txSig, sigKey)); + else + throw new ScriptException("Don't know how to sign for this kind of scriptPubKey: " + scriptPubKey); + + input.getScriptSig().correctlySpends(tx, 1, scriptPubKey, false); + + /* + IN[0] offerer + IN[1] taker signed + OUT[0] MS + OUT[1] offerer change + OUT[2] taker change + */ + + return tx; + } + + // deposit 3. offerer + public Transaction offererSignAndSendTx(String tx1AsHex, + String tx2AsHex, + String tx2ConnOutAsHex, + String tx2ScriptSigAsHex) throws Exception + { + Transaction tx = new Transaction(params); + + Transaction tx1 = new Transaction(params, Utils.parseAsHexOrBase58(tx1AsHex)); + Transaction tx1ConnOut = wallet.getTransaction(tx1.getInput(0).getOutpoint().getHash()); + TransactionOutPoint tx1OutPoint = new TransactionOutPoint(params, 0, tx1ConnOut); + TransactionInput tx1Input = new TransactionInput(params, tx, tx1.getInput(0).getScriptBytes(), tx1OutPoint); + tx1Input.setParent(tx); + tx.addInput(tx1Input); + + Transaction tx2 = new Transaction(params, Utils.parseAsHexOrBase58(tx2AsHex)); + Transaction tx2ConnOut = new Transaction(params, Utils.parseAsHexOrBase58(tx2ConnOutAsHex)); + TransactionOutPoint tx2OutPoint = new TransactionOutPoint(params, 1, tx2ConnOut); + TransactionInput tx2Input = new TransactionInput(params, tx, Utils.parseAsHexOrBase58(tx2ScriptSigAsHex), tx2OutPoint); + tx2Input.setParent(tx); + tx.addInput(tx2Input); + + tx.addOutput(tx2.getOutput(0)); + tx.addOutput(tx2.getOutput(1)); + tx.addOutput(tx2.getOutput(2)); + + TransactionInput input = tx.getInput(0); + Script scriptPubKey = input.getConnectedOutput().getScriptPubKey(); + ECKey sigKey = input.getOutpoint().getConnectedKey(wallet); + Sha256Hash hash = tx.hashForSignature(0, scriptPubKey, Transaction.SigHash.ALL, false); + ECKey.ECDSASignature ecSig = sigKey.sign(hash); + TransactionSignature txSig = new TransactionSignature(ecSig, Transaction.SigHash.ALL, false); + if (scriptPubKey.isSentToRawPubKey()) + input.setScriptSig(ScriptBuilder.createInputScript(txSig)); + else if (scriptPubKey.isSentToAddress()) + input.setScriptSig(ScriptBuilder.createInputScript(txSig, sigKey)); + else + throw new ScriptException("Don't know how to sign for this kind of scriptPubKey: " + scriptPubKey); + + input.getScriptSig().correctlySpends(tx, 0, scriptPubKey, false); + + input = tx.getInput(1); + scriptPubKey = input.getConnectedOutput().getScriptPubKey(); + input.getScriptSig().correctlySpends(tx, 1, scriptPubKey, false); + + /* + IN[0] offerer signed + IN[1] taker signed + OUT[0] MS + OUT[1] offerer change + OUT[2] taker change + */ + + tx.verify(); + + ListenableFuture broadcastComplete = walletAppKit.peerGroup().broadcastTransaction(tx); + + FutureCallback callback = new FutureCallback() + { + @Override + public void onSuccess(Transaction transaction) + { + log.info("sendResult onSuccess:" + transaction.toString()); + } + + @Override + public void onFailure(Throwable t) + { + log.warn("sendResult onFailure:" + t.toString()); + Popups.openErrorPopup("Fee payment failed", "Fee payment failed. " + t.toString()); + } + }; + Futures.addCallback(broadcastComplete, callback); + + return tx; + } + + // payout 1. offerer + private Pair offererCreateAndSignPayoutTx(String depositTxID, + BigInteger offererPaybackAmount, + BigInteger takerPaybackAmount, + String takerAddress) throws InsufficientMoneyException, AddressFormatException + { + ECKey key = wallet.getKeys().get(0); + // offerer has published depositTx so he has it in wallet + Transaction depositTx = wallet.getTransaction(new Sha256Hash(depositTxID)); + TransactionOutput multiSigOutput = depositTx.getOutput(0); + Script multiSigScript = multiSigOutput.getScriptPubKey(); + + Transaction tx = new Transaction(params); + tx.addInput(multiSigOutput); + //TODO fee calculation + tx.addOutput(offererPaybackAmount.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE), key.toAddress(params)); + tx.addOutput(takerPaybackAmount.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE), new Address(params, takerAddress)); + + Sha256Hash sigHash = tx.hashForSignature(0, multiSigScript, Transaction.SigHash.ALL, false); + ECKey.ECDSASignature signature = key.sign(sigHash); + + return new Pair<>(signature, depositTx); + } + + // payout 2. taker + private Transaction takerSignAndSendTx(String depositTxAsHex, + String offererSignatureR, + String offererSignatureS, + BigInteger offererPaybackAmount, + BigInteger takerPaybackAmount, + String offererAddress) throws InsufficientMoneyException, AddressFormatException + { + ECKey key = wallet.getKeys().get(0); + + Transaction depositTx = new Transaction(params, Utils.parseAsHexOrBase58(depositTxAsHex)); + TransactionOutput multiSigOutput = depositTx.getOutput(0); + Script multiSigScript = multiSigOutput.getScriptPubKey(); + + Transaction tx = new Transaction(params); + tx.addInput(multiSigOutput); + //TODO fee calculation + tx.addOutput(offererPaybackAmount.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE), new Address(params, offererAddress)); + tx.addOutput(takerPaybackAmount.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE), key.toAddress(params)); + + Sha256Hash sigHash = tx.hashForSignature(0, multiSigScript, Transaction.SigHash.ALL, false); + + ECKey.ECDSASignature takerSignature = key.sign(sigHash); + TransactionSignature takerTxSig = new TransactionSignature(takerSignature, Transaction.SigHash.ALL, false); + + ECKey.ECDSASignature offererSignature = new ECKey.ECDSASignature(new BigInteger(offererSignatureR), new BigInteger(offererSignatureS)); + TransactionSignature offererTxSig = new TransactionSignature(offererSignature, Transaction.SigHash.ALL, false); + + Script inputScript = ScriptBuilder.createMultiSigInputScript(ImmutableList.of(offererTxSig, takerTxSig)); + tx.getInput(0).setScriptSig(inputScript); + + tx.verify(); + tx.getInput(0).getScriptSig().correctlySpends(tx, 0, multiSigScript, false); + tx.getInput(0).verify(multiSigOutput); + + ListenableFuture broadcastComplete = walletAppKit.peerGroup().broadcastTransaction(tx); + + FutureCallback callback = new FutureCallback() + { + @Override + public void onSuccess(Transaction transaction) + { + log.info("sendResult onSuccess:" + transaction.toString()); + } + + @Override + public void onFailure(Throwable t) + { + log.warn("sendResult onFailure:" + t.toString()); + Popups.openErrorPopup("Fee payment failed", "Fee payment failed. " + t.toString()); + } + }; + Futures.addCallback(broadcastComplete, callback); + + return tx; + } + + /////////////////////////////////////////////////////////////////////////////////////////// // Inner classes ///////////////////////////////////////////////////////////////////////////////////////////