package org.coinjoin.client; import java.security.PublicKey; import java.security.interfaces.RSAPublicKey; import java.util.Arrays; import org.bitcoinj.core.Address; import org.bitcoinj.core.Coin; import org.bitcoinj.core.ECKey; import org.bitcoinj.core.ScriptException; import org.bitcoinj.core.Sha256Hash; import org.bitcoinj.core.Transaction; import org.bitcoinj.core.Transaction.SigHash; import org.bitcoinj.core.TransactionInput; import org.bitcoinj.core.TransactionOutput; import org.bitcoinj.core.Wallet.SendRequest; import org.bitcoinj.crypto.TransactionSignature; import org.bitcoinj.script.ScriptBuilder; import org.coinjoin.server.MainServer.TxStatus; import org.coinjoin.util.RSABlindSignUtil; import org.coinjoin.util.RSABlindedData; import wallettemplate.Main; import wallettemplate.MainController; import javafx.application.Platform; import javafx.beans.Observable; import javafx.beans.property.StringProperty; import javafx.beans.value.ObservableStringValue; import javafx.collections.ObservableList; import javafx.concurrent.Task; import javafx.scene.control.TextArea; public class MixStart extends Task { private final long CHUNK_SIZE = 1000000; private StringProperty debugger; private TransactionOutput inputBuilder; private Address destination; private Address change; private MainController main; public MixStart(MainController main, StringProperty debugger, TransactionOutput inputBuilder, Address destination, Address change) { this.debugger = debugger; this.inputBuilder = inputBuilder; this.destination = destination; this.change = change; this.main = main; } private void log(String message) { Platform.runLater(new Runnable() { @Override public void run() { debugger.setValue(message + "\n" + debugger.getValue()); } }); } private void finish() { Platform.runLater(new Runnable() { @Override public void run() { main.finishMix(); } }); } @Override protected Object call() throws Exception { // Get RSA Key PublicKey pub = null; byte[] outputSig = null; byte[] blindSig = null; TxStatus status = TxStatus.OPEN; Transaction toSign = null; log("[INFO] Getting RSA Key..."); try { pub = SSLClient.getPublicRSA(); } catch (APIException e) { log("[ERROR] Server failed to respond.\n"); e.printStackTrace(); finish(); return null; } // Generate Blinded Output Address and Change TransactionOutput log("[INFO] Blinding Output Address"); RSABlindedData blindAddr = RSABlindSignUtil.blindData(pub, destination.getHash160()); Transaction dummy = new Transaction(wallettemplate.Main.params); dummy.addOutput(inputBuilder.getValue().subtract(Coin.valueOf(CHUNK_SIZE)), change); // Register Blinded Output and Change Transaction log("[INFO] Registering Input Address..."); try { blindSig = SSLClient.registerInput(pub.hashCode(), inputBuilder, dummy.getOutput(0), blindAddr.GetData()); } catch (APIException e) { log("[ERROR] Server failed to respond.\n"); e.printStackTrace(); finish(); return null; } // Unblind Signature log("[INFO] Unblinding Signature..."); outputSig = RSABlindSignUtil.unblindSignature(pub, blindAddr, blindSig); // Wait Until PENDING log("[INFO] Waiting until PENDING..."); while(status != TxStatus.PENDING) { try { status = SSLClient.txidStatus(pub.hashCode()); } catch (APIException e) { log("[ERROR] Server failed to respond.\n"); e.printStackTrace(); finish(); return null; } Thread.sleep(1000); } // Register Output Address log("[INFO] Registering Output Address..."); try { toSign = SSLClient.registerOutput(pub.hashCode(), destination, outputSig); } catch (APIException e) { log("[ERROR] Server failed to respond.\n"); e.printStackTrace(); finish(); return null; } // If Transaction is null, wait until SIGNING, then request again if (toSign == null) { // Wait Until Signing log("[INFO] Waiting until SIGNING..."); status = TxStatus.PENDING; while(status != TxStatus.SIGNING) { try { status = SSLClient.txidStatus(pub.hashCode()); } catch (APIException e) { log("[ERROR] Server failed to respond.\n"); e.printStackTrace(); finish(); return null; } Thread.sleep(1000); } log("[INFO] Requesting Full Transaction..."); try { toSign = SSLClient.registerOutput(pub.hashCode(), destination, outputSig); } catch (APIException e) { log("[ERROR] Server failed to respond.\n"); e.printStackTrace(); finish(); return null; } } // Sign Transaction log("[INFO] Checking for Output among " + toSign.getOutputs().size() + "..."); boolean isThere = false; for(TransactionOutput output : toSign.getOutputs()) { if (output.getAddressFromP2PKHScript(output.getParams()).equals(destination) && output.getValue().equals(Coin.valueOf(CHUNK_SIZE))) { isThere = true; } } log("[INFO] Signing Transaction..."); int index = -1; if (isThere) { // find input to sign for (index = 0; index < toSign.getInputs().size(); index++) { TransactionInput i = toSign.getInput(index); if (i.getOutpoint().getHash().equals(inputBuilder.getParentTransaction().getHash())) { ECKey signKey = Main.bitcoin.wallet().findKeyFromPubHash(inputBuilder.getScriptPubKey().getPubKeyHash()); Sha256Hash sighash = toSign.hashForSignature(index, inputBuilder.getScriptPubKey(), SigHash.ALL, false); ECKey.ECDSASignature mySignature = signKey.sign(sighash); i.setScriptSig(ScriptBuilder.createInputScript(new TransactionSignature(mySignature, SigHash.ALL, false), signKey)); break; } } } else { log("[WARN] Output Not Found! Aborting..."); finish(); return null; } // Search for signed statement log("[INFO] Verifying Transaction..."); TransactionInput finalInput = toSign.getInput(index); int finalIndex = index; finalInput = toSign.getInput(index); try { finalInput.verify(inputBuilder); } catch (ScriptException e) { log("[WARN] Could Not Verify Signature! Aborting..."); finish(); return null; } // Register Signature log("[INFO] Sending Signature..."); try { status = SSLClient.registerSignature(pub.hashCode(), finalIndex, finalInput); } catch (APIException e) { log("[ERROR] Server failed to respond.\n"); e.printStackTrace(); finish(); return null; } // Wait for BROADCAST log("[INFO] Waiting until BROADCAST..."); while(status != TxStatus.BROADCAST) { try { status = SSLClient.txidStatus(pub.hashCode()); } catch (APIException e) { log("[ERROR] Server failed to respond.\n"); e.printStackTrace(); finish(); return null; } Thread.sleep(1000); } log("[INFO] Transaction Successful!"); finish(); return null; } }