diff --git a/src/main/java/org/qortal/controller/PirateChainWalletController.java b/src/main/java/org/qortal/controller/PirateChainWalletController.java index 662e33c7..d12660e6 100644 --- a/src/main/java/org/qortal/controller/PirateChainWalletController.java +++ b/src/main/java/org/qortal/controller/PirateChainWalletController.java @@ -105,8 +105,9 @@ public class PirateChainWalletController extends Thread { } try { - this.currentWallet = new PirateWallet(entropyBytes); - if (!this.currentWallet.isReady()) { + this.currentWallet = new PirateWallet(entropyBytes, false); + if (!this.currentWallet.isReady() || this.currentWallet.isDisposable()) { + // Don't persist wallets that aren't ready or are disposable this.currentWallet = null; } return true; @@ -117,6 +118,16 @@ public class PirateChainWalletController extends Thread { return false; } + public PirateWallet switchToDisposableWallet() { + try { + this.currentWallet = null; + return new PirateWallet(null, true); + + } catch (IOException e) { + return null; + } + } + private void saveCurrentWallet() { if (this.currentWallet == null) { // Nothing to do @@ -149,6 +160,13 @@ public class PirateChainWalletController extends Thread { } } + public void ensureNotDisposable() throws ForeignBlockchainException { + // Safety check to make sure funds aren't sent to a disposable wallet + if (this.currentWallet == null || this.currentWallet.isDisposable()) { + throw new ForeignBlockchainException("Invalid wallet"); + } + } + public void ensureSynchronized() throws ForeignBlockchainException { String response = LiteWalletJni.execute("syncStatus", ""); JSONObject json = new JSONObject(response); diff --git a/src/main/java/org/qortal/controller/tradebot/PirateChainACCTv3TradeBot.java b/src/main/java/org/qortal/controller/tradebot/PirateChainACCTv3TradeBot.java index 4bccd1d9..365ed61f 100644 --- a/src/main/java/org/qortal/controller/tradebot/PirateChainACCTv3TradeBot.java +++ b/src/main/java/org/qortal/controller/tradebot/PirateChainACCTv3TradeBot.java @@ -777,8 +777,8 @@ public class PirateChainACCTv3TradeBot implements AcctTradeBot { String privateKey58 = Base58.encode(privateKey); String redeemScript58 = Base58.encode(redeemScriptA); - String txid = PirateChain.getInstance().redeemP2sh(tradeBotData.getForeignKey(), p2shAddressT3, - receivingAddress, redeemAmount.value, redeemScript58, fundingTxid58, secret58, privateKey58); + String txid = PirateChain.getInstance().redeemP2sh(p2shAddressT3, receivingAddress, redeemAmount.value, + redeemScript58, fundingTxid58, secret58, privateKey58); LOGGER.info("Redeem txid: {}", txid); break; } @@ -848,7 +848,7 @@ public class PirateChainACCTv3TradeBot implements AcctTradeBot { String redeemScript58 = Base58.encode(redeemScriptA); String receivingAddress = pirateChain.getWalletAddress(tradeBotData.getForeignKey()); - String txid = PirateChain.getInstance().refundP2sh(tradeBotData.getForeignKey(), p2shAddressT3, + String txid = PirateChain.getInstance().refundP2sh(p2shAddressT3, receivingAddress, refundAmount.value, redeemScript58, fundingTxid58, lockTimeA, privateKey58); LOGGER.info("Refund txid: {}", txid); break; diff --git a/src/main/java/org/qortal/crosschain/PirateChain.java b/src/main/java/org/qortal/crosschain/PirateChain.java index cdae44a1..44029e21 100644 --- a/src/main/java/org/qortal/crosschain/PirateChain.java +++ b/src/main/java/org/qortal/crosschain/PirateChain.java @@ -263,6 +263,7 @@ public class PirateChain extends Bitcoiny { walletController.initWithEntropy58(entropy58); walletController.ensureInitialized(); walletController.ensureSynchronized(); + walletController.ensureNotDisposable(); // Get balance String response = LiteWalletJni.execute("balance", ""); @@ -281,6 +282,7 @@ public class PirateChain extends Bitcoiny { walletController.initWithEntropy58(entropy58); walletController.ensureInitialized(); walletController.ensureSynchronized(); + walletController.ensureNotDisposable(); List transactions = new ArrayList<>(); @@ -330,6 +332,7 @@ public class PirateChain extends Bitcoiny { PirateChainWalletController walletController = PirateChainWalletController.getInstance(); walletController.initWithEntropy58(entropy58); walletController.ensureInitialized(); + walletController.ensureNotDisposable(); return walletController.getCurrentWallet().getWalletAddress(); } @@ -340,6 +343,7 @@ public class PirateChain extends Bitcoiny { walletController.initWithEntropy58(pirateChainSendRequest.entropy58); walletController.ensureInitialized(); walletController.ensureSynchronized(); + walletController.ensureNotDisposable(); // Unlock wallet walletController.getCurrentWallet().unlock(); @@ -386,6 +390,7 @@ public class PirateChain extends Bitcoiny { walletController.initWithEntropy58(entropy58); walletController.ensureInitialized(); walletController.ensureSynchronized(); + walletController.ensureNotDisposable(); // Unlock wallet walletController.getCurrentWallet().unlock(); @@ -426,16 +431,13 @@ public class PirateChain extends Bitcoiny { throw new ForeignBlockchainException("Something went wrong"); } - public String redeemP2sh(String entropy58, String p2shAddress, String receivingAddress, long amount, String redeemScript58, + public String redeemP2sh(String p2shAddress, String receivingAddress, long amount, String redeemScript58, String fundingTxid58, String secret58, String privateKey58) throws ForeignBlockchainException { - PirateChainWalletController walletController = PirateChainWalletController.getInstance(); - walletController.initWithEntropy58(entropy58); - walletController.ensureInitialized(); - walletController.ensureSynchronized(); - - // Unlock wallet - walletController.getCurrentWallet().unlock(); + PirateWallet wallet = PirateChainWalletController.getInstance().switchToDisposableWallet(); + if (wallet == null) { + throw new ForeignBlockchainException("Unable to initialize disposable Pirate Chain wallet"); + } // Build spend JSONObject txn = new JSONObject(); @@ -478,16 +480,13 @@ public class PirateChain extends Bitcoiny { throw new ForeignBlockchainException("Something went wrong"); } - public String refundP2sh(String entropy58, String p2shAddress, String receivingAddress, long amount, String redeemScript58, + public String refundP2sh(String p2shAddress, String receivingAddress, long amount, String redeemScript58, String fundingTxid58, int lockTime, String privateKey58) throws ForeignBlockchainException { - PirateChainWalletController walletController = PirateChainWalletController.getInstance(); - walletController.initWithEntropy58(entropy58); - walletController.ensureInitialized(); - walletController.ensureSynchronized(); - - // Unlock wallet - walletController.getCurrentWallet().unlock(); + PirateWallet wallet = PirateChainWalletController.getInstance().switchToDisposableWallet(); + if (wallet == null) { + throw new ForeignBlockchainException("Unable to initialize disposable Pirate Chain wallet"); + } // Build spend JSONObject txn = new JSONObject(); diff --git a/src/main/java/org/qortal/crosschain/PirateWallet.java b/src/main/java/org/qortal/crosschain/PirateWallet.java index c0ff10b0..89014338 100644 --- a/src/main/java/org/qortal/crosschain/PirateWallet.java +++ b/src/main/java/org/qortal/crosschain/PirateWallet.java @@ -24,11 +24,14 @@ import java.nio.file.StandardOpenOption; import java.util.Arrays; import java.util.Objects; +import static org.qortal.crosschain.PirateChain.DEFAULT_BIRTHDAY; + public class PirateWallet { protected static final Logger LOGGER = LogManager.getLogger(PirateWallet.class); private byte[] entropyBytes; + private final boolean isDisposable; private String seedPhrase; private boolean ready = false; @@ -36,13 +39,14 @@ public class PirateWallet { private final String saplingOutput64; private final String saplingSpend64; - private static String SERVER_URI = "https://lightd.pirate.black:443/"; - private static String COIN_PARAMS_RESOURCE = "piratechain/coinparams.json"; - private static String SAPLING_OUTPUT_RESOURCE = "piratechain/saplingoutput_base64"; - private static String SAPLING_SPEND_RESOURCE = "piratechain/saplingspend_base64"; + private final static String SERVER_URI = "https://lightd.pirate.black:443/"; + private final static String COIN_PARAMS_RESOURCE = "piratechain/coinparams.json"; + private final static String SAPLING_OUTPUT_RESOURCE = "piratechain/saplingoutput_base64"; + private final static String SAPLING_SPEND_RESOURCE = "piratechain/saplingspend_base64"; - public PirateWallet(byte[] entropyBytes) throws IOException { + public PirateWallet(byte[] entropyBytes, boolean isDisposable) throws IOException { this.entropyBytes = entropyBytes; + this.isDisposable = isDisposable; final URL paramsUrl = Resources.getResource(COIN_PARAMS_RESOURCE); this.params = Resources.toString(paramsUrl, StandardCharsets.UTF_8); @@ -60,6 +64,17 @@ public class PirateWallet { try { LiteWalletJni.initlogging(); + if (this.entropyBytes == null) { + if (this.isDisposable) { + // Generate disposable wallet + this.entropyBytes = new byte[32]; + } + else { + // Need entropy bytes for a non disposable wallet + return false; + } + } + // Pirate library uses base64 encoding String entropy64 = Base64.toBase64String(this.entropyBytes); @@ -75,8 +90,20 @@ public class PirateWallet { if (wallet == null) { // Wallet doesn't exist, so create a new one + int birthday = DEFAULT_BIRTHDAY; + if (this.isDisposable) { + try { + // Attempt to set birthday to the current block for disposable wallets + birthday = PirateChain.getInstance().blockchainProvider.getCurrentHeight(); + } + catch (ForeignBlockchainException e) { + // Use the default height + } + } + // Initialize new wallet - String outputSeedResponse = LiteWalletJni.initfromseed(SERVER_URI, this.params, inputSeedPhrase, "1886500", this.saplingOutput64, this.saplingSpend64); // Thread-safe. + String birthdayString = String.format("%d", birthday); + String outputSeedResponse = LiteWalletJni.initfromseed(SERVER_URI, this.params, inputSeedPhrase, birthdayString, this.saplingOutput64, this.saplingSpend64); // Thread-safe. JSONObject outputSeedJson = new JSONObject(outputSeedResponse); String outputSeedPhrase = null; if (outputSeedJson.has("seed")) { @@ -170,6 +197,10 @@ public class PirateWallet { LOGGER.info("Error: can't save wallet, because no wallet it initialized"); return false; } + if (this.isDisposable) { + LOGGER.info("Error: can't save disposable wallet"); + return false; + } // Encrypt first (will do nothing if already encrypted) this.encrypt(); @@ -198,6 +229,10 @@ public class PirateWallet { } public String load() throws IOException { + if (this.isDisposable) { + // Can't load disposable wallets + return null; + } Path walletPath = this.getCurrentWalletPath(); if (!Files.exists(walletPath)) { return null; @@ -286,6 +321,10 @@ public class PirateWallet { return null; } + public boolean isDisposable() { + return this.isDisposable; + } + public Boolean isEncrypted() { String response = LiteWalletJni.execute("encryptionstatus", ""); JSONObject json = new JSONObject(response);