Browse Source

Added concept of a "disposable" pirate chain wallet.

This is needed to allow redeem/refund of P2SH without having an actively synced and initialized wallet. It also ultimately avoids us having to retain the wallet entropy in the trade bot states. Various safety checks have been introduced to make sure that a disposable wallet is never used for anything other than P2SH redeem/refund.
pirate-chain
CalDescent 2 years ago
parent
commit
bb4bdfede5
  1. 22
      src/main/java/org/qortal/controller/PirateChainWalletController.java
  2. 6
      src/main/java/org/qortal/controller/tradebot/PirateChainACCTv3TradeBot.java
  3. 31
      src/main/java/org/qortal/crosschain/PirateChain.java
  4. 51
      src/main/java/org/qortal/crosschain/PirateWallet.java

22
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);

6
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;

31
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<SimpleTransaction> 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();

51
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);

Loading…
Cancel
Save