package org.coinjoin.server;
import java.util.Arrays;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.TransactionInput;
import org.bitcoinj.core.TransactionOutput;
import org.coinjoin.server.MainServer.TxStatus;
import org.coinjoin.server.MainServer.TxWrapper;
import org.coinjoin.server.SSLListener.SSLStatus;
import org.coinjoin.util.RSABlindSignUtil;
public class SSLAPI {
public static MainServer server;
/**
* @param output: Write the RSA Public Key for a currently OEPN transaction.
* and the ID of that transaction.
* @return Status
* @
*/
public static SSLResponse getPublicRSA() {
SSLResponse response = new SSLResponse();
Integer currentOpenID = server.currentOpenID();
if (currentOpenID == null) {
response.retObjects.add("Error: no currently open transaction. Please try again later.");
response.retStatus = SSLStatus.ERR_SERVER;
return response;
}
TxWrapper currentOpen = server.lockTransaction(currentOpenID);
if (currentOpen == null) {
response.retObjects.add("Error: transaction does not exist");
response.retStatus = SSLStatus.ERR_CLIENT;
return response;
}
response.retObjects.add(currentOpen.rsa.getPublic());
server.releaseTransaction(currentOpen);
response.retStatus = SSLStatus.OK;
return response;
};
/**
* If the Transaction is OPEN and
* the difference between Input and Change Address is no smaller than CHUNK_SIZE,
* add the input and change output to the corresponding
* transaction and sign the blinded output address.
* Otherwise, return the status of the Transaction and an error message.
* @param txid: Transaction ID
* @param outputAddr: Blinded output address for signing.
* @param inputs: A transaction containing the unsigned input and the change output.
* @param output: Write RSA signed output address if successful.
* @return Status
*/
public static SSLResponse registerInput(int txid,
TransactionOutput inputBuilder, TransactionOutput changeOut, byte[] blindedOutput) {
SSLResponse response = new SSLResponse();
// Check that input/change address add up to CHUNK_SIZE or bigger
if(inputBuilder.getValue().subtract(changeOut.getValue()).value < MainServer.CHUNK_SIZE) {
response.retObjects.add("Error: Input and change address do not add upt to chunk size.");
response.retStatus = SSLStatus.ERR_CLIENT;
return response;
}
// Check that transaction is open
TxWrapper wrapper = server.lockTransaction(txid);
if (wrapper == null) {
response.retObjects.add("Error: transaction does not exist.");
response.retStatus = SSLStatus.ERR_CLIENT;
return response;
} else if (wrapper.status != TxStatus.OPEN) {
response.retObjects.add("Error: transaction is not longer open.");
server.releaseTransaction(wrapper);
response.retStatus = SSLStatus.ERR_SERVER;
return response;
}
// Add Input and Output to Transaction and write signed output
wrapper.tx.addInput(inputBuilder);
wrapper.tx.addOutput(changeOut);
byte[] retSig = RSABlindSignUtil.signData(wrapper.rsa.getPrivate(), blindedOutput);
response.retObjects.add(retSig);
server.releaseTransaction(wrapper);
response.retStatus = SSLStatus.OK;
return response;
};
/**
* If the Transaction is PENDING or OPEN and the signature is correct for the key
* corresponding to the txid, add the outputAddress to that transaction.
*
* If the number of outputs equals the number of
* inputs, return the fully signed transaction and set the transaction to SIGNING.
*
* If the Transaction is SIGNING, return the fully signed transaction.
*
* If the Transaction is OPEN or FAILED, return error.
*
* @param output: On success, write the completed transaction hash for signing , else
* write the status of the Transaction (OPEN, PENDING, SIGNING, FAILED, CLEARED) and an
* error message.
* @param txid: Transaction ID
* @param outputAddr: Unblinded output address to add to transaction.
* @param outputSig: RSA Signature of the Output Address.
* @return Status
*/
public static SSLResponse registerOutput(int txid, Address outputAddr, byte[] outputSig) {
SSLResponse response = new SSLResponse();
// Check Transaction Status
TxWrapper wrapper = server.lockTransaction(txid);
if (wrapper == null) {
response.retObjects.add("Error: transaction does not exist.");
response.retStatus = SSLStatus.ERR_CLIENT;
return response;
} else if (wrapper.status == TxStatus.OPEN || wrapper.status == TxStatus.FAILED) {
response.retObjects.add("Error: transaction is not PENDING or SIGNING.");
server.releaseTransaction(wrapper);
response.retStatus = SSLStatus.ERR_SERVER;
return response;
}
// Check Output Signature
if (!RSABlindSignUtil.verifyData(wrapper.rsa.getPublic(), outputAddr.getHash160(), outputSig)) {
response.retObjects.add("Error: Signature does not match given txid.");
server.releaseTransaction(wrapper);
response.retStatus = SSLStatus.ERR_CLIENT;
return response;
}
// If PENDING, add output to transaction.
if (wrapper.status == TxStatus.PENDING) {
// Check for Duplicates
boolean isDuplicate = false;
for (TransactionOutput t : wrapper.tx.getOutputs()) {
if (t.getAddressFromP2PKHScript(t.getParams()).equals(outputAddr)) {
isDuplicate = true;
break;
}
}
if (!isDuplicate) {
wrapper.tx.addOutput(Coin.valueOf(MainServer.CHUNK_SIZE), outputAddr);
wrapper.regOutputs++;
if (wrapper.regOutputs >= wrapper.tx.getInputs().size()) {
String err = server.feeTransaction(wrapper);
if (err != null) {
wrapper.status = TxStatus.FAILED;
System.err.println("Error: Not enough fee for transaction.");
response.retObjects.add("Error: transaction has FAILED.");
server.releaseTransaction(wrapper);
response.retStatus = SSLStatus.ERR_SERVER;
return response;
}
wrapper.status = TxStatus.SIGNING;
}
}
}
// If SIGNING, return full transaction.
if (wrapper.status == TxStatus.SIGNING) {
response.retObjects.add(wrapper.tx);
} else {
response.retObjects.add(null);
}
server.releaseTransaction(wrapper);
response.retStatus = SSLStatus.OK;
return response;
}
/**
* If the transaction is SIGNING, verify the signed input, then add the
* input to the transaction and remove the unsigned input. If all inputs
* are signed. Broadcast the transaction and set its state to BROADCAST.
* @param output: Write the status of the Transaction (OPEN, PENDING, SIGNING, FAILED, BROADCAST, CLEARED)
* and an error message.
* @param txid: Transaction ID
* @param signedInput: Signed Input for the Transaction
* @return HTTP Status (200 on Success)
*/
public static SSLResponse registerSignature(int txid, int inputIndex, TransactionInput signedInput) {
SSLResponse response = new SSLResponse();
// Check Transaction Status
TxWrapper wrapper = server.lockTransaction(txid);
if (wrapper == null) {
response.retObjects.add(TxStatus.CLEARED);
response.retObjects.add("Transaction doesn't exist or has been cleared.");
response.retStatus = SSLStatus.ERR_CLIENT;
return response;
} else if (wrapper.status != TxStatus.SIGNING) {
response.retObjects.add(wrapper.status);
response.retObjects.add("Error: transaction is not SIGNING.");
server.releaseTransaction(wrapper);
response.retStatus = SSLStatus.ERR_SERVER;
return response;
}
// Add signature to current transaction
TransactionInput unsigned = wrapper.tx.getInput(inputIndex);
unsigned.setScriptSig(signedInput.getScriptSig());
try {
unsigned.verify();
} catch (Exception e) {
e.printStackTrace();
response.retObjects.add(wrapper.status);
response.retObjects.add("Error: signature invalid.");
server.releaseTransaction(wrapper);
response.retStatus = SSLStatus.ERR_CLIENT;
return response;
}
wrapper.signedInputs++;
// If all inputs signed, broadcast transaction
if (wrapper.signedInputs >= wrapper.tx.getInputs().size()) {
String err = server.broadcastTransaction(wrapper);
if (err != null) {
wrapper.status = TxStatus.FAILED;
System.err.println("Broadcast Error: " + err);
response.retObjects.add("Error: transaction has FAILED.");
server.releaseTransaction(wrapper);
response.retStatus = SSLStatus.ERR_SERVER;
return response;
}
wrapper.status = TxStatus.BROADCAST;
}
// Write status and success message.
response.retObjects.add(wrapper.status);
response.retObjects.add("Success");
server.releaseTransaction(wrapper);
response.retStatus = SSLStatus.OK;
return response;
}
/**
* Return the transaction status. If the transaction doesn't exist, its status is CLEARED.
* @param output: Write the Status of the transaction (OPEN, PENDING, SIGNING, FAILED, BROADCAST, CLEARED).
* @param txid: Transaction ID
* @return Status
*/
public static SSLResponse txidStatus(int txid) {
SSLResponse response = new SSLResponse();
TxWrapper wrapper = server.lockTransaction(txid);
if (wrapper == null) {
response.retObjects.add(TxStatus.CLEARED);
} else {
response.retObjects.add(wrapper.status);
System.out.println("Return Status: " + wrapper.status.toString());
}
server.releaseTransaction(wrapper);
response.retStatus = SSLStatus.OK;
return response;
}
}