package network.thunder.core.etc; import network.thunder.core.communication.objects.lightning.subobjects.PaymentData; import network.thunder.core.communication.objects.subobjects.PaymentSecret; import network.thunder.core.lightning.RevocationHash; import org.bitcoinj.core.ECKey; import org.bitcoinj.script.Script; import org.bitcoinj.script.ScriptBuilder; import org.bitcoinj.script.ScriptChunk; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.List; /** * Created by matsjerratsch on 03/11/15. */ public class ScriptTools { /* A9 HASH160 76 DUP 87 EQUAL 7C SWAP 93 ADD 63 IF 64 NOTIF 67 ELSE 68 ENDIF B1 CLTV B3 CSV 75 DROP 6D 2DROP AC CHECKSIG */ //Due to some policy, miners won't include OP_NOP until they are activated. Not even on testnet. public static final boolean CLTV_CSV_ENABLED = true; //Templates are like normal scripts but with 0xFF as placeholders for parameters public static final byte[] ANCHOR_OUTPUT_SCRIPT = Tools.hexStringToByteArray("A9 FF 87 63 FF 67 FF 68 52 7C FF 52 AE"); public static final byte[] ESCAPE_INPUT_SCRIPT = Tools.hexStringToByteArray("00 FF FF FF FF"); public static final byte[] COMMIT_INPUT_SCRIPT = Tools.hexStringToByteArray("00 FF FF 00 FF"); public static final byte[] ESCAPE_OUTPUT_SCRIPT = Tools.hexStringToByteArray("A9 FF 87 63 FF 67 FF B3 75 FF 68 AC"); public static final byte[] ESCAPE_OUTPUT_SCRIPT_WITHOUT_CSV = Tools.hexStringToByteArray("A9 FF 87 63 FF 67 FF 75 FF 68 AC"); public static final byte[] ESCAPE_INPUT_REVOCATION_SCRIPT = Tools.hexStringToByteArray("FF FF FF"); public static final byte[] ESCAPE_INPUT_TIMEOUT_SCRIPT = Tools.hexStringToByteArray("FF 00 FF"); public static final byte[] FAST_ESCAPE_OUTPUT_SCRIPT = Tools.hexStringToByteArray("A9 FF 87 63 FF 67 FF B3 75 FF 68 AC"); public static final byte[] FAST_ESCAPE_OUTPUT_SCRIPT_WITHOUT_CSV = Tools.hexStringToByteArray("A9 FF 87 63 FF 67 FF 75 FF 68 AC"); public static final byte[] FAST_ESCAPE_INPUT_SECRET_SCRIPT = Tools.hexStringToByteArray("FF FF FF"); public static final byte[] FAST_ESCAPE_INPUT_TIMEOUT_SCRIPT = Tools.hexStringToByteArray("FF 00 FF"); public static final byte[] CHANNEL_TX_OUTPUT_REVOCATION = Tools.hexStringToByteArray("A9 FF 87 63 FF 67 FF B3 75 FF 68 AC"); public static final byte[] CHANNEL_TX_OUTPUT_PLAIN = Tools.hexStringToByteArray("FF AC"); public static final byte[] CHANNEL_TX_OUTPUT_PAYMENT_SENDING_ONE = Tools.scriptStringToByte( "HASH160 DUP FF EQUAL SWAP FF EQUAL ADD " + "IF " + //If revocation or payment hash is supplied, allow taking directly "FF CHECKSIG " + "ELSE " + //Else pay to refund tx after timeout "FF CLTV DROP OP_2 FF FF OP_2 CHECKMULTISIG " + "ENDIF"); public static Script getChannelTxOutputPaymentSending (ECKey keyServer, ECKey keyClient, RevocationHash revocationHash, PaymentSecret secret, int refundTimeout) { return produceScript(CHANNEL_TX_OUTPUT_PAYMENT_SENDING_ONE, revocationHash.getSecretHash(), secret.hash, keyClient.getPubKey(),integerToByteArray(refundTimeout), keyServer.getPubKey(), keyClient.getPubKey()); } //TODO not very efficient yet with 3 times client pubkey... public static final byte[] CHANNEL_TX_OUTPUT_PAYMENT_RECEIVING_ONE = Tools.scriptStringToByte( "HASH160 FF EQUAL " + "IF " + //If revocation or payment hash is supplied, allow taking directly "FF CHECKSIG " + "ELSE " + "HASH160 FF EQUAL" + "IF " + //Pay to redeem tx if payment hash is supplied "OP_2 FF FF OP_2 CHECKMULTISIG " + "ELSE " + //Pay to sender directly after timeout "FF CLTV DROP FF CHECKSIG " + "ENDIF " + "ENDIF "); public static Script getChannelTxOutputPaymentReceiving (ECKey keyServer, ECKey keyClient, RevocationHash revocationHash, PaymentSecret secret, int refundTimeout) { return produceScript(CHANNEL_TX_OUTPUT_PAYMENT_RECEIVING_ONE, revocationHash.getSecretHash(), keyClient.getPubKey(), secret.hash, keyServer.getPubKey(), keyClient.getPubKey(), integerToByteArray(refundTimeout), keyClient.getPubKey()); } public static final byte[] CHANNEL_TX_INPUT_PAYMENT_SENDING_TIMEOUT = Tools.scriptStringToByte("00 FF FF"); public static final byte[] CHANNEL_TX_INPUT_PAYMENT_SENDING_REDEEM = Tools.scriptStringToByte("FF FF"); public static final byte[] CHANNEL_TX_INPUT_PAYMENT_SENDING_STEAL = Tools.scriptStringToByte("FF FF"); public static final byte[] CHANNEL_TX_INPUT_PAYMENT_RECEIVING_TIMEOUT = Tools.scriptStringToByte("00 00 FF"); public static final byte[] CHANNEL_TX_INPUT_PAYMENT_RECEIVING_REDEEM = Tools.scriptStringToByte("00 FF FF"); public static final byte[] CHANNEL_TX_INPUT_PAYMENT_RECEIVING_STEAL = Tools.scriptStringToByte("FF FF"); public static final byte[] CHANNEL_TX_OUTPUT_PAYMENT_TWO = Tools.scriptStringToByte( "HASH160 FF EQUAL " + "IF " + "FF CHECKSIG " + "ELSE " + "FF CSV DROP FF CHECKSIG " + "ENDIF"); public static Script getPaymentTxOutput (ECKey keyReceiver, ECKey keyRevocation, RevocationHash revocationHash, int revocationTimeout) { return produceScript(CHANNEL_TX_OUTPUT_PAYMENT_TWO, revocationHash.getSecretHash(), keyRevocation.getPubKey(), integerToByteArray(revocationTimeout), keyReceiver.getPubKey()); } public static final byte[] CHANNEL_TX_OUTPUT_PAYMENT_SENDING_ONEa = Tools.hexStringToByteArray("A9 FF 87 63 FF AC 67 B1 FF 75 52 FF FF 52 AE 68"); public static final byte[] CHANNEL_TX_OUTPUT_MULTISIG1 = Tools.hexStringToByteArray("A9 FF 87 63 FF AC 67 52 FF FF 52 AE 68"); public static final byte[] CHANNEL_TX_OUTPUT_PAYMENT_SENDING = Tools.hexStringToByteArray("A9 76 FF 87 7C FF 87 93 63 FF 67 FF B1 FF B3 6D FF 68 AC"); public static final byte[] CHANNEL_TX_OUTPUT_PAYMENT_RECEIVING = Tools.hexStringToByteArray("A9 76 FF 87 63 FF B3 6D FF 67 FF 87 64 FF B1 75 68 FF 68 AC"); public static Script getAnchorOutputScript (byte[] secretServerHash, ECKey keyClient, ECKey keyClientA, ECKey keyServer) { return produceScript(ANCHOR_OUTPUT_SCRIPT, secretServerHash, keyClientA.getPubKey(), keyClient.getPubKey(), keyServer.getPubKey()); } /* * Input Script to spend the anchor */ public static Script getEscapeInputScript (byte[] signatureClientA, byte[] signatureServer, byte[] secretServer, byte[] secretServerHash, ECKey keyClient, ECKey keyClientA, ECKey keyServer) { byte[] redeemscript = getAnchorOutputScript(secretServerHash, keyClient, keyClientA, keyServer).getProgram(); return produceScript(ESCAPE_INPUT_SCRIPT, signatureClientA, signatureServer, secretServer, redeemscript); } public static Script getCommitInputScript (byte[] signatureClient, byte[] signatureServer, byte[] secretServerHash, ECKey keyClient, ECKey keyClientA, ECKey keyServer) { byte[] redeemscript = getAnchorOutputScript(secretServerHash, keyClient, keyClientA, keyServer).getProgram(); return produceScript(COMMIT_INPUT_SCRIPT, signatureClient, signatureServer, redeemscript); } /* * Output scripts to build the escape and fast escape transactions. */ public static Script getEscapeOutputScript (byte[] revocationHashServer, ECKey keyServer, ECKey keyClient, int revocationDelay) { if (CLTV_CSV_ENABLED) { return produceScript(ESCAPE_OUTPUT_SCRIPT, revocationHashServer, keyClient.getPubKey(), integerToByteArray(revocationDelay), keyServer.getPubKey()); } else { return produceScript(ESCAPE_OUTPUT_SCRIPT_WITHOUT_CSV, revocationHashServer, keyClient.getPubKey(), integerToByteArray(revocationDelay), keyServer.getPubKey()); } } public static Script getEscapeInputRevocationScript (byte[] revocationHashServer, ECKey keyServer, ECKey keyClient, int revocationDelay, byte[] signatureClient, byte[] revocationClient) { byte[] redeemScript = getEscapeOutputScript(revocationHashServer, keyServer, keyClient, revocationDelay).getProgram(); return produceScript(ESCAPE_INPUT_REVOCATION_SCRIPT, signatureClient, revocationClient, redeemScript); } public static Script getEscapeInputTimeoutScript (byte[] revocationHashServer, ECKey keyServer, ECKey keyClient, int revocationDelay, byte[] signatureServer) { byte[] redeemScript = getEscapeOutputScript(revocationHashServer, keyServer, keyClient, revocationDelay).getProgram(); return produceScript(ESCAPE_INPUT_TIMEOUT_SCRIPT, signatureServer, redeemScript); } public static Script getFastEscapeOutputScript (byte[] secretHashClient, ECKey keyServer, ECKey keyClient, int revocationDelay) { if (CLTV_CSV_ENABLED) { return produceScript(FAST_ESCAPE_OUTPUT_SCRIPT, secretHashClient, keyServer.getPubKey(), integerToByteArray(revocationDelay), keyClient.getPubKey ()); } else { return produceScript(FAST_ESCAPE_OUTPUT_SCRIPT_WITHOUT_CSV, secretHashClient, keyServer.getPubKey(), integerToByteArray(revocationDelay), keyClient.getPubKey()); } } public static Script getFastEscapeInputSecretScript (byte[] secretClientHash, ECKey keyServer, ECKey keyClient, int revocationDelay, byte[] signatureServer, byte[] secretClient) { byte[] redeemScript = getFastEscapeOutputScript(secretClientHash, keyServer, keyClient, revocationDelay).getProgram(); return produceScript(FAST_ESCAPE_INPUT_SECRET_SCRIPT, signatureServer, secretClient, redeemScript); } public static Script getFastEscapeInputTimeoutScript (byte[] secretClientHash, ECKey keyServer, ECKey keyClient, int revocationDelay, byte[] signatureClient) { byte[] redeemScript = getFastEscapeOutputScript(secretClientHash, keyServer, keyClient, revocationDelay).getProgram(); return produceScript(FAST_ESCAPE_INPUT_TIMEOUT_SCRIPT, signatureClient, redeemScript); } public static Script getChannelTxOutputRevocation (RevocationHash revocationHash, ECKey keyServer, ECKey keyClient, int revocationDelay) { return produceScript(CHANNEL_TX_OUTPUT_REVOCATION, revocationHash.getSecretHash(), keyClient.getPubKey(), integerToByteArray(revocationDelay), keyServer.getPubKey()); } public static Script getChannelTxOutputPlain (ECKey keyClient) { return produceScript(CHANNEL_TX_OUTPUT_PLAIN, keyClient.getPubKey()); } public static Script getChannelTxOutputSending (RevocationHash revocationHash, PaymentData paymentData, ECKey keyServer, ECKey keyClient) { return produceScript(CHANNEL_TX_OUTPUT_PAYMENT_SENDING, paymentData.secret.hash, revocationHash.getSecretHash(), keyClient.getPubKey(), integerToByteArray(paymentData.timestampRefund), integerToByteArray(paymentData.csvDelay), keyServer.getPubKey()); } public static Script getChannelTxOutputReceiving (RevocationHash revocationHash, PaymentData paymentData, ECKey keyServer, ECKey keyClient) { return produceScript(CHANNEL_TX_OUTPUT_PAYMENT_RECEIVING, paymentData.secret.hash, integerToByteArray(paymentData.csvDelay), keyClient.getPubKey(), revocationHash.getSecretHash(), integerToByteArray(paymentData.timestampRefund), keyServer.getPubKey()); } public static Script produceScript (byte[] template, byte[]... parameters) { try { ScriptBuilder builder = new ScriptBuilder(); int parameter = 0; for (byte chunk : template) { int op = chunk; if (op < 0) { op = op + 256; } if (op == 255) { builder.data(parameters[parameter]); parameter++; } else if (op == 0) { builder.data(new byte[0]); } else { builder.op(op); } } //Bug in bitcoinJ when dealing with OP_0. Gets solved by reserializing. Script s = builder.build(); return new Script(s.getProgram()); } catch (Exception e) { e.printStackTrace(); return null; } } public static boolean testScript (byte[] script, byte[] template, byte[]... parameters) { Script s = new Script(script); List<ScriptChunk> chunks = s.getChunks(); int parameter = 0; for (int i = 0; i < chunks.size(); i++) { boolean correct = false; ScriptChunk chunk = chunks.get(i); byte templateChunk = template[i]; int op = templateChunk; if (op < 0) { op = op + 256; } if (op == 255) { if (chunk.isPushData()) { if (Arrays.equals(chunk.data, parameters[parameter])) { parameter++; correct = true; } } } else { if (chunk.opcode == op) { correct = true; } } if (!correct) { return false; } } return true; } public static byte[] integerToByteArray (int i) { ByteBuffer buffer = ByteBuffer.allocate(4); buffer.putInt(i); buffer.flip(); return buffer.array(); } }