forked from Qortal/qortal
catbref
5 years ago
8 changed files with 572 additions and 236 deletions
@ -0,0 +1,69 @@
|
||||
package org.qora.test.apps; |
||||
|
||||
import java.io.File; |
||||
import java.io.FileNotFoundException; |
||||
import java.io.FileOutputStream; |
||||
import java.io.OutputStreamWriter; |
||||
import java.io.PrintWriter; |
||||
import java.net.InetAddress; |
||||
import java.nio.ByteBuffer; |
||||
import java.nio.charset.StandardCharsets; |
||||
import java.util.TreeMap; |
||||
|
||||
import org.bitcoinj.core.BlockChain; |
||||
import org.bitcoinj.core.CheckpointManager; |
||||
import org.bitcoinj.core.NetworkParameters; |
||||
import org.bitcoinj.core.PeerAddress; |
||||
import org.bitcoinj.core.PeerGroup; |
||||
import org.bitcoinj.core.StoredBlock; |
||||
import org.bitcoinj.core.VerificationException; |
||||
import org.bitcoinj.core.listeners.NewBestBlockListener; |
||||
import org.bitcoinj.params.RegTestParams; |
||||
import org.bitcoinj.store.BlockStore; |
||||
import org.bitcoinj.store.MemoryBlockStore; |
||||
|
||||
public class BuildCheckpoints { |
||||
|
||||
private static final TreeMap<Integer, StoredBlock> checkpoints = new TreeMap<>(); |
||||
|
||||
public static void main(String[] args) throws Exception { |
||||
final NetworkParameters params = RegTestParams.get(); |
||||
|
||||
final BlockStore store = new MemoryBlockStore(params); |
||||
final BlockChain chain = new BlockChain(params, store); |
||||
final PeerGroup peerGroup = new PeerGroup(params, chain); |
||||
|
||||
final InetAddress ipAddress = InetAddress.getLocalHost(); |
||||
final PeerAddress peerAddress = new PeerAddress(params, ipAddress); |
||||
peerGroup.addAddress(peerAddress); |
||||
peerGroup.start(); |
||||
|
||||
chain.addNewBestBlockListener((block) -> checkpoints.put(block.getHeight(), block)); |
||||
|
||||
peerGroup.downloadBlockChain(); |
||||
peerGroup.stop(); |
||||
|
||||
final File checkpointsFile = new File("regtest-checkpoints"); |
||||
saveAsText(checkpointsFile); |
||||
} |
||||
|
||||
private static void saveAsText(File textFile) { |
||||
try (PrintWriter writer = new PrintWriter( |
||||
new OutputStreamWriter(new FileOutputStream(textFile), StandardCharsets.US_ASCII))) { |
||||
writer.println("TXT CHECKPOINTS 1"); |
||||
writer.println("0"); // Number of signatures to read. Do this later.
|
||||
writer.println(checkpoints.size()); |
||||
|
||||
ByteBuffer buffer = ByteBuffer.allocate(StoredBlock.COMPACT_SERIALIZED_SIZE); |
||||
|
||||
for (StoredBlock block : checkpoints.values()) { |
||||
block.serializeCompact(buffer); |
||||
writer.println(CheckpointManager.BASE64.encode(buffer.array())); |
||||
buffer.position(0); |
||||
} |
||||
} catch (FileNotFoundException e) { |
||||
return; |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,144 @@
|
||||
package org.qora.test.btcacct; |
||||
|
||||
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.AddressFormatException; |
||||
import org.bitcoinj.core.Coin; |
||||
import org.bitcoinj.core.ECKey; |
||||
import org.bitcoinj.core.LegacyAddress; |
||||
import org.bitcoinj.core.NetworkParameters; |
||||
import org.bitcoinj.params.RegTestParams; |
||||
import org.bitcoinj.params.TestNet3Params; |
||||
import org.bitcoinj.script.Script.ScriptType; |
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider; |
||||
import org.qora.controller.Controller; |
||||
import org.qora.crosschain.BTC; |
||||
import org.qora.crosschain.BTCACCT; |
||||
import org.qora.repository.DataException; |
||||
import org.qora.repository.Repository; |
||||
import org.qora.repository.RepositoryFactory; |
||||
import org.qora.repository.RepositoryManager; |
||||
import org.qora.repository.hsqldb.HSQLDBRepositoryFactory; |
||||
import org.qora.settings.Settings; |
||||
|
||||
import com.google.common.hash.HashCode; |
||||
|
||||
/** |
||||
* Initiator must be Qora-chain so that initiator can send initial message to BTC P2SH then Qora can scan for P2SH add send corresponding message to Qora AT. |
||||
* |
||||
* Initiator (wants Qora, has BTC) |
||||
* Funds BTC P2SH address |
||||
* |
||||
* Responder (has Qora, wants BTC) |
||||
* Builds Qora ACCT AT and funds it with Qora |
||||
* |
||||
* Initiator sends recipient+secret+script as input to BTC P2SH address, releasing BTC amount - fees to responder |
||||
* |
||||
* Qora nodes scan for P2SH output, checks amount and recipient and if ok sends secret to Qora ACCT AT |
||||
* (Or it's possible to feed BTC transaction details into Qora AT so it can check them itself?) |
||||
* |
||||
* Qora ACCT AT sends its Qora to initiator |
||||
* |
||||
*/ |
||||
|
||||
public class Initiate { |
||||
|
||||
private static final long REFUND_TIMEOUT = 600L; // seconds
|
||||
|
||||
private static void usage(String error) { |
||||
if (error != null) |
||||
System.err.println(error); |
||||
|
||||
System.err.println(String.format("usage: Initiate <your-BTC-P2PKH> <BTC-amount> <their-BTC-P2PKH> (<BTC-redeem/refund-fee>)")); |
||||
System.err.println(String.format("example: Initiate mrTDPdM15cFWJC4g223BXX5snicfVJBx6M \\\n" |
||||
+ "\t0.00008642 \\\n" |
||||
+ "\tn2N5VKrzq39nmuefZwp3wBiF4icdXX2B6o")); |
||||
System.exit(1); |
||||
} |
||||
|
||||
public static void main(String[] args) { |
||||
if (args.length < 3 || args.length > 4) |
||||
usage(null); |
||||
|
||||
Security.insertProviderAt(new BouncyCastleProvider(), 0); |
||||
Settings.fileInstance("settings-test.json"); |
||||
NetworkParameters params = RegTestParams.get(); |
||||
// TestNet3Params.get();
|
||||
|
||||
Address yourBitcoinAddress = null; |
||||
Coin bitcoinAmount = null; |
||||
Address theirBitcoinAddress = null; |
||||
Coin bitcoinFee = BTCACCT.DEFAULT_BTC_FEE; |
||||
|
||||
try { |
||||
int argIndex = 0; |
||||
|
||||
yourBitcoinAddress = Address.fromString(params, args[argIndex++]); |
||||
if (yourBitcoinAddress.getOutputScriptType() != ScriptType.P2PKH) |
||||
usage("Your BTC address is not in P2PKH form"); |
||||
|
||||
bitcoinAmount = Coin.parseCoin(args[argIndex++]); |
||||
|
||||
theirBitcoinAddress = Address.fromString(params, args[argIndex++]); |
||||
if (theirBitcoinAddress.getOutputScriptType() != ScriptType.P2PKH) |
||||
usage("Their BTC address is not in P2PKH form"); |
||||
|
||||
if (args.length > argIndex) |
||||
bitcoinFee = Coin.parseCoin(args[argIndex++]); |
||||
} catch (NumberFormatException | AddressFormatException e) { |
||||
usage(String.format("Argument format exception: %s", e.getMessage())); |
||||
} |
||||
|
||||
try { |
||||
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.getRepositoryUrl()); |
||||
RepositoryManager.setRepositoryFactory(repositoryFactory); |
||||
} catch (DataException e) { |
||||
throw new RuntimeException("Repository startup issue: " + e.getMessage()); |
||||
} |
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) { |
||||
System.out.println("Confirm the following is correct based on the info you've given:"); |
||||
|
||||
System.out.println(String.format("Your Bitcoin address: %s", yourBitcoinAddress)); |
||||
System.out.println(String.format("Their Bitcoin address: %s", theirBitcoinAddress)); |
||||
System.out.println(String.format("Bitcoin redeem amount: %s", bitcoinAmount.toPlainString())); |
||||
System.out.println(String.format("Bitcoin redeem fee: %s", bitcoinFee.toPlainString())); |
||||
|
||||
// New/derived info
|
||||
|
||||
ECKey tradeKey = new ECKey(); |
||||
System.out.println("\nSecret info (DO NOT share with other party):"); |
||||
System.out.println(String.format("Trade private key: %s", HashCode.fromBytes(tradeKey.getPrivKeyBytes()))); |
||||
|
||||
System.out.println("\nGive this info to other party:"); |
||||
|
||||
System.out.println(String.format("Trade pubkeyhash: %s", HashCode.fromBytes(tradeKey.getPubKeyHash()))); |
||||
|
||||
int lockTime = (int) ((System.currentTimeMillis() / 1000L) + REFUND_TIMEOUT); |
||||
System.out.println(String.format("Redeem script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneOffset.UTC), lockTime)); |
||||
|
||||
byte[] redeemScriptBytes = BTCACCT.buildScript(tradeKey.getPubKeyHash(), yourBitcoinAddress.getHash(), theirBitcoinAddress.getHash(), lockTime); |
||||
System.out.println(String.format("Redeem script: %s", HashCode.fromBytes(redeemScriptBytes))); |
||||
|
||||
byte[] redeemScriptHash = BTC.hash160(redeemScriptBytes); |
||||
|
||||
Address p2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash); |
||||
System.out.println(String.format("P2SH address: %s", p2shAddress)); |
||||
|
||||
bitcoinAmount = bitcoinAmount.add(bitcoinFee); |
||||
|
||||
// Fund P2SH
|
||||
System.out.println(String.format("\nYou need to fund %s with %s BTC (includes redeem/refund fee of %s)", |
||||
p2shAddress.toString(), bitcoinAmount.toPlainString(), bitcoinFee.toPlainString())); |
||||
|
||||
System.out.println("Once this is done, responder should run Respond to check P2SH funding and create AT"); |
||||
} catch (DataException e) { |
||||
throw new RuntimeException("Repository issue: " + e.getMessage()); |
||||
} |
||||
} |
||||
|
||||
} |
@ -1,155 +0,0 @@
|
||||
package org.qora.test.btcacct; |
||||
|
||||
import java.math.BigDecimal; |
||||
import java.security.SecureRandom; |
||||
import java.security.Security; |
||||
import java.time.Instant; |
||||
import java.time.LocalDateTime; |
||||
import java.time.ZoneId; |
||||
|
||||
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.params.TestNet3Params; |
||||
import org.bitcoinj.script.Script.ScriptType; |
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider; |
||||
import org.qora.account.PrivateKeyAccount; |
||||
import org.qora.account.PublicKeyAccount; |
||||
import org.qora.asset.Asset; |
||||
import org.qora.controller.Controller; |
||||
import org.qora.crosschain.BTC; |
||||
import org.qora.crosschain.BTCACCT; |
||||
import org.qora.crypto.Crypto; |
||||
import org.qora.repository.DataException; |
||||
import org.qora.repository.Repository; |
||||
import org.qora.repository.RepositoryFactory; |
||||
import org.qora.repository.RepositoryManager; |
||||
import org.qora.repository.hsqldb.HSQLDBRepositoryFactory; |
||||
import org.qora.utils.Base58; |
||||
|
||||
import com.google.common.hash.HashCode; |
||||
|
||||
/** |
||||
* Initiator must be Qora-chain so that initiator can send initial message to BTC P2SH then Qora can scan for P2SH add send corresponding message to Qora AT. |
||||
* |
||||
* Initiator (wants Qora, has BTC) |
||||
* Funds BTC P2SH address |
||||
* |
||||
* Responder (has Qora, wants BTC) |
||||
* Builds Qora ACCT AT and funds it with Qora |
||||
* |
||||
* Initiator sends recipient+secret+script as input to BTC P2SH address, releasing BTC amount - fees to responder |
||||
* |
||||
* Qora nodes scan for P2SH output, checks amount and recipient and if ok sends secret to Qora ACCT AT |
||||
* (Or it's possible to feed BTC transaction details into Qora AT so it can check them itself?) |
||||
* |
||||
* Qora ACCT AT sends its Qora to initiator |
||||
* |
||||
*/ |
||||
|
||||
public class Initiate1 { |
||||
|
||||
private static final long REFUND_TIMEOUT = 600L; // seconds
|
||||
|
||||
private static void usage() { |
||||
System.err.println(String.format("usage: Initiate1 <your-QORT-PRIVkey> <your-BTC-pubkey> <QORT-amount> <BTC-amount> <their-QORT-pubkey> <their-BTC-pubkey>")); |
||||
System.err.println(String.format("example: Initiate1 pYQ6DpQBJ2n72TCLJLScEvwhf3boxWy2kQEPynakwpj \\\n" |
||||
+ "\t03aa20871c2195361f2826c7a649eab6b42639630c4d8c33c55311d5c1e476b5d6 \\\n" |
||||
+ "\t123 0.00008642 \\\n" |
||||
+ "\tJBNBQQDzZsm5do1BrwWAp53Ps4KYJVt749EGpCf7ofte \\\n" |
||||
+ "\t032783606be32a3e639a33afe2b15f058708ab124f3b290d595ee954390a0c8559")); |
||||
System.exit(1); |
||||
} |
||||
|
||||
public static void main(String[] args) { |
||||
if (args.length != 6) |
||||
usage(); |
||||
|
||||
Security.insertProviderAt(new BouncyCastleProvider(), 0); |
||||
NetworkParameters params = TestNet3Params.get(); |
||||
|
||||
int argIndex = 0; |
||||
String yourQortPrivKey58 = args[argIndex++]; |
||||
String yourBitcoinPubKeyHex = args[argIndex++]; |
||||
|
||||
String rawQortAmount = args[argIndex++]; |
||||
String rawBitcoinAmount = args[argIndex++]; |
||||
|
||||
String theirQortPubKey58 = args[argIndex++]; |
||||
String theirBitcoinPubKeyHex = args[argIndex++]; |
||||
|
||||
try { |
||||
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.getRepositoryUrl()); |
||||
RepositoryManager.setRepositoryFactory(repositoryFactory); |
||||
} catch (DataException e) { |
||||
throw new RuntimeException("Repository startup issue: " + e.getMessage()); |
||||
} |
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) { |
||||
System.out.println("Confirm the following is correct based on the info you've given:"); |
||||
|
||||
byte[] yourQortPrivKey = Base58.decode(yourQortPrivKey58); |
||||
PrivateKeyAccount yourQortalAccount = new PrivateKeyAccount(repository, yourQortPrivKey); |
||||
System.out.println(String.format("Your Qortal address: %s", yourQortalAccount.getAddress())); |
||||
|
||||
byte[] yourBitcoinPubKey = HashCode.fromString(yourBitcoinPubKeyHex).asBytes(); |
||||
ECKey yourBitcoinKey = ECKey.fromPublicOnly(yourBitcoinPubKey); |
||||
Address yourBitcoinAddress = Address.fromKey(params, yourBitcoinKey, ScriptType.P2PKH); |
||||
System.out.println(String.format("Your Bitcoin address: %s", yourBitcoinAddress)); |
||||
|
||||
byte[] theirQortPubKey = Base58.decode(theirQortPubKey58); |
||||
PublicKeyAccount theirQortalAccount = new PublicKeyAccount(repository, theirQortPubKey); |
||||
System.out.println(String.format("Their Qortal address: %s", theirQortalAccount.getAddress())); |
||||
|
||||
byte[] theirBitcoinPubKey = HashCode.fromString(theirBitcoinPubKeyHex).asBytes(); |
||||
ECKey theirBitcoinKey = ECKey.fromPublicOnly(theirBitcoinPubKey); |
||||
Address theirBitcoinAddress = Address.fromKey(params, theirBitcoinKey, ScriptType.P2PKH); |
||||
System.out.println(String.format("Their Bitcoin address: %s", theirBitcoinAddress)); |
||||
|
||||
// Some checks
|
||||
BigDecimal qortAmount = new BigDecimal(rawQortAmount).setScale(8); |
||||
BigDecimal yourQortBalance = yourQortalAccount.getConfirmedBalance(Asset.QORT); |
||||
if (yourQortBalance.compareTo(qortAmount) <= 0) { |
||||
System.err.println(String.format("Your QORT balance %s is less than required %s", yourQortBalance.toPlainString(), qortAmount.toPlainString())); |
||||
System.exit(2); |
||||
} |
||||
|
||||
// New/derived info
|
||||
|
||||
byte[] secret = new byte[32]; |
||||
new SecureRandom().nextBytes(secret); |
||||
System.out.println("\nSecret info (DO NOT share with other party):"); |
||||
System.out.println("Secret: " + HashCode.fromBytes(secret).toString()); |
||||
|
||||
System.out.println("\nGive this info to other party:"); |
||||
|
||||
byte[] secretHash = Crypto.digest(secret); |
||||
System.out.println("Hash of secret: " + HashCode.fromBytes(secretHash).toString()); |
||||
|
||||
int lockTime = (int) ((System.currentTimeMillis() / 1000L) + REFUND_TIMEOUT); |
||||
System.out.println(String.format("Redeem script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneId.systemDefault()), lockTime)); |
||||
|
||||
byte[] redeemScriptBytes = BTCACCT.buildRedeemScript(secretHash, yourBitcoinPubKey, theirBitcoinPubKey, lockTime); |
||||
System.out.println("Redeem script: " + HashCode.fromBytes(redeemScriptBytes).toString()); |
||||
|
||||
byte[] redeemScriptHash = BTC.hash160(redeemScriptBytes); |
||||
|
||||
Address p2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash); |
||||
System.out.println("P2SH address: " + p2shAddress.toString()); |
||||
|
||||
Coin bitcoinAmount = Coin.parseCoin(rawBitcoinAmount).add(BTCACCT.DEFAULT_BTC_FEE); |
||||
|
||||
// Fund P2SH
|
||||
System.out.println(String.format("\nYou need to fund %s with %s BTC (includes redeem/refund fee)", p2shAddress.toString(), bitcoinAmount.toPlainString())); |
||||
|
||||
System.out.println("Once this is done, responder should run Respond2 to check P2SH funding and create AT"); |
||||
} catch (NumberFormatException e) { |
||||
usage(); |
||||
} catch (DataException e) { |
||||
throw new RuntimeException("Repository issue: " + e.getMessage()); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,199 @@
|
||||
package org.qora.test.btcacct; |
||||
|
||||
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.AddressFormatException; |
||||
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.params.RegTestParams; |
||||
import org.bitcoinj.params.TestNet3Params; |
||||
import org.bitcoinj.script.Script.ScriptType; |
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider; |
||||
import org.qora.controller.Controller; |
||||
import org.qora.crosschain.BTC; |
||||
import org.qora.crosschain.BTCACCT; |
||||
import org.qora.repository.DataException; |
||||
import org.qora.repository.Repository; |
||||
import org.qora.repository.RepositoryFactory; |
||||
import org.qora.repository.RepositoryManager; |
||||
import org.qora.repository.hsqldb.HSQLDBRepositoryFactory; |
||||
import org.qora.settings.Settings; |
||||
|
||||
import com.google.common.hash.HashCode; |
||||
|
||||
/** |
||||
* Initiator must be Qora-chain so that initiator can send initial message to BTC P2SH then Qora can scan for P2SH add send corresponding message to Qora AT. |
||||
* |
||||
* Initiator (wants Qora, has BTC) |
||||
* Funds BTC P2SH address |
||||
* |
||||
* Responder (has Qora, wants BTC) |
||||
* Builds Qora ACCT AT and funds it with Qora |
||||
* |
||||
* Initiator sends trade private key to Responder. |
||||
* Responder uses their public key + tx signature + trade pubkey + script as input to BTC P2SH address, releasing BTC amount to responder. |
||||
* |
||||
* Qora nodes scan for P2SH output, checks amount and recipient and if ok sends secret to Qora ACCT AT |
||||
* (Or it's possible to feed BTC transaction details into Qora AT so it can check them itself?) |
||||
* |
||||
* Qora ACCT AT sends its Qora to initiator |
||||
* |
||||
*/ |
||||
|
||||
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 final long REFUND_TIMEOUT = 600L; // seconds
|
||||
|
||||
private static void usage(String error) { |
||||
if (error != null) |
||||
System.err.println(error); |
||||
|
||||
System.err.println(String.format("usage: Redeem <your-BTC-pubkey> <their-BTC-P2PKH> <trade-PRIVATE-key> <locktime> <P2SH-address> (<BTC-redeem/refund-fee>)")); |
||||
System.err.println(String.format("example: Redeem 032783606be32a3e639a33afe2b15f058708ab124f3b290d595ee954390a0c8559 \\\n" |
||||
+ "\tmrTDPdM15cFWJC4g223BXX5snicfVJBx6M \\\n" |
||||
+ "\teb95e1c1a5e9e6733549faec85b71f74f67638ea63b0acf2f077e9d0cb94dfe8 1575653814 2Mtn4aLjjWVEWckdoTMK7P8WbkXJf1ES6yL")); |
||||
System.exit(1); |
||||
} |
||||
|
||||
public static void main(String[] args) { |
||||
if (args.length < 5 || args.length > 6) |
||||
usage(null); |
||||
|
||||
Security.insertProviderAt(new BouncyCastleProvider(), 0); |
||||
Settings.fileInstance("settings-test.json"); |
||||
NetworkParameters params = RegTestParams.get(); |
||||
// TestNet3Params.get();
|
||||
|
||||
ECKey yourBitcoinKey = null; |
||||
Address theirBitcoinAddress = null; |
||||
byte[] tradePrivateKey = null; |
||||
int lockTime = 0; |
||||
Address p2shAddress = null; |
||||
Coin bitcoinFee = BTCACCT.DEFAULT_BTC_FEE; |
||||
|
||||
try { |
||||
int argIndex = 0; |
||||
|
||||
yourBitcoinKey = ECKey.fromPublicOnly(HashCode.fromString(args[argIndex++]).asBytes()); |
||||
|
||||
theirBitcoinAddress = Address.fromString(params, args[argIndex++]); |
||||
if (theirBitcoinAddress.getOutputScriptType() != ScriptType.P2PKH) |
||||
usage("Their BTC address is not in P2PKH form"); |
||||
|
||||
tradePrivateKey = HashCode.fromString(args[argIndex++]).asBytes(); |
||||
if (tradePrivateKey.length != 32) |
||||
usage("Trade private key not 32 bytes"); |
||||
|
||||
lockTime = Integer.parseInt(args[argIndex++]); |
||||
|
||||
p2shAddress = Address.fromString(params, args[argIndex++]); |
||||
if (p2shAddress.getOutputScriptType() != ScriptType.P2SH) |
||||
usage("P2SH address invalid"); |
||||
|
||||
if (args.length > argIndex) |
||||
bitcoinFee = Coin.parseCoin(args[argIndex++]); |
||||
} catch (NumberFormatException | AddressFormatException e) { |
||||
usage(String.format("Argument format exception: %s", e.getMessage())); |
||||
} |
||||
|
||||
try { |
||||
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.getRepositoryUrl()); |
||||
RepositoryManager.setRepositoryFactory(repositoryFactory); |
||||
} catch (DataException e) { |
||||
throw new RuntimeException("Repository startup issue: " + e.getMessage()); |
||||
} |
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) { |
||||
System.out.println("Confirm the following is correct based on the info you've given:"); |
||||
|
||||
System.out.println(String.format("Your Bitcoin address: %s", Address.fromKey(params, yourBitcoinKey, ScriptType.P2PKH))); |
||||
System.out.println(String.format("Their Bitcoin address: %s", theirBitcoinAddress)); |
||||
System.out.println(String.format("Trade PRIVATE key: %s", HashCode.fromBytes(tradePrivateKey))); |
||||
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("Bitcoin redeem fee: %s", bitcoinFee.toPlainString())); |
||||
|
||||
// New/derived info
|
||||
|
||||
System.out.println("\nCHECKING info from other party:"); |
||||
|
||||
ECKey tradeKey = ECKey.fromPrivate(tradePrivateKey); |
||||
System.out.println(String.format("Trade pubkeyhash: %s", HashCode.fromBytes(tradeKey.getPubKeyHash()))); |
||||
|
||||
byte[] redeemScriptBytes = BTCACCT.buildScript(tradeKey.getPubKeyHash(), theirBitcoinAddress.getHash(), yourBitcoinKey.getPubKeyHash(), lockTime); |
||||
System.out.println(String.format("Redeem script: %s", HashCode.fromBytes(redeemScriptBytes))); |
||||
|
||||
byte[] redeemScriptHash = BTC.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 = BTC.getInstance().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.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); |
||||
} |
||||
|
||||
Coin p2shBalance = BTC.getInstance().getBalance(p2shAddress.toString(), lockTime - REFUND_TIMEOUT); |
||||
if (p2shBalance == null) { |
||||
System.err.println(String.format("Unable to check P2SH address %s balance", p2shAddress)); |
||||
System.exit(2); |
||||
} |
||||
System.out.println(String.format("P2SH address %s balance: %s BTC", p2shAddress, p2shBalance.toPlainString())); |
||||
|
||||
// Grab all P2SH funding transactions (just in case there are more than one)
|
||||
List<TransactionOutput> fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress.toString(), lockTime - REFUND_TIMEOUT); |
||||
System.out.println(String.format("Found %d unspent output%s for P2SH", fundingOutputs.size(), (fundingOutputs.size() != 1 ? "s" : ""))); |
||||
|
||||
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); |
||||
} |
||||
|
||||
TransactionOutput fundingOutput = fundingOutputs.get(0); |
||||
System.out.println(String.format("Using output %s:%d for redeem", HashCode.fromBytes(fundingOutput.getParentTransactionHash().getBytes()), fundingOutput.getIndex())); |
||||
|
||||
Coin redeemAmount = p2shBalance.subtract(bitcoinFee); |
||||
Transaction redeemTransaction = BTCACCT.buildRedeemTransaction(redeemAmount, tradeKey, yourBitcoinKey.getPubKey(), fundingOutput, redeemScriptBytes); |
||||
|
||||
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())); |
||||
} catch (DataException e) { |
||||
throw new RuntimeException("Repository issue: " + e.getMessage()); |
||||
} |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue