Unified Bitcoin/Litecoin test apps

This commit is contained in:
catbref 2020-09-16 10:57:45 +01:00
parent 2ffd0770c6
commit af7d7d0966
14 changed files with 803 additions and 1096 deletions

View File

@ -0,0 +1,112 @@
package org.qortal.test.crosschain.apps;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.script.Script.ScriptType;
import org.qortal.crosschain.Litecoin;
import org.qortal.crosschain.Bitcoin;
import org.qortal.crosschain.Bitcoiny;
import org.qortal.crosschain.BitcoinyHTLC;
import com.google.common.hash.HashCode;
public class BuildHTLC {
private static void usage(String error) {
if (error != null)
System.err.println(error);
System.err.println(String.format("usage: BuildHTLC (-b | -l) <refund-P2PKH> <amount> <redeem-P2PKH> <HASH160-of-secret> <locktime>"));
System.err.println("where: -b means use Bitcoin, -l means use Litecoin");
System.err.println(String.format("example: BuildHTLC -l "
+ "msAfaDaJ8JiprxxFaAXEEPxKK3JaZCYpLv \\\n"
+ "\t0.00008642 \\\n"
+ "\tmrBpZYYGYMwUa8tRjTiXfP1ySqNXszWN5h \\\n"
+ "\tdaf59884b4d1aec8c1b17102530909ee43c0151a \\\n"
+ "\t1600000000"));
System.exit(1);
}
public static void main(String[] args) {
if (args.length < 6 || args.length > 6)
usage(null);
Common.init();
Bitcoiny bitcoiny = null;
NetworkParameters params = null;
Address refundAddress = null;
Coin amount = null;
Address redeemAddress = null;
byte[] secretHash = null;
int lockTime = 0;
int argIndex = 0;
try {
switch (args[argIndex++]) {
case "-b":
bitcoiny = Bitcoin.getInstance();
break;
case "-l":
bitcoiny = Litecoin.getInstance();
break;
default:
usage("Only Bitcoin (-b) or Litecoin (-l) supported");
}
params = bitcoiny.getNetworkParameters();
refundAddress = Address.fromString(params, args[argIndex++]);
if (refundAddress.getOutputScriptType() != ScriptType.P2PKH)
usage("Refund address must be in P2PKH form");
amount = Coin.parseCoin(args[argIndex++]);
redeemAddress = Address.fromString(params, args[argIndex++]);
if (redeemAddress.getOutputScriptType() != ScriptType.P2PKH)
usage("Redeem address must be in P2PKH form");
secretHash = HashCode.fromString(args[argIndex++]).asBytes();
if (secretHash.length != 20)
usage("Hash of secret must be 20 bytes");
lockTime = Integer.parseInt(args[argIndex++]);
int refundTimeoutDelay = lockTime - (int) (System.currentTimeMillis() / 1000L);
if (refundTimeoutDelay < 600 || refundTimeoutDelay > 30 * 24 * 60 * 60)
usage("Locktime (seconds) should be at between 10 minutes and 1 month from now");
} catch (IllegalArgumentException e) {
usage(String.format("Invalid argument %d: %s", argIndex, e.getMessage()));
}
Coin p2shFee = Coin.valueOf(Common.getP2shFee(bitcoiny));
if (p2shFee.isZero())
return;
System.out.println(String.format("Refund address: %s", refundAddress));
System.out.println(String.format("Amount: %s", amount.toPlainString()));
System.out.println(String.format("Redeem address: %s", redeemAddress));
System.out.println(String.format("Refund/redeem miner's fee: %s", bitcoiny.format(p2shFee)));
System.out.println(String.format("Script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneOffset.UTC), lockTime));
System.out.println(String.format("Hash of secret: %s", HashCode.fromBytes(secretHash)));
byte[] redeemScriptBytes = BitcoinyHTLC.buildScript(refundAddress.getHash(), lockTime, redeemAddress.getHash(), secretHash);
System.out.println(String.format("Raw script bytes: %s", HashCode.fromBytes(redeemScriptBytes)));
String p2shAddress = bitcoiny.deriveP2shAddress(redeemScriptBytes);
System.out.println(String.format("P2SH address: %s", p2shAddress));
amount = amount.add(p2shFee);
// Fund P2SH
System.out.println(String.format("\nYou need to fund %s with %s (includes redeem/refund fee of %s)",
p2shAddress, bitcoiny.format(amount), bitcoiny.format(p2shFee)));
}
}

View File

@ -0,0 +1,133 @@
package org.qortal.test.crosschain.apps;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.LegacyAddress;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.script.Script.ScriptType;
import org.qortal.crosschain.Bitcoin;
import org.qortal.crosschain.Bitcoiny;
import org.qortal.crosschain.Litecoin;
import org.qortal.crosschain.BitcoinyHTLC;
import org.qortal.crypto.Crypto;
import com.google.common.hash.HashCode;
public class CheckHTLC {
private static void usage(String error) {
if (error != null)
System.err.println(error);
System.err.println(String.format("usage: CheckHTLC (-b | -l) <P2SH-address> <refund-P2PKH> <amount> <redeem-P2PKH> <HASH160-of-secret> <locktime>"));
System.err.println("where: -b means use Bitcoin, -l means use Litecoin");
System.err.println(String.format("example: CheckP2SH -l "
+ "2N4378NbEVGjmiUmoUD9g1vCY6kyx9tDUJ6 \\\n"
+ "msAfaDaJ8JiprxxFaAXEEPxKK3JaZCYpLv \\\n"
+ "\t0.00008642 \\\n"
+ "\tmrBpZYYGYMwUa8tRjTiXfP1ySqNXszWN5h \\\n"
+ "\tdaf59884b4d1aec8c1b17102530909ee43c0151a \\\n"
+ "\t1600184800"));
System.exit(1);
}
public static void main(String[] args) {
if (args.length < 7 || args.length > 7)
usage(null);
Common.init();
Bitcoiny bitcoiny = null;
NetworkParameters params = null;
Address p2shAddress = null;
Address refundAddress = null;
Coin amount = null;
Address redeemAddress = null;
byte[] secretHash = null;
int lockTime = 0;
int argIndex = 0;
try {
switch (args[argIndex++]) {
case "-b":
bitcoiny = Bitcoin.getInstance();
break;
case "-l":
bitcoiny = Litecoin.getInstance();
break;
default:
usage("Only Bitcoin (-b) or Litecoin (-l) supported");
}
params = bitcoiny.getNetworkParameters();
p2shAddress = Address.fromString(params, args[argIndex++]);
if (p2shAddress.getOutputScriptType() != ScriptType.P2SH)
usage("P2SH address invalid");
refundAddress = Address.fromString(params, args[argIndex++]);
if (refundAddress.getOutputScriptType() != ScriptType.P2PKH)
usage("Refund address must be in P2PKH form");
amount = Coin.parseCoin(args[argIndex++]);
redeemAddress = Address.fromString(params, args[argIndex++]);
if (redeemAddress.getOutputScriptType() != ScriptType.P2PKH)
usage("Redeem address must be in P2PKH form");
secretHash = HashCode.fromString(args[argIndex++]).asBytes();
if (secretHash.length != 20)
usage("Hash of secret must be 20 bytes");
lockTime = Integer.parseInt(args[argIndex++]);
} catch (IllegalArgumentException e) {
usage(String.format("Invalid argument %d: %s", argIndex, e.getMessage()));
}
Coin p2shFee = Coin.valueOf(Common.getP2shFee(bitcoiny));
if (p2shFee.isZero())
return;
System.out.println(String.format("P2SH address: %s", p2shAddress));
System.out.println(String.format("Refund PKH: %s", refundAddress));
System.out.println(String.format("Redeem/refund amount: %s", amount.toPlainString()));
System.out.println(String.format("Redeem PKH: %s", redeemAddress));
System.out.println(String.format("Hash of secret: %s", HashCode.fromBytes(secretHash)));
System.out.println(String.format("Script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneOffset.UTC), lockTime));
System.out.println(String.format("Redeem/refund miner's fee: %s", bitcoiny.format(p2shFee)));
byte[] redeemScriptBytes = BitcoinyHTLC.buildScript(refundAddress.getHash(), lockTime, redeemAddress.getHash(), secretHash);
System.out.println(String.format("Raw script bytes: %s", HashCode.fromBytes(redeemScriptBytes)));
byte[] redeemScriptHash = Crypto.hash160(redeemScriptBytes);
Address derivedP2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash);
if (!derivedP2shAddress.equals(p2shAddress)) {
System.err.println(String.format("Derived P2SH address %s does not match given address %s", derivedP2shAddress, p2shAddress));
System.exit(2);
}
amount = amount.add(p2shFee);
// Check network's median block time
int medianBlockTime = Common.checkMedianBlockTime(bitcoiny, null);
if (medianBlockTime == 0)
return;
// Check P2SH is funded
Common.getBalance(bitcoiny, p2shAddress.toString());
// Grab all unspent outputs
Common.getUnspentOutputs(bitcoiny, p2shAddress.toString());
Common.determineHtlcStatus(bitcoiny, p2shAddress.toString(), amount.value);
}
}

View File

@ -0,0 +1,155 @@
package org.qortal.test.crosschain.apps;
import java.security.Security;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Collections;
import java.util.List;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionOutput;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
import org.qortal.crosschain.Bitcoiny;
import org.qortal.crosschain.BitcoinyHTLC;
import org.qortal.crosschain.ForeignBlockchainException;
import org.qortal.settings.Settings;
import com.google.common.hash.HashCode;
public abstract class Common {
public static void init() {
Security.insertProviderAt(new BouncyCastleProvider(), 0);
Security.insertProviderAt(new BouncyCastleJsseProvider(), 1);
Settings.fileInstance("settings-test.json");
}
public static long getP2shFee(Bitcoiny bitcoiny) {
long p2shFee;
try {
p2shFee = bitcoiny.getP2shFee(null);
} catch (ForeignBlockchainException e) {
System.err.println(String.format("Unable to determine P2SH fee: %s", e.getMessage()));
return 0;
}
return p2shFee;
}
public static int checkMedianBlockTime(Bitcoiny bitcoiny, Integer lockTime) {
int medianBlockTime;
try {
medianBlockTime = bitcoiny.getMedianBlockTime();
} catch (ForeignBlockchainException e) {
System.err.println(String.format("Unable to determine median block time: %s", e.getMessage()));
return 0;
}
System.out.println(String.format("Median block time: %s", LocalDateTime.ofInstant(Instant.ofEpochSecond(medianBlockTime), ZoneOffset.UTC)));
long now = System.currentTimeMillis();
if (now < medianBlockTime * 1000L) {
System.out.println(String.format("Too soon (%s) based on median block time %s",
LocalDateTime.ofInstant(Instant.ofEpochMilli(now), ZoneOffset.UTC),
LocalDateTime.ofInstant(Instant.ofEpochSecond(medianBlockTime), ZoneOffset.UTC)));
return 0;
}
if (lockTime != null && now < lockTime * 1000L) {
System.err.println(String.format("Too soon (%s) based on lockTime %s",
LocalDateTime.ofInstant(Instant.ofEpochMilli(now), ZoneOffset.UTC),
LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneOffset.UTC)));
return 0;
}
return medianBlockTime;
}
public static long getBalance(Bitcoiny bitcoiny, String address58) {
long balance;
try {
balance = bitcoiny.getConfirmedBalance(address58);
} catch (ForeignBlockchainException e) {
System.err.println(String.format("Unable to check address %s balance: %s", address58, e.getMessage()));
return 0;
}
System.out.println(String.format("Address %s balance: %s", address58, bitcoiny.format(balance)));
return balance;
}
public static List<TransactionOutput> getUnspentOutputs(Bitcoiny bitcoiny, String address58) {
List<TransactionOutput> unspentOutputs = Collections.emptyList();
try {
unspentOutputs = bitcoiny.getUnspentOutputs(address58);
} catch (ForeignBlockchainException e) {
System.err.println(String.format("Can't find unspent outputs for %s: %s", address58, e.getMessage()));
return unspentOutputs;
}
System.out.println(String.format("Found %d output%s for %s",
unspentOutputs.size(),
(unspentOutputs.size() != 1 ? "s" : ""),
address58));
for (TransactionOutput fundingOutput : unspentOutputs)
System.out.println(String.format("Output %s:%d amount %s",
HashCode.fromBytes(fundingOutput.getParentTransactionHash().getBytes()), fundingOutput.getIndex(),
bitcoiny.format(fundingOutput.getValue())));
if (unspentOutputs.isEmpty())
System.err.println(String.format("Can't use spent/unfunded %s", address58));
if (unspentOutputs.size() != 1)
System.err.println(String.format("Expecting only one unspent output?"));
return unspentOutputs;
}
public static BitcoinyHTLC.Status determineHtlcStatus(Bitcoiny bitcoiny, String address58, long minimumAmount) {
BitcoinyHTLC.Status htlcStatus = null;
try {
htlcStatus = BitcoinyHTLC.determineHtlcStatus(bitcoiny.getBlockchainProvider(), address58, minimumAmount);
System.out.println(String.format("HTLC status: %s", htlcStatus.name()));
} catch (ForeignBlockchainException e) {
System.err.println(String.format("Unable to determine HTLC status: %s", e.getMessage()));
}
return htlcStatus;
}
public static void broadcastTransaction(Bitcoiny bitcoiny, Transaction transaction) {
byte[] rawTransactionBytes = transaction.bitcoinSerialize();
System.out.println(String.format("%nRaw transaction bytes:%n%s%n", HashCode.fromBytes(rawTransactionBytes).toString()));
for (int countDown = 5; countDown >= 1; --countDown) {
System.out.print(String.format("\rBroadcasting transaction in %d second%s... use CTRL-C to abort ", countDown, (countDown != 1 ? "s" : "")));
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
System.exit(0);
}
}
System.out.println("Broadcasting transaction... ");
try {
bitcoiny.broadcastTransaction(transaction);
} catch (ForeignBlockchainException e) {
System.err.println(String.format("Failed to broadcast transaction: %s", e.getMessage()));
System.exit(1);
}
}
}

View File

@ -0,0 +1,78 @@
package org.qortal.test.crosschain.apps;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Transaction;
import org.qortal.crosschain.Bitcoin;
import org.qortal.crosschain.Bitcoiny;
import org.qortal.crosschain.Litecoin;
public class Pay {
private static void usage(String error) {
if (error != null)
System.err.println(error);
System.err.println(String.format("usage: Pay (-b | -l) <xprv58> <recipient> <LTC-amount>"));
System.err.println("where: -b means use Bitcoin, -l means use Litecoin");
System.err.println(String.format("example: Pay -l "
+ "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ \\\n"
+ "\tmsAfaDaJ8JiprxxFaAXEEPxKK3JaZCYpLv \\\n"
+ "\t0.00008642"));
System.exit(1);
}
public static void main(String[] args) {
if (args.length < 4 || args.length > 4)
usage(null);
Common.init();
Bitcoiny bitcoiny = null;
NetworkParameters params = null;
String xprv58 = null;
Address address = null;
Coin amount = null;
int argIndex = 0;
try {
switch (args[argIndex++]) {
case "-b":
bitcoiny = Bitcoin.getInstance();
break;
case "-l":
bitcoiny = Litecoin.getInstance();
break;
default:
usage("Only Bitcoin (-b) or Litecoin (-l) supported");
}
params = bitcoiny.getNetworkParameters();
xprv58 = args[argIndex++];
if (!bitcoiny.isValidXprv(xprv58))
usage("xprv invalid");
address = Address.fromString(params, args[argIndex++]);
amount = Coin.parseCoin(args[argIndex++]);
} catch (IllegalArgumentException e) {
usage(String.format("Invalid argument %d: %s", argIndex, e.getMessage()));
}
System.out.println(String.format("Address: %s", address));
System.out.println(String.format("Amount: %s", amount.toPlainString()));
Transaction transaction = bitcoiny.buildSpend(xprv58, address.toString(), amount.value);
if (transaction == null) {
System.err.println("Insufficent funds");
System.exit(1);
}
Common.broadcastTransaction(bitcoiny, transaction);
}
}

View File

@ -0,0 +1,164 @@
package org.qortal.test.crosschain.apps;
import java.util.Arrays;
import java.util.List;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.LegacyAddress;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionOutput;
import org.bitcoinj.script.Script.ScriptType;
import org.qortal.crosschain.Bitcoin;
import org.qortal.crosschain.Bitcoiny;
import org.qortal.crosschain.Litecoin;
import org.qortal.crosschain.BitcoinyHTLC;
import org.qortal.crypto.Crypto;
import com.google.common.hash.HashCode;
public class RedeemHTLC {
static {
// This must go before any calls to LogManager/Logger
System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
}
private static void usage(String error) {
if (error != null)
System.err.println(error);
System.err.println(String.format("usage: Redeem (-b | -l) <P2SH-address> <refund-P2PKH> <redeem-PRIVATE-key> <secret> <locktime> <output-address>"));
System.err.println("where: -b means use Bitcoin, -l means use Litecoin");
System.err.println(String.format("example: Redeem -l "
+ "2N4378NbEVGjmiUmoUD9g1vCY6kyx9tDUJ6 \\\n"
+ "\tmsAfaDaJ8JiprxxFaAXEEPxKK3JaZCYpLv \\\n"
+ "\tefdaed23c4bc85c8ccae40d774af3c2a10391c648b6420cdd83cd44c27fcb5955201c64e372d \\\n"
+ "\t5468697320737472696e672069732065786163746c7920333220627974657321 \\\n"
+ "\t1600184800 \\\n"
+ "\tmrBpZYYGYMwUa8tRjTiXfP1ySqNXszWN5h"));
System.exit(1);
}
public static void main(String[] args) {
if (args.length < 7 || args.length > 7)
usage(null);
Common.init();
Bitcoiny bitcoiny = null;
NetworkParameters params = null;
Address p2shAddress = null;
Address refundAddress = null;
byte[] redeemPrivateKey = null;
byte[] secret = null;
int lockTime = 0;
Address outputAddress = null;
int argIndex = 0;
try {
switch (args[argIndex++]) {
case "-b":
bitcoiny = Bitcoin.getInstance();
break;
case "-l":
bitcoiny = Litecoin.getInstance();
break;
default:
usage("Only Bitcoin (-b) or Litecoin (-l) supported");
}
params = bitcoiny.getNetworkParameters();
p2shAddress = Address.fromString(params, args[argIndex++]);
if (p2shAddress.getOutputScriptType() != ScriptType.P2SH)
usage("P2SH address invalid");
refundAddress = Address.fromString(params, args[argIndex++]);
if (refundAddress.getOutputScriptType() != ScriptType.P2PKH)
usage("Refund address must be in P2PKH form");
redeemPrivateKey = HashCode.fromString(args[argIndex++]).asBytes();
// Auto-trim
if (redeemPrivateKey.length >= 37 && redeemPrivateKey.length <= 38)
redeemPrivateKey = Arrays.copyOfRange(redeemPrivateKey, 1, 33);
if (redeemPrivateKey.length != 32)
usage("Redeem private key must be 32 bytes");
secret = HashCode.fromString(args[argIndex++]).asBytes();
if (secret.length == 0)
usage("Invalid secret bytes");
lockTime = Integer.parseInt(args[argIndex++]);
outputAddress = Address.fromString(params, args[argIndex++]);
if (outputAddress.getOutputScriptType() != ScriptType.P2PKH)
usage("Output address invalid");
} catch (IllegalArgumentException e) {
usage(String.format("Invalid argument %d: %s", argIndex, e.getMessage()));
}
Coin p2shFee = Coin.valueOf(Common.getP2shFee(bitcoiny));
if (p2shFee.isZero())
return;
System.out.println(String.format("Attempting to redeem HTLC %s to %s", p2shAddress, outputAddress));
byte[] secretHash = Crypto.hash160(secret);
ECKey redeemKey = ECKey.fromPrivate(redeemPrivateKey);
Address redeemAddress = Address.fromKey(params, redeemKey, ScriptType.P2PKH);
byte[] redeemScriptBytes = BitcoinyHTLC.buildScript(refundAddress.getHash(), lockTime, redeemAddress.getHash(), secretHash);
byte[] redeemScriptHash = Crypto.hash160(redeemScriptBytes);
Address derivedP2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash);
if (!derivedP2shAddress.equals(p2shAddress)) {
System.err.println(String.format("Raw script bytes: %s", HashCode.fromBytes(redeemScriptBytes)));
System.err.println(String.format("Derived P2SH address %s does not match given address %s", derivedP2shAddress, p2shAddress));
System.exit(2);
return;
}
// Actual live processing...
int medianBlockTime = Common.checkMedianBlockTime(bitcoiny, null);
if (medianBlockTime == 0)
return;
// Check P2SH is funded
long p2shBalance = Common.getBalance(bitcoiny, p2shAddress.toString());
if (p2shBalance == 0)
return;
// Grab all unspent outputs
List<TransactionOutput> unspentOutputs = Common.getUnspentOutputs(bitcoiny, p2shAddress.toString());
if (unspentOutputs.isEmpty())
return;
Coin redeemAmount = Coin.valueOf(p2shBalance).subtract(p2shFee);
BitcoinyHTLC.Status htlcStatus = Common.determineHtlcStatus(bitcoiny, p2shAddress.toString(), redeemAmount.value);
if (htlcStatus == null)
return;
if (htlcStatus != BitcoinyHTLC.Status.FUNDED) {
System.err.println(String.format("Expecting %s HTLC status, but actual status is %s", "FUNDED", htlcStatus.name()));
System.exit(2);
return;
}
System.out.println(String.format("Spending %s of outputs, with %s as mining fee", bitcoiny.format(redeemAmount), bitcoiny.format(p2shFee)));
Transaction redeemTransaction = BitcoinyHTLC.buildRedeemTransaction(bitcoiny.getNetworkParameters(), redeemAmount, redeemKey,
unspentOutputs, redeemScriptBytes, secret, outputAddress.getHash());
Common.broadcastTransaction(bitcoiny, redeemTransaction);
}
}

View File

@ -0,0 +1,161 @@
package org.qortal.test.crosschain.apps;
import java.util.Arrays;
import java.util.List;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.LegacyAddress;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionOutput;
import org.bitcoinj.script.Script.ScriptType;
import org.qortal.crosschain.Litecoin;
import org.qortal.crosschain.Bitcoin;
import org.qortal.crosschain.Bitcoiny;
import org.qortal.crosschain.BitcoinyHTLC;
import org.qortal.crypto.Crypto;
import com.google.common.hash.HashCode;
public class RefundHTLC {
static {
// This must go before any calls to LogManager/Logger
System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
}
private static void usage(String error) {
if (error != null)
System.err.println(error);
System.err.println(String.format("usage: RefundHTLC (-b | -l) <P2SH-address> <refund-PRIVATE-KEY> <redeem-P2PKH> <HASH160-of-secret> <locktime> <output-address>"));
System.err.println("where: -b means use Bitcoin, -l means use Litecoin");
System.err.println(String.format("example: RefundHTLC -l "
+ "2N4378NbEVGjmiUmoUD9g1vCY6kyx9tDUJ6 \\\n"
+ "\tef8f31b49c31b4a140aebcd9605fded88cc2dad0844c4b984f9191a5a416f72d3801e16447b0 \\\n"
+ "\tmrBpZYYGYMwUa8tRjTiXfP1ySqNXszWN5h \\\n"
+ "\tdaf59884b4d1aec8c1b17102530909ee43c0151a \\\n"
+ "\t1600184800 \\\n"
+ "\tmoJtbbhs7T4Z5hmBH2iyKhGrCWBzQWS2CL"));
System.exit(1);
}
public static void main(String[] args) {
if (args.length < 7 || args.length > 7)
usage(null);
Common.init();
Bitcoiny bitcoiny = null;
NetworkParameters params = null;
Address p2shAddress = null;
byte[] refundPrivateKey = null;
Address redeemAddress = null;
byte[] secretHash = null;
int lockTime = 0;
Address outputAddress = null;
int argIndex = 0;
try {
switch (args[argIndex++]) {
case "-b":
bitcoiny = Bitcoin.getInstance();
break;
case "-l":
bitcoiny = Litecoin.getInstance();
break;
default:
usage("Only Bitcoin (-b) or Litecoin (-l) supported");
}
params = bitcoiny.getNetworkParameters();
p2shAddress = Address.fromString(params, args[argIndex++]);
if (p2shAddress.getOutputScriptType() != ScriptType.P2SH)
usage("P2SH address invalid");
refundPrivateKey = HashCode.fromString(args[argIndex++]).asBytes();
// Auto-trim
if (refundPrivateKey.length >= 37 && refundPrivateKey.length <= 38)
refundPrivateKey = Arrays.copyOfRange(refundPrivateKey, 1, 33);
if (refundPrivateKey.length != 32)
usage("Refund private key must be 32 bytes");
redeemAddress = Address.fromString(params, args[argIndex++]);
if (redeemAddress.getOutputScriptType() != ScriptType.P2PKH)
usage("Redeem address must be in P2PKH form");
secretHash = HashCode.fromString(args[argIndex++]).asBytes();
if (secretHash.length != 20)
usage("HASH160 of secret must be 20 bytes");
lockTime = Integer.parseInt(args[argIndex++]);
outputAddress = Address.fromString(params, args[argIndex++]);
if (outputAddress.getOutputScriptType() != ScriptType.P2PKH)
usage("Output address invalid");
} catch (IllegalArgumentException e) {
usage(String.format("Invalid argument %d: %s", argIndex, e.getMessage()));
}
Coin p2shFee = Coin.valueOf(Common.getP2shFee(bitcoiny));
if (p2shFee.isZero())
return;
System.out.println(String.format("Attempting to refund HTLC %s to %s", p2shAddress, outputAddress));
ECKey refundKey = ECKey.fromPrivate(refundPrivateKey);
Address refundAddress = Address.fromKey(params, refundKey, ScriptType.P2PKH);
byte[] redeemScriptBytes = BitcoinyHTLC.buildScript(refundAddress.getHash(), lockTime, redeemAddress.getHash(), secretHash);
byte[] redeemScriptHash = Crypto.hash160(redeemScriptBytes);
Address derivedP2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash);
if (!derivedP2shAddress.equals(p2shAddress)) {
System.err.println(String.format("Raw script bytes: %s", HashCode.fromBytes(redeemScriptBytes)));
System.err.println(String.format("Derived P2SH address %s does not match given address %s", derivedP2shAddress, p2shAddress));
System.exit(2);
}
// Actual live processing...
int medianBlockTime = Common.checkMedianBlockTime(bitcoiny, lockTime);
if (medianBlockTime == 0)
return;
// Check P2SH is funded
long p2shBalance = Common.getBalance(bitcoiny, p2shAddress.toString());
if (p2shBalance == 0)
return;
// Grab all unspent outputs
List<TransactionOutput> unspentOutputs = Common.getUnspentOutputs(bitcoiny, p2shAddress.toString());
if (unspentOutputs.isEmpty())
return;
Coin refundAmount = Coin.valueOf(p2shBalance).subtract(p2shFee);
BitcoinyHTLC.Status htlcStatus = Common.determineHtlcStatus(bitcoiny, p2shAddress.toString(), refundAmount.value);
if (htlcStatus == null)
return;
if (htlcStatus != BitcoinyHTLC.Status.FUNDED) {
System.err.println(String.format("Expecting %s HTLC status, but actual status is %s", "FUNDED", htlcStatus.name()));
System.exit(2);
return;
}
System.out.println(String.format("Spending %s of outputs, with %s as mining fee", bitcoiny.format(refundAmount), bitcoiny.format(p2shFee)));
Transaction refundTransaction = BitcoinyHTLC.buildRefundTransaction(bitcoiny.getNetworkParameters(), refundAmount, refundKey,
unspentOutputs, redeemScriptBytes, lockTime, outputAddress.getHash());
Common.broadcastTransaction(bitcoiny, refundTransaction);
}
}

View File

@ -1,112 +0,0 @@
package org.qortal.test.crosschain.bitcoinv1;
import java.security.Security;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.script.Script.ScriptType;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
import org.qortal.crosschain.Bitcoin;
import org.qortal.crosschain.ForeignBlockchainException;
import org.qortal.crosschain.BitcoinyHTLC;
import org.qortal.settings.Settings;
import com.google.common.hash.HashCode;
public class BuildHTLC {
private static void usage(String error) {
if (error != null)
System.err.println(error);
System.err.println(String.format("usage: BuildHTLC <refund-P2PKH> <BTC-amount> <redeem-P2PKH> <HASH160-of-secret> <locktime>"));
System.err.println(String.format("example: BuildHTLC "
+ "mrTDPdM15cFWJC4g223BXX5snicfVJBx6M \\\n"
+ "\t0.00008642 \\\n"
+ "\tn2N5VKrzq39nmuefZwp3wBiF4icdXX2B6o \\\n"
+ "\tdaf59884b4d1aec8c1b17102530909ee43c0151a \\\n"
+ "\t1585920000"));
System.exit(1);
}
public static void main(String[] args) {
if (args.length < 5 || args.length > 5)
usage(null);
Security.insertProviderAt(new BouncyCastleProvider(), 0);
Security.insertProviderAt(new BouncyCastleJsseProvider(), 1);
Settings.fileInstance("settings-test.json");
Bitcoin bitcoin = Bitcoin.getInstance();
NetworkParameters params = bitcoin.getNetworkParameters();
Address refundBitcoinAddress = null;
Coin bitcoinAmount = null;
Address redeemBitcoinAddress = null;
byte[] secretHash = null;
int lockTime = 0;
int argIndex = 0;
try {
refundBitcoinAddress = Address.fromString(params, args[argIndex++]);
if (refundBitcoinAddress.getOutputScriptType() != ScriptType.P2PKH)
usage("Refund BTC address must be in P2PKH form");
bitcoinAmount = Coin.parseCoin(args[argIndex++]);
redeemBitcoinAddress = Address.fromString(params, args[argIndex++]);
if (redeemBitcoinAddress.getOutputScriptType() != ScriptType.P2PKH)
usage("Redeem BTC address must be in P2PKH form");
secretHash = HashCode.fromString(args[argIndex++]).asBytes();
if (secretHash.length != 20)
usage("Hash of secret must be 20 bytes");
lockTime = Integer.parseInt(args[argIndex++]);
int refundTimeoutDelay = lockTime - (int) (System.currentTimeMillis() / 1000L);
if (refundTimeoutDelay < 600 || refundTimeoutDelay > 30 * 24 * 60 * 60)
usage("Locktime (seconds) should be at between 10 minutes and 1 month from now");
} catch (IllegalArgumentException e) {
usage(String.format("Invalid argument %d: %s", argIndex, e.getMessage()));
}
Coin p2shFee;
try {
p2shFee = Coin.valueOf(bitcoin.getP2shFee(null));
} catch (ForeignBlockchainException e) {
throw new RuntimeException(e.getMessage());
}
System.out.println("Confirm the following is correct based on the info you've given:");
System.out.println(String.format("Refund Bitcoin address: %s", refundBitcoinAddress));
System.out.println(String.format("Bitcoin redeem amount: %s", bitcoinAmount.toPlainString()));
System.out.println(String.format("Redeem Bitcoin address: %s", redeemBitcoinAddress));
System.out.println(String.format("Redeem miner's fee: %s", bitcoin.format(p2shFee)));
System.out.println(String.format("Redeem script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneOffset.UTC), lockTime));
System.out.println(String.format("Hash of secret: %s", HashCode.fromBytes(secretHash)));
byte[] redeemScriptBytes = BitcoinyHTLC.buildScript(refundBitcoinAddress.getHash(), lockTime, redeemBitcoinAddress.getHash(), secretHash);
System.out.println(String.format("Redeem script: %s", HashCode.fromBytes(redeemScriptBytes)));
String p2shAddress = bitcoin.deriveP2shAddress(redeemScriptBytes);
System.out.println(String.format("P2SH address: %s", p2shAddress));
bitcoinAmount = bitcoinAmount.add(p2shFee);
// Fund P2SH
System.out.println(String.format("\nYou need to fund %s with %s (includes redeem/refund fee of %s)",
p2shAddress, bitcoin.format(bitcoinAmount), bitcoin.format(p2shFee)));
System.out.println("Once this is done, responder should run Respond to check P2SH funding and create AT");
}
}

View File

@ -1,161 +0,0 @@
package org.qortal.test.crosschain.bitcoinv1;
import java.security.Security;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.List;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.LegacyAddress;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.TransactionOutput;
import org.bitcoinj.script.Script.ScriptType;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
import org.qortal.crosschain.Bitcoin;
import org.qortal.crosschain.ForeignBlockchainException;
import org.qortal.crosschain.BitcoinyHTLC;
import org.qortal.crypto.Crypto;
import org.qortal.settings.Settings;
import com.google.common.hash.HashCode;
public class CheckHTLC {
private static void usage(String error) {
if (error != null)
System.err.println(error);
System.err.println(String.format("usage: CheckHTLC <P2SH-address> <refund-P2PKH> <BTC-amount> <redeem-P2PKH> <HASH160-of-secret> <locktime>"));
System.err.println(String.format("example: CheckP2SH "
+ "2NEZboTLhBDPPQciR7sExBhy3TsDi7wV3Cv \\\n"
+ "mrTDPdM15cFWJC4g223BXX5snicfVJBx6M \\\n"
+ "\t0.00008642 \\\n"
+ "\tn2N5VKrzq39nmuefZwp3wBiF4icdXX2B6o \\\n"
+ "\tdaf59884b4d1aec8c1b17102530909ee43c0151a \\\n"
+ "\t1585920000"));
System.exit(1);
}
public static void main(String[] args) {
if (args.length < 6 || args.length > 6)
usage(null);
Security.insertProviderAt(new BouncyCastleProvider(), 0);
Security.insertProviderAt(new BouncyCastleJsseProvider(), 1);
Settings.fileInstance("settings-test.json");
Bitcoin bitcoin = Bitcoin.getInstance();
NetworkParameters params = bitcoin.getNetworkParameters();
Address p2shAddress = null;
Address refundBitcoinAddress = null;
Coin bitcoinAmount = null;
Address redeemBitcoinAddress = null;
byte[] secretHash = null;
int lockTime = 0;
int argIndex = 0;
try {
p2shAddress = Address.fromString(params, args[argIndex++]);
if (p2shAddress.getOutputScriptType() != ScriptType.P2SH)
usage("P2SH address invalid");
refundBitcoinAddress = Address.fromString(params, args[argIndex++]);
if (refundBitcoinAddress.getOutputScriptType() != ScriptType.P2PKH)
usage("Refund BTC address must be in P2PKH form");
bitcoinAmount = Coin.parseCoin(args[argIndex++]);
redeemBitcoinAddress = Address.fromString(params, args[argIndex++]);
if (redeemBitcoinAddress.getOutputScriptType() != ScriptType.P2PKH)
usage("Redeem BTC address must be in P2PKH form");
secretHash = HashCode.fromString(args[argIndex++]).asBytes();
if (secretHash.length != 20)
usage("Hash of secret must be 20 bytes");
lockTime = Integer.parseInt(args[argIndex++]);
int refundTimeoutDelay = lockTime - (int) (System.currentTimeMillis() / 1000L);
if (refundTimeoutDelay < 600 || refundTimeoutDelay > 7 * 24 * 60 * 60)
usage("Locktime (seconds) should be at between 10 minutes and 1 week from now");
} catch (IllegalArgumentException e) {
usage(String.format("Invalid argument %d: %s", argIndex, e.getMessage()));
}
Coin p2shFee;
try {
p2shFee = Coin.valueOf(bitcoin.getP2shFee(null));
} catch (ForeignBlockchainException e) {
throw new RuntimeException(e.getMessage());
}
try {
System.out.println("Confirm the following is correct based on the info you've given:");
System.out.println(String.format("Refund Bitcoin address: %s", redeemBitcoinAddress));
System.out.println(String.format("Bitcoin redeem amount: %s", bitcoinAmount.toPlainString()));
System.out.println(String.format("Redeem Bitcoin address: %s", refundBitcoinAddress));
System.out.println(String.format("Redeem miner's fee: %s", bitcoin.format(p2shFee)));
System.out.println(String.format("Redeem script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneOffset.UTC), lockTime));
System.out.println(String.format("Hash of secret: %s", HashCode.fromBytes(secretHash)));
System.out.println(String.format("P2SH address: %s", p2shAddress));
byte[] redeemScriptBytes = BitcoinyHTLC.buildScript(refundBitcoinAddress.getHash(), lockTime, redeemBitcoinAddress.getHash(), secretHash);
System.out.println(String.format("Redeem script: %s", HashCode.fromBytes(redeemScriptBytes)));
byte[] redeemScriptHash = Crypto.hash160(redeemScriptBytes);
Address derivedP2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash);
if (!derivedP2shAddress.equals(p2shAddress)) {
System.err.println(String.format("Derived P2SH address %s does not match given address %s", derivedP2shAddress, p2shAddress));
System.exit(2);
}
bitcoinAmount = bitcoinAmount.add(p2shFee);
long medianBlockTime = bitcoin.getMedianBlockTime();
System.out.println(String.format("Median block time: %s", LocalDateTime.ofInstant(Instant.ofEpochSecond(medianBlockTime), ZoneOffset.UTC)));
long now = System.currentTimeMillis();
if (now < medianBlockTime * 1000L)
System.out.println(String.format("Too soon (%s) to redeem based on median block time %s", LocalDateTime.ofInstant(Instant.ofEpochMilli(now), ZoneOffset.UTC), LocalDateTime.ofInstant(Instant.ofEpochSecond(medianBlockTime), ZoneOffset.UTC)));
// Check P2SH is funded
long p2shBalance = bitcoin.getConfirmedBalance(p2shAddress.toString());
System.out.println(String.format("P2SH address %s balance: %s", p2shAddress, bitcoin.format(p2shBalance)));
// Grab all P2SH funding transactions (just in case there are more than one)
List<TransactionOutput> fundingOutputs = bitcoin.getUnspentOutputs(p2shAddress.toString());
if (fundingOutputs == null) {
System.err.println(String.format("Can't find outputs for P2SH"));
System.exit(2);
}
System.out.println(String.format("Found %d output%s for P2SH", fundingOutputs.size(), (fundingOutputs.size() != 1 ? "s" : "")));
for (TransactionOutput fundingOutput : fundingOutputs)
System.out.println(String.format("Output %s:%d amount %s", HashCode.fromBytes(fundingOutput.getParentTransactionHash().getBytes()), fundingOutput.getIndex(), bitcoin.format(fundingOutput.getValue())));
if (fundingOutputs.isEmpty()) {
System.err.println(String.format("Can't redeem spent/unfunded P2SH"));
System.exit(2);
}
if (fundingOutputs.size() != 1) {
System.err.println(String.format("Expecting only one unspent output for P2SH"));
System.exit(2);
}
} catch (ForeignBlockchainException e) {
System.err.println("Bitcoin issue: " + e.getMessage());
}
}
}

View File

@ -1,9 +0,0 @@
package org.qortal.test.crosschain.bitcoinv1;
import org.bitcoinj.core.Coin;
public abstract class Common {
public static final Coin DEFAULT_BTC_FEE = Coin.parseCoin("0.00001000");
}

View File

@ -1,203 +0,0 @@
package org.qortal.test.crosschain.bitcoinv1;
import java.security.Security;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Arrays;
import java.util.List;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.LegacyAddress;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionOutput;
import org.bitcoinj.script.Script.ScriptType;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
import org.qortal.crosschain.Bitcoin;
import org.qortal.crosschain.ForeignBlockchainException;
import org.qortal.crosschain.BitcoinyHTLC;
import org.qortal.crypto.Crypto;
import org.qortal.settings.Settings;
import com.google.common.hash.HashCode;
public class Redeem {
static {
// This must go before any calls to LogManager/Logger
System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
}
private static void usage(String error) {
if (error != null)
System.err.println(error);
System.err.println(String.format("usage: Redeem <P2SH-address> <refund-P2PKH> <redeem-PRIVATE-key> <secret> <locktime> "));
System.err.println(String.format("example: Redeem "
+ "2NEZboTLhBDPPQciR7sExBhy3TsDi7wV3Cv \\\n"
+ "\tmrTDPdM15cFWJC4g223BXX5snicfVJBx6M \\\n"
+ "\tec199a4abc9d3bf024349e397535dfee9d287e174aeabae94237eb03a0118c03 \\\n"
+ "\t5468697320737472696e672069732065786163746c7920333220627974657321 \\\n"
+ "\t1585920000"));
System.exit(1);
}
public static void main(String[] args) {
if (args.length < 5 || args.length > 5)
usage(null);
Security.insertProviderAt(new BouncyCastleProvider(), 0);
Security.insertProviderAt(new BouncyCastleJsseProvider(), 1);
Settings.fileInstance("settings-test.json");
Bitcoin bitcoin = Bitcoin.getInstance();
NetworkParameters params = bitcoin.getNetworkParameters();
Address p2shAddress = null;
Address refundBitcoinAddress = null;
byte[] redeemPrivateKey = null;
byte[] secret = null;
int lockTime = 0;
int argIndex = 0;
try {
p2shAddress = Address.fromString(params, args[argIndex++]);
if (p2shAddress.getOutputScriptType() != ScriptType.P2SH)
usage("P2SH address invalid");
refundBitcoinAddress = Address.fromString(params, args[argIndex++]);
if (refundBitcoinAddress.getOutputScriptType() != ScriptType.P2PKH)
usage("Refund BTC address must be in P2PKH form");
redeemPrivateKey = HashCode.fromString(args[argIndex++]).asBytes();
// Auto-trim
if (redeemPrivateKey.length >= 37 && redeemPrivateKey.length <= 38)
redeemPrivateKey = Arrays.copyOfRange(redeemPrivateKey, 1, 33);
if (redeemPrivateKey.length != 32)
usage("Redeem private key must be 32 bytes");
secret = HashCode.fromString(args[argIndex++]).asBytes();
if (secret.length == 0)
usage("Invalid secret bytes");
lockTime = Integer.parseInt(args[argIndex++]);
} catch (IllegalArgumentException e) {
usage(String.format("Invalid argument %d: %s", argIndex, e.getMessage()));
}
Coin p2shFee;
try {
p2shFee = Coin.valueOf(bitcoin.getP2shFee(null));
} catch (ForeignBlockchainException e) {
throw new RuntimeException(e.getMessage());
}
try {
System.out.println("Confirm the following is correct based on the info you've given:");
System.out.println(String.format("Redeem PRIVATE key: %s", HashCode.fromBytes(redeemPrivateKey)));
System.out.println(String.format("Redeem miner's fee: %s", bitcoin.format(p2shFee)));
System.out.println(String.format("Redeem script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneOffset.UTC), lockTime));
// New/derived info
byte[] secretHash = Crypto.hash160(secret);
System.out.println(String.format("HASH160 of secret: %s", HashCode.fromBytes(secretHash)));
ECKey redeemKey = ECKey.fromPrivate(redeemPrivateKey);
Address redeemAddress = Address.fromKey(params, redeemKey, ScriptType.P2PKH);
System.out.println(String.format("Redeem recipient (PKH): %s (%s)", redeemAddress, HashCode.fromBytes(redeemAddress.getHash())));
System.out.println(String.format("P2SH address: %s", p2shAddress));
byte[] redeemScriptBytes = BitcoinyHTLC.buildScript(refundBitcoinAddress.getHash(), lockTime, redeemAddress.getHash(), secretHash);
System.out.println(String.format("Redeem script: %s", HashCode.fromBytes(redeemScriptBytes)));
byte[] redeemScriptHash = Crypto.hash160(redeemScriptBytes);
Address derivedP2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash);
if (!derivedP2shAddress.equals(p2shAddress)) {
System.err.println(String.format("Derived P2SH address %s does not match given address %s", derivedP2shAddress, p2shAddress));
System.exit(2);
}
// Some checks
System.out.println("\nProcessing:");
long medianBlockTime;
try {
medianBlockTime = bitcoin.getMedianBlockTime();
} catch (ForeignBlockchainException e1) {
System.err.println("Unable to determine median block time");
System.exit(2);
return;
}
System.out.println(String.format("Median block time: %s", LocalDateTime.ofInstant(Instant.ofEpochSecond(medianBlockTime), ZoneOffset.UTC)));
long now = System.currentTimeMillis();
if (now < medianBlockTime * 1000L) {
System.err.println(String.format("Too soon (%s) to redeem based on median block time %s", LocalDateTime.ofInstant(Instant.ofEpochMilli(now), ZoneOffset.UTC), LocalDateTime.ofInstant(Instant.ofEpochSecond(medianBlockTime), ZoneOffset.UTC)));
System.exit(2);
}
// Check P2SH is funded
long p2shBalance;
try {
p2shBalance = bitcoin.getConfirmedBalance(p2shAddress.toString());
} catch (ForeignBlockchainException e) {
System.err.println(String.format("Unable to check P2SH address %s balance", p2shAddress));
System.exit(2);
return;
}
System.out.println(String.format("P2SH address %s balance: %s", p2shAddress, bitcoin.format(p2shBalance)));
// Grab all P2SH funding transactions (just in case there are more than one)
List<TransactionOutput> fundingOutputs;
try {
fundingOutputs = bitcoin.getUnspentOutputs(p2shAddress.toString());
} catch (ForeignBlockchainException e) {
System.err.println(String.format("Can't find outputs for P2SH"));
System.exit(2);
return;
}
System.out.println(String.format("Found %d output%s for P2SH", fundingOutputs.size(), (fundingOutputs.size() != 1 ? "s" : "")));
for (TransactionOutput fundingOutput : fundingOutputs)
System.out.println(String.format("Output %s:%d amount %s", HashCode.fromBytes(fundingOutput.getParentTransactionHash().getBytes()), fundingOutput.getIndex(), bitcoin.format(fundingOutput.getValue())));
if (fundingOutputs.isEmpty()) {
System.err.println(String.format("Can't redeem spent/unfunded P2SH"));
System.exit(2);
}
if (fundingOutputs.size() != 1) {
System.err.println(String.format("Expecting only one unspent output for P2SH"));
// No longer fatal
}
for (TransactionOutput fundingOutput : fundingOutputs)
System.out.println(String.format("Using output %s:%d for redeem", HashCode.fromBytes(fundingOutput.getParentTransactionHash().getBytes()), fundingOutput.getIndex()));
Coin redeemAmount = Coin.valueOf(p2shBalance).subtract(p2shFee);
System.out.println(String.format("Spending %s of output, with %s as mining fee", bitcoin.format(redeemAmount), bitcoin.format(p2shFee)));
Transaction redeemTransaction = BitcoinyHTLC.buildRedeemTransaction(bitcoin.getNetworkParameters(), redeemAmount, redeemKey,
fundingOutputs, redeemScriptBytes, secret, redeemAddress.getHash());
byte[] redeemBytes = redeemTransaction.bitcoinSerialize();
System.out.println(String.format("\nLoad this transaction into your wallet and broadcast:\n%s\n", HashCode.fromBytes(redeemBytes).toString()));
} catch (NumberFormatException e) {
usage(String.format("Number format exception: %s", e.getMessage()));
}
}
}

View File

@ -1,207 +0,0 @@
package org.qortal.test.crosschain.bitcoinv1;
import java.security.Security;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Arrays;
import java.util.List;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.LegacyAddress;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionOutput;
import org.bitcoinj.script.Script.ScriptType;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
import org.qortal.crosschain.Bitcoin;
import org.qortal.crosschain.ForeignBlockchainException;
import org.qortal.crosschain.BitcoinyHTLC;
import org.qortal.crypto.Crypto;
import org.qortal.settings.Settings;
import com.google.common.hash.HashCode;
public class Refund {
static {
// This must go before any calls to LogManager/Logger
System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
}
private static void usage(String error) {
if (error != null)
System.err.println(error);
System.err.println(String.format("usage: Refund <P2SH-address> <refund-PRIVATE-KEY> <redeem-P2PKH> <HASH160-of-secret> <locktime>"));
System.err.println(String.format("example: Refund "
+ "2NEZboTLhBDPPQciR7sExBhy3TsDi7wV3Cv \\\n"
+ "\tef027fb5828c5e201eaf6de4cd3b0b340d16a191ef848cd691f35ef8f727358c9c01b576fb7e \\\n"
+ "\tn2N5VKrzq39nmuefZwp3wBiF4icdXX2B6o \\\n"
+ "\tdaf59884b4d1aec8c1b17102530909ee43c0151a \\\n"
+ "\t1585920000"));
System.exit(1);
}
public static void main(String[] args) {
if (args.length < 5 || args.length > 5)
usage(null);
Security.insertProviderAt(new BouncyCastleProvider(), 0);
Security.insertProviderAt(new BouncyCastleJsseProvider(), 1);
Settings.fileInstance("settings-test.json");
Bitcoin bitcoin = Bitcoin.getInstance();
NetworkParameters params = bitcoin.getNetworkParameters();
Address p2shAddress = null;
byte[] refundPrivateKey = null;
Address redeemBitcoinAddress = null;
byte[] secretHash = null;
int lockTime = 0;
int argIndex = 0;
try {
p2shAddress = Address.fromString(params, args[argIndex++]);
if (p2shAddress.getOutputScriptType() != ScriptType.P2SH)
usage("P2SH address invalid");
refundPrivateKey = HashCode.fromString(args[argIndex++]).asBytes();
// Auto-trim
if (refundPrivateKey.length >= 37 && refundPrivateKey.length <= 38)
refundPrivateKey = Arrays.copyOfRange(refundPrivateKey, 1, 33);
if (refundPrivateKey.length != 32)
usage("Refund private key must be 32 bytes");
redeemBitcoinAddress = Address.fromString(params, args[argIndex++]);
if (redeemBitcoinAddress.getOutputScriptType() != ScriptType.P2PKH)
usage("Their BTC address must be in P2PKH form");
secretHash = HashCode.fromString(args[argIndex++]).asBytes();
if (secretHash.length != 20)
usage("HASH160 of secret must be 20 bytes");
lockTime = Integer.parseInt(args[argIndex++]);
} catch (IllegalArgumentException e) {
usage(String.format("Invalid argument %d: %s", argIndex, e.getMessage()));
}
Coin p2shFee;
try {
p2shFee = Coin.valueOf(bitcoin.getP2shFee(null));
} catch (ForeignBlockchainException e) {
throw new RuntimeException(e.getMessage());
}
try {
System.out.println("Confirm the following is correct based on the info you've given:");
System.out.println(String.format("Refund PRIVATE key: %s", HashCode.fromBytes(refundPrivateKey)));
System.out.println(String.format("Redeem Bitcoin address: %s", redeemBitcoinAddress));
System.out.println(String.format("Redeem script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneOffset.UTC), lockTime));
System.out.println(String.format("P2SH address: %s", p2shAddress));
System.out.println(String.format("Refund miner's fee: %s", bitcoin.format(p2shFee)));
// New/derived info
System.out.println("\nCHECKING info from other party:");
ECKey refundKey = ECKey.fromPrivate(refundPrivateKey);
Address refundAddress = Address.fromKey(params, refundKey, ScriptType.P2PKH);
System.out.println(String.format("Refund recipient (PKH): %s (%s)", refundAddress, HashCode.fromBytes(refundAddress.getHash())));
byte[] redeemScriptBytes = BitcoinyHTLC.buildScript(refundAddress.getHash(), lockTime, redeemBitcoinAddress.getHash(), secretHash);
System.out.println(String.format("Redeem script: %s", HashCode.fromBytes(redeemScriptBytes)));
byte[] redeemScriptHash = Crypto.hash160(redeemScriptBytes);
Address derivedP2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash);
if (!derivedP2shAddress.equals(p2shAddress)) {
System.err.println(String.format("Derived P2SH address %s does not match given address %s", derivedP2shAddress, p2shAddress));
System.exit(2);
}
// Some checks
System.out.println("\nProcessing:");
long medianBlockTime;
try {
medianBlockTime = bitcoin.getMedianBlockTime();
} catch (ForeignBlockchainException e) {
System.err.println("Unable to determine median block time");
System.exit(2);
return;
}
System.out.println(String.format("Median block time: %s", LocalDateTime.ofInstant(Instant.ofEpochSecond(medianBlockTime), ZoneOffset.UTC)));
long now = System.currentTimeMillis();
if (now < medianBlockTime * 1000L) {
System.err.println(String.format("Too soon (%s) to refund based on median block time %s", LocalDateTime.ofInstant(Instant.ofEpochMilli(now), ZoneOffset.UTC), LocalDateTime.ofInstant(Instant.ofEpochSecond(medianBlockTime), ZoneOffset.UTC)));
System.exit(2);
}
if (now < lockTime * 1000L) {
System.err.println(String.format("Too soon (%s) to refund based on lockTime %s", LocalDateTime.ofInstant(Instant.ofEpochMilli(now), ZoneOffset.UTC), LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneOffset.UTC)));
System.exit(2);
}
// Check P2SH is funded
long p2shBalance;
try {
p2shBalance = bitcoin.getConfirmedBalance(p2shAddress.toString());
} catch (ForeignBlockchainException e) {
System.err.println(String.format("Unable to check P2SH address %s balance", p2shAddress));
System.exit(2);
return;
}
System.out.println(String.format("P2SH address %s balance: %s", p2shAddress, bitcoin.format(p2shBalance)));
// Grab all P2SH funding transactions (just in case there are more than one)
List<TransactionOutput> fundingOutputs;
try {
fundingOutputs = bitcoin.getUnspentOutputs(p2shAddress.toString());
} catch (ForeignBlockchainException e) {
System.err.println(String.format("Can't find outputs for P2SH"));
System.exit(2);
return;
}
System.out.println(String.format("Found %d output%s for P2SH", fundingOutputs.size(), (fundingOutputs.size() != 1 ? "s" : "")));
for (TransactionOutput fundingOutput : fundingOutputs)
System.out.println(String.format("Output %s:%d amount %s", HashCode.fromBytes(fundingOutput.getParentTransactionHash().getBytes()), fundingOutput.getIndex(), bitcoin.format(fundingOutput.getValue())));
if (fundingOutputs.isEmpty()) {
System.err.println(String.format("Can't refund spent/unfunded P2SH"));
System.exit(2);
}
if (fundingOutputs.size() != 1) {
System.err.println(String.format("Expecting only one unspent output for P2SH"));
// No longer fatal
}
for (TransactionOutput fundingOutput : fundingOutputs)
System.out.println(String.format("Using output %s:%d for redeem", HashCode.fromBytes(fundingOutput.getParentTransactionHash().getBytes()), fundingOutput.getIndex()));
Coin refundAmount = Coin.valueOf(p2shBalance).subtract(p2shFee);
System.out.println(String.format("Spending %s of output, with %s as mining fee", bitcoin.format(refundAmount), bitcoin.format(p2shFee)));
Transaction redeemTransaction = BitcoinyHTLC.buildRefundTransaction(bitcoin.getNetworkParameters(), refundAmount, refundKey,
fundingOutputs, redeemScriptBytes, lockTime, refundKey.getPubKeyHash());
byte[] redeemBytes = redeemTransaction.bitcoinSerialize();
System.out.println(String.format("\nLoad this transaction into your wallet and broadcast:\n%s\n", HashCode.fromBytes(redeemBytes).toString()));
} catch (NumberFormatException e) {
usage(String.format("Number format exception: %s", e.getMessage()));
}
}
}

View File

@ -1,112 +0,0 @@
package org.qortal.test.crosschain.litecoinv1;
import java.security.Security;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.script.Script.ScriptType;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
import org.qortal.crosschain.ForeignBlockchainException;
import org.qortal.crosschain.Litecoin;
import org.qortal.crosschain.BitcoinyHTLC;
import org.qortal.settings.Settings;
import com.google.common.hash.HashCode;
public class BuildHTLC {
private static void usage(String error) {
if (error != null)
System.err.println(error);
System.err.println(String.format("usage: BuildHTLC <refund-P2PKH> <LTC-amount> <redeem-P2PKH> <HASH160-of-secret> <locktime>"));
System.err.println(String.format("example: BuildHTLC "
+ "msAfaDaJ8JiprxxFaAXEEPxKK3JaZCYpLv \\\n"
+ "\t0.00008642 \\\n"
+ "\tmrBpZYYGYMwUa8tRjTiXfP1ySqNXszWN5h \\\n"
+ "\tdaf59884b4d1aec8c1b17102530909ee43c0151a \\\n"
+ "\t1600000000"));
System.exit(1);
}
public static void main(String[] args) {
if (args.length < 5 || args.length > 5)
usage(null);
Security.insertProviderAt(new BouncyCastleProvider(), 0);
Security.insertProviderAt(new BouncyCastleJsseProvider(), 1);
Settings.fileInstance("settings-test.json");
Litecoin litecoin = Litecoin.getInstance();
NetworkParameters params = litecoin.getNetworkParameters();
Address refundLitecoinAddress = null;
Coin litecoinAmount = null;
Address redeemLitecoinAddress = null;
byte[] secretHash = null;
int lockTime = 0;
int argIndex = 0;
try {
refundLitecoinAddress = Address.fromString(params, args[argIndex++]);
if (refundLitecoinAddress.getOutputScriptType() != ScriptType.P2PKH)
usage("Refund Litecoin address must be in P2PKH form");
litecoinAmount = Coin.parseCoin(args[argIndex++]);
redeemLitecoinAddress = Address.fromString(params, args[argIndex++]);
if (redeemLitecoinAddress.getOutputScriptType() != ScriptType.P2PKH)
usage("Redeem Litecoin address must be in P2PKH form");
secretHash = HashCode.fromString(args[argIndex++]).asBytes();
if (secretHash.length != 20)
usage("Hash of secret must be 20 bytes");
lockTime = Integer.parseInt(args[argIndex++]);
int refundTimeoutDelay = lockTime - (int) (System.currentTimeMillis() / 1000L);
if (refundTimeoutDelay < 600 || refundTimeoutDelay > 30 * 24 * 60 * 60)
usage("Locktime (seconds) should be at between 10 minutes and 1 month from now");
} catch (IllegalArgumentException e) {
usage(String.format("Invalid argument %d: %s", argIndex, e.getMessage()));
}
Coin p2shFee;
try {
p2shFee = Coin.valueOf(litecoin.getP2shFee(null));
} catch (ForeignBlockchainException e) {
throw new RuntimeException(e.getMessage());
}
System.out.println("Confirm the following is correct based on the info you've given:");
System.out.println(String.format("Refund Litecoin address: %s", refundLitecoinAddress));
System.out.println(String.format("Litecoin redeem amount: %s", litecoinAmount.toPlainString()));
System.out.println(String.format("Redeem Litecoin address: %s", redeemLitecoinAddress));
System.out.println(String.format("Redeem miner's fee: %s", litecoin.format(p2shFee)));
System.out.println(String.format("Redeem script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneOffset.UTC), lockTime));
System.out.println(String.format("Hash of secret: %s", HashCode.fromBytes(secretHash)));
byte[] redeemScriptBytes = BitcoinyHTLC.buildScript(refundLitecoinAddress.getHash(), lockTime, redeemLitecoinAddress.getHash(), secretHash);
System.out.println(String.format("Redeem script: %s", HashCode.fromBytes(redeemScriptBytes)));
String p2shAddress = litecoin.deriveP2shAddress(redeemScriptBytes);
System.out.println(String.format("P2SH address: %s", p2shAddress));
litecoinAmount = litecoinAmount.add(p2shFee);
// Fund P2SH
System.out.println(String.format("\nYou need to fund %s with %s (includes redeem/refund fee of %s)",
p2shAddress, litecoin.format(litecoinAmount), litecoin.format(p2shFee)));
System.out.println("Once this is done, responder should run Respond to check P2SH funding and create AT");
}
}

View File

@ -1,218 +0,0 @@
package org.qortal.test.crosschain.litecoinv1;
import java.security.Security;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Arrays;
import java.util.List;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.LegacyAddress;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionOutput;
import org.bitcoinj.script.Script.ScriptType;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
import org.qortal.crosschain.ForeignBlockchainException;
import org.qortal.crosschain.Litecoin;
import org.qortal.crosschain.Bitcoiny;
import org.qortal.crosschain.BitcoinyHTLC;
import org.qortal.crypto.Crypto;
import org.qortal.settings.Settings;
import com.google.common.hash.HashCode;
public class Refund {
static {
// This must go before any calls to LogManager/Logger
System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
}
private static void usage(String error) {
if (error != null)
System.err.println(error);
System.err.println(String.format("usage: Refund <P2SH-address> <refund-PRIVATE-KEY> <redeem-P2PKH> <HASH160-of-secret> <locktime> <output-address>"));
System.err.println(String.format("example: Refund "
+ "2N4378NbEVGjmiUmoUD9g1vCY6kyx9tDUJ6 \\\n"
+ "\tef8f31b49c31b4a140aebcd9605fded88cc2dad0844c4b984f9191a5a416f72d3801e16447b0 \\\n"
+ "\tmrBpZYYGYMwUa8tRjTiXfP1ySqNXszWN5h \\\n"
+ "\tdaf59884b4d1aec8c1b17102530909ee43c0151a \\\n"
+ "\t1600184800 \\\n"
+ "\tmoJtbbhs7T4Z5hmBH2iyKhGrCWBzQWS2CL"));
System.exit(1);
}
public static void main(String[] args) {
if (args.length < 6 || args.length > 6)
usage(null);
Security.insertProviderAt(new BouncyCastleProvider(), 0);
Security.insertProviderAt(new BouncyCastleJsseProvider(), 1);
Settings.fileInstance("settings-test.json");
Bitcoiny bitcoiny = Litecoin.getInstance();
NetworkParameters params = bitcoiny.getNetworkParameters();
Address p2shAddress = null;
byte[] refundPrivateKey = null;
Address redeemAddress = null;
byte[] secretHash = null;
int lockTime = 0;
Address outputAddress = null;
int argIndex = 0;
try {
p2shAddress = Address.fromString(params, args[argIndex++]);
if (p2shAddress.getOutputScriptType() != ScriptType.P2SH)
usage("P2SH address invalid");
refundPrivateKey = HashCode.fromString(args[argIndex++]).asBytes();
// Auto-trim
if (refundPrivateKey.length >= 37 && refundPrivateKey.length <= 38)
refundPrivateKey = Arrays.copyOfRange(refundPrivateKey, 1, 33);
if (refundPrivateKey.length != 32)
usage("Refund private key must be 32 bytes");
redeemAddress = Address.fromString(params, args[argIndex++]);
if (redeemAddress.getOutputScriptType() != ScriptType.P2PKH)
usage("Redeem address must be in P2PKH form");
secretHash = HashCode.fromString(args[argIndex++]).asBytes();
if (secretHash.length != 20)
usage("HASH160 of secret must be 20 bytes");
lockTime = Integer.parseInt(args[argIndex++]);
outputAddress = Address.fromString(params, args[argIndex++]);
if (outputAddress.getOutputScriptType() != ScriptType.P2PKH)
usage("output address invalid");
} catch (IllegalArgumentException e) {
usage(String.format("Invalid argument %d: %s", argIndex, e.getMessage()));
}
Coin p2shFee;
try {
p2shFee = Coin.valueOf(bitcoiny.getP2shFee(null));
} catch (ForeignBlockchainException e) {
throw new RuntimeException(e.getMessage());
}
try {
System.out.println(String.format("P2SH address: %s", p2shAddress));
System.out.println(String.format("Refund PRIVATE key: %s", HashCode.fromBytes(refundPrivateKey)));
System.out.println(String.format("Redeem address: %s", redeemAddress));
System.out.println(String.format("Redeem script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneOffset.UTC), lockTime));
System.out.println(String.format("Refund recipient (PKH): %s (%s)", outputAddress, HashCode.fromBytes(outputAddress.getHash())));
System.out.println(String.format("Refund miner's fee: %s", bitcoiny.format(p2shFee)));
// New/derived info
ECKey refundKey = ECKey.fromPrivate(refundPrivateKey);
Address refundAddress = Address.fromKey(params, refundKey, ScriptType.P2PKH);
byte[] redeemScriptBytes = BitcoinyHTLC.buildScript(refundAddress.getHash(), lockTime, redeemAddress.getHash(), secretHash);
System.out.println(String.format("Redeem script: %s", HashCode.fromBytes(redeemScriptBytes)));
byte[] redeemScriptHash = Crypto.hash160(redeemScriptBytes);
Address derivedP2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash);
if (!derivedP2shAddress.equals(p2shAddress)) {
System.err.println(String.format("Derived P2SH address %s does not match given address %s", derivedP2shAddress, p2shAddress));
System.exit(2);
}
// Some checks
System.out.println("\nProcessing:");
long medianBlockTime;
try {
medianBlockTime = bitcoiny.getMedianBlockTime();
} catch (ForeignBlockchainException e) {
System.err.println("Unable to determine median block time");
System.exit(2);
return;
}
System.out.println(String.format("Median block time: %s", LocalDateTime.ofInstant(Instant.ofEpochSecond(medianBlockTime), ZoneOffset.UTC)));
long now = System.currentTimeMillis();
if (now < medianBlockTime * 1000L) {
System.err.println(String.format("Too soon (%s) to refund based on median block time %s", LocalDateTime.ofInstant(Instant.ofEpochMilli(now), ZoneOffset.UTC), LocalDateTime.ofInstant(Instant.ofEpochSecond(medianBlockTime), ZoneOffset.UTC)));
System.exit(2);
}
if (now < lockTime * 1000L) {
System.err.println(String.format("Too soon (%s) to refund based on lockTime %s", LocalDateTime.ofInstant(Instant.ofEpochMilli(now), ZoneOffset.UTC), LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneOffset.UTC)));
System.exit(2);
}
// Check P2SH is funded
long p2shBalance;
try {
p2shBalance = bitcoiny.getConfirmedBalance(p2shAddress.toString());
} catch (ForeignBlockchainException e) {
System.err.println(String.format("Unable to check P2SH address %s balance", p2shAddress));
System.exit(2);
return;
}
System.out.println(String.format("P2SH address %s balance: %s", p2shAddress, bitcoiny.format(p2shBalance)));
// Grab all P2SH funding transactions (just in case there are more than one)
List<TransactionOutput> fundingOutputs;
try {
fundingOutputs = bitcoiny.getUnspentOutputs(p2shAddress.toString());
} catch (ForeignBlockchainException e) {
System.err.println(String.format("Can't find outputs for P2SH"));
System.exit(2);
return;
}
System.out.println(String.format("Found %d output%s for P2SH", fundingOutputs.size(), (fundingOutputs.size() != 1 ? "s" : "")));
for (TransactionOutput fundingOutput : fundingOutputs)
System.out.println(String.format("Output %s:%d amount %s", HashCode.fromBytes(fundingOutput.getParentTransactionHash().getBytes()), fundingOutput.getIndex(), bitcoiny.format(fundingOutput.getValue())));
if (fundingOutputs.isEmpty()) {
System.err.println(String.format("Can't refund spent/unfunded P2SH"));
System.exit(2);
}
if (fundingOutputs.size() != 1) {
System.err.println(String.format("Expecting only one unspent output for P2SH"));
// No longer fatal
}
for (TransactionOutput fundingOutput : fundingOutputs)
System.out.println(String.format("Using output %s:%d for redeem", HashCode.fromBytes(fundingOutput.getParentTransactionHash().getBytes()), fundingOutput.getIndex()));
Coin refundAmount = Coin.valueOf(p2shBalance).subtract(p2shFee);
System.out.println(String.format("Spending %s of output, with %s as mining fee", bitcoiny.format(refundAmount), bitcoiny.format(p2shFee)));
Transaction refundTransaction = BitcoinyHTLC.buildRefundTransaction(bitcoiny.getNetworkParameters(), refundAmount, refundKey,
fundingOutputs, redeemScriptBytes, lockTime, outputAddress.getHash());
byte[] rawTransactionBytes = refundTransaction.bitcoinSerialize();
System.out.println(String.format("\nRaw transaction bytes:\n%s\n", HashCode.fromBytes(rawTransactionBytes).toString()));
try {
bitcoiny.broadcastTransaction(refundTransaction);
} catch (ForeignBlockchainException e) {
System.err.println(String.format("Failed to broadcast transaction: %s", e.getMessage()));
System.exit(1);
}
} catch (NumberFormatException e) {
usage(String.format("Number format exception: %s", e.getMessage()));
}
}
}

View File

@ -1,74 +0,0 @@
package org.qortal.test.crosschain.litecoinv1;
import java.security.Security;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Transaction;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
import org.qortal.crosschain.ForeignBlockchainException;
import org.qortal.crosschain.Litecoin;
import org.qortal.settings.Settings;
public class SendLTC {
private static void usage(String error) {
if (error != null)
System.err.println(error);
System.err.println(String.format("usage: SendLTC <xprv58> <recipient> <LTC-amount>"));
System.err.println(String.format("example: SendLTC "
+ "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ \\\n"
+ "\tmsAfaDaJ8JiprxxFaAXEEPxKK3JaZCYpLv \\\n"
+ "\t0.00008642"));
System.exit(1);
}
public static void main(String[] args) {
if (args.length < 3 || args.length > 3)
usage(null);
Security.insertProviderAt(new BouncyCastleProvider(), 0);
Security.insertProviderAt(new BouncyCastleJsseProvider(), 1);
Settings.fileInstance("settings-test.json");
Litecoin litecoin = Litecoin.getInstance();
NetworkParameters params = litecoin.getNetworkParameters();
String xprv58 = null;
Address litecoinAddress = null;
Coin litecoinAmount = null;
int argIndex = 0;
try {
xprv58 = args[argIndex++];
if (!litecoin.isValidXprv(xprv58))
usage("xprv invalid");
litecoinAddress = Address.fromString(params, args[argIndex++]);
litecoinAmount = Coin.parseCoin(args[argIndex++]);
} catch (IllegalArgumentException e) {
usage(String.format("Invalid argument %d: %s", argIndex, e.getMessage()));
}
System.out.println(String.format("Litecoin address: %s", litecoinAddress));
System.out.println(String.format("Litecoin amount: %s", litecoinAmount.toPlainString()));
Transaction transaction = litecoin.buildSpend(xprv58, litecoinAddress.toString(), litecoinAmount.value);
if (transaction == null) {
System.err.println("Insufficent funds");
System.exit(1);
}
try {
litecoin.broadcastTransaction(transaction);
} catch (ForeignBlockchainException e) {
System.err.println("Transaction broadcast failed: " + e.getMessage());
}
}
}