Browse Source

Pirate Chain uses the 'b' prefix for P2SH addresses, but the light wallet library is configured to use 't3' (from Zcash), so it's easiest to just derive a different prefix for each destination.

This could be simplified by configuring the light wallet library to use the correct 'b' prefix, but this didn't work when first attempted.
pirate-chain
CalDescent 2 years ago
parent
commit
6b53eb5384
  1. 46
      src/main/java/org/qortal/controller/tradebot/PirateChainACCTv3TradeBot.java
  2. 2
      src/main/java/org/qortal/controller/tradebot/TradeBot.java
  3. 9
      src/main/java/org/qortal/crosschain/PirateChain.java
  4. 32
      src/test/java/org/qortal/test/crosschain/PirateChainTests.java

46
src/main/java/org/qortal/controller/tradebot/PirateChainACCTv3TradeBot.java

@ -293,13 +293,13 @@ public class PirateChainACCTv3TradeBot implements AcctTradeBot {
// P2SH-A to be funded
byte[] redeemScriptBytes = PirateChainHTLC.buildScript(tradeForeignPublicKey, lockTimeA, crossChainTradeData.creatorForeignPKH, hashOfSecretA);
String p2shAddress = PirateChain.getInstance().deriveP2shAddress(redeemScriptBytes);
String p2shAddressT3 = PirateChain.getInstance().deriveP2shAddress(redeemScriptBytes); // Use t3 prefix when funding
byte[] redeemScriptWithPrefixBytes = PirateChainHTLC.buildScriptWithPrefix(tradeForeignPublicKey, lockTimeA, crossChainTradeData.creatorForeignPKH, hashOfSecretA);
String redeemScriptWithPrefix58 = Base58.encode(redeemScriptWithPrefixBytes);
// Send to P2SH address
try {
String txid = PirateChain.getInstance().fundP2SH(seed58, p2shAddress, amountA, redeemScriptWithPrefix58);
String txid = PirateChain.getInstance().fundP2SH(seed58, p2shAddressT3, amountA, redeemScriptWithPrefix58);
LOGGER.info("fundingTxidHex: {}", txid);
} catch (ForeignBlockchainException e) {
@ -329,7 +329,7 @@ public class PirateChainACCTv3TradeBot implements AcctTradeBot {
}
}
TradeBot.updateTradeBotState(repository, tradeBotData, () -> String.format("Funding P2SH-A %s. Messaged Bob. Waiting for AT-lock", p2shAddress));
TradeBot.updateTradeBotState(repository, tradeBotData, () -> String.format("Funding P2SH-A %s. Messaged Bob. Waiting for AT-lock", p2shAddressT3));
return ResponseResult.OK;
}
@ -510,13 +510,13 @@ public class PirateChainACCTv3TradeBot implements AcctTradeBot {
// Determine P2SH-A address and confirm funded
byte[] redeemScriptA = PirateChainHTLC.buildScript(aliceForeignPublicKey, lockTimeA, tradeBotData.getTradeForeignPublicKey(), hashOfSecretA);
String p2shAddressA = pirateChain.deriveP2shAddress(redeemScriptA);
String p2shAddress = pirateChain.deriveP2shAddressBPrefix(redeemScriptA); // Use 'b' prefix when checking status
long feeTimestamp = calcFeeTimestamp(lockTimeA, crossChainTradeData.tradeTimeout);
long p2shFee = PirateChain.getInstance().getP2shFee(feeTimestamp);
final long minimumAmountA = tradeBotData.getForeignAmount() + p2shFee;
PirateChainHTLC.Status htlcStatusA = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddressA, minimumAmountA);
PirateChainHTLC.Status htlcStatusA = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmountA);
switch (htlcStatusA) {
case UNFUNDED:
@ -528,7 +528,7 @@ public class PirateChainACCTv3TradeBot implements AcctTradeBot {
case REDEEMED:
// We've already redeemed this?
TradeBot.updateTradeBotState(repository, tradeBotData, State.BOB_DONE,
() -> String.format("P2SH-A %s already spent? Assuming trade complete", p2shAddressA));
() -> String.format("P2SH-A %s already spent? Assuming trade complete", p2shAddress));
return;
case REFUND_IN_PROGRESS:
@ -600,13 +600,13 @@ public class PirateChainACCTv3TradeBot implements AcctTradeBot {
// Refund P2SH-A if we've passed lockTime-A
if (NTP.getTime() >= lockTimeA * 1000L) {
byte[] redeemScriptA = PirateChainHTLC.buildScript(tradeBotData.getTradeForeignPublicKey(), lockTimeA, crossChainTradeData.creatorForeignPKH, tradeBotData.getHashOfSecret());
String p2shAddressA = pirateChain.deriveP2shAddress(redeemScriptA);
String p2shAddress = pirateChain.deriveP2shAddressBPrefix(redeemScriptA); // Use 'b' prefix when checking status
long feeTimestamp = calcFeeTimestamp(lockTimeA, crossChainTradeData.tradeTimeout);
long p2shFee = PirateChain.getInstance().getP2shFee(feeTimestamp);
long minimumAmountA = crossChainTradeData.expectedForeignAmount + p2shFee;
PirateChainHTLC.Status htlcStatusA = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddressA, minimumAmountA);
PirateChainHTLC.Status htlcStatusA = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmountA);
switch (htlcStatusA) {
case UNFUNDED:
@ -618,21 +618,21 @@ public class PirateChainACCTv3TradeBot implements AcctTradeBot {
case REDEEMED:
// Already redeemed?
TradeBot.updateTradeBotState(repository, tradeBotData, State.ALICE_DONE,
() -> String.format("P2SH-A %s already spent? Assuming trade completed", p2shAddressA));
() -> String.format("P2SH-A %s already spent? Assuming trade completed", p2shAddress));
return;
case REFUND_IN_PROGRESS:
case REFUNDED:
TradeBot.updateTradeBotState(repository, tradeBotData, State.ALICE_REFUNDED,
() -> String.format("P2SH-A %s already refunded. Trade aborted", p2shAddressA));
() -> String.format("P2SH-A %s already refunded. Trade aborted", p2shAddress));
return;
}
TradeBot.updateTradeBotState(repository, tradeBotData, State.ALICE_REFUNDING_A,
() -> atData.getIsFinished()
? String.format("AT %s cancelled. Refunding P2SH-A %s - aborting trade", tradeBotData.getAtAddress(), p2shAddressA)
: String.format("LockTime-A reached, refunding P2SH-A %s - aborting trade", p2shAddressA));
? String.format("AT %s cancelled. Refunding P2SH-A %s - aborting trade", tradeBotData.getAtAddress(), p2shAddress)
: String.format("LockTime-A reached, refunding P2SH-A %s - aborting trade", p2shAddress));
return;
}
@ -735,7 +735,8 @@ public class PirateChainACCTv3TradeBot implements AcctTradeBot {
byte[] receivingAccountInfo = tradeBotData.getReceivingAccountInfo();
int lockTimeA = crossChainTradeData.lockTimeA;
byte[] redeemScriptA = PirateChainHTLC.buildScript(crossChainTradeData.partnerForeignPKH, lockTimeA, crossChainTradeData.creatorForeignPKH, crossChainTradeData.hashOfSecretA);
String p2shAddressA = pirateChain.deriveP2shAddress(redeemScriptA);
String p2shAddress = pirateChain.deriveP2shAddressBPrefix(redeemScriptA); // Use 'b' prefix when checking status
String p2shAddressT3 = pirateChain.deriveP2shAddress(redeemScriptA); // Use 't3' prefix when refunding
// Fee for redeem/refund is subtracted from P2SH-A balance.
long feeTimestamp = calcFeeTimestamp(lockTimeA, crossChainTradeData.tradeTimeout);
@ -743,7 +744,7 @@ public class PirateChainACCTv3TradeBot implements AcctTradeBot {
long minimumAmountA = crossChainTradeData.expectedForeignAmount + p2shFee;
String receivingAddress = Bech32.encode("zs", receivingAccountInfo);
PirateChainHTLC.Status htlcStatusA = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddressA, minimumAmountA);
PirateChainHTLC.Status htlcStatusA = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmountA);
switch (htlcStatusA) {
case UNFUNDED:
@ -763,7 +764,7 @@ public class PirateChainACCTv3TradeBot implements AcctTradeBot {
case FUNDED: {
// Get funding txid
String fundingTxidHex = PirateChainHTLC.getUnspentFundingTxid(pirateChain.getBlockchainProvider(), p2shAddressA, minimumAmountA);
String fundingTxidHex = PirateChainHTLC.getUnspentFundingTxid(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmountA);
if (fundingTxidHex == null) {
throw new ForeignBlockchainException("Missing funding txid when redeeming P2SH");
}
@ -776,7 +777,7 @@ public class PirateChainACCTv3TradeBot implements AcctTradeBot {
String privateKey58 = Base58.encode(privateKey);
String redeemScript58 = Base58.encode(redeemScriptA);
String txid = PirateChain.getInstance().redeemP2sh(tradeBotData.getForeignKey(), p2shAddressA,
String txid = PirateChain.getInstance().redeemP2sh(tradeBotData.getForeignKey(), p2shAddressT3,
receivingAddress, redeemAmount.value, redeemScript58, fundingTxid58, secret58, privateKey58);
LOGGER.info("Redeem txid: {}", txid);
break;
@ -807,13 +808,14 @@ public class PirateChainACCTv3TradeBot implements AcctTradeBot {
return;
byte[] redeemScriptA = PirateChainHTLC.buildScript(tradeBotData.getTradeForeignPublicKey(), lockTimeA, crossChainTradeData.creatorForeignPKH, tradeBotData.getHashOfSecret());
String p2shAddressA = pirateChain.deriveP2shAddress(redeemScriptA);
String p2shAddress = pirateChain.deriveP2shAddressBPrefix(redeemScriptA); // Use 'b' prefix when checking status
String p2shAddressT3 = pirateChain.deriveP2shAddress(redeemScriptA); // Use 't3' prefix when refunding
// Fee for redeem/refund is subtracted from P2SH-A balance.
long feeTimestamp = calcFeeTimestamp(lockTimeA, crossChainTradeData.tradeTimeout);
long p2shFee = PirateChain.getInstance().getP2shFee(feeTimestamp);
long minimumAmountA = crossChainTradeData.expectedForeignAmount + p2shFee;
PirateChainHTLC.Status htlcStatusA = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddressA, minimumAmountA);
PirateChainHTLC.Status htlcStatusA = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmountA);
switch (htlcStatusA) {
case UNFUNDED:
@ -825,7 +827,7 @@ public class PirateChainACCTv3TradeBot implements AcctTradeBot {
case REDEEMED:
// Too late!
TradeBot.updateTradeBotState(repository, tradeBotData, State.ALICE_DONE,
() -> String.format("P2SH-A %s already spent!", p2shAddressA));
() -> String.format("P2SH-A %s already spent!", p2shAddress));
return;
case REFUND_IN_PROGRESS:
@ -834,7 +836,7 @@ public class PirateChainACCTv3TradeBot implements AcctTradeBot {
case FUNDED:{
// Get funding txid
String fundingTxidHex = PirateChainHTLC.getUnspentFundingTxid(pirateChain.getBlockchainProvider(), p2shAddressA, minimumAmountA);
String fundingTxidHex = PirateChainHTLC.getUnspentFundingTxid(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmountA);
if (fundingTxidHex == null) {
throw new ForeignBlockchainException("Missing funding txid when refunding P2SH");
}
@ -846,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(), p2shAddressA,
String txid = PirateChain.getInstance().refundP2sh(tradeBotData.getForeignKey(), p2shAddressT3,
receivingAddress, refundAmount.value, redeemScript58, fundingTxid58, lockTimeA, privateKey58);
LOGGER.info("Refund txid: {}", txid);
break;
@ -854,7 +856,7 @@ public class PirateChainACCTv3TradeBot implements AcctTradeBot {
}
TradeBot.updateTradeBotState(repository, tradeBotData, State.ALICE_REFUNDED,
() -> String.format("LockTime-A reached. Refunded P2SH-A %s. Trade aborted", p2shAddressA));
() -> String.format("LockTime-A reached. Refunded P2SH-A %s. Trade aborted", p2shAddress));
}
/**

2
src/main/java/org/qortal/controller/tradebot/TradeBot.java

@ -299,7 +299,7 @@ public class TradeBot implements Listener {
return ECKey.fromPrivate(privateKey).getPubKey();
}
/*package*/ static byte[] generateSecret() {
/*package*/ public static byte[] generateSecret() {
byte[] secret = new byte[32];
RANDOM.nextBytes(secret);
return secret;

9
src/main/java/org/qortal/crosschain/PirateChain.java

@ -243,13 +243,20 @@ public class PirateChain extends Bitcoiny {
return walletKey != null && Base58.decode(walletKey).length == 32;
}
/** Returns P2SH address using passed redeem script. */
/** Returns 't3' prefixed P2SH address using passed redeem script. */
public String deriveP2shAddress(byte[] redeemScriptBytes) {
Context.propagate(bitcoinjContext);
byte[] redeemScriptHash = Crypto.hash160(redeemScriptBytes);
return LegacyZcashAddress.fromScriptHash(this.params, redeemScriptHash).toString();
}
/** Returns 'b' prefixed P2SH address using passed redeem script. */
public String deriveP2shAddressBPrefix(byte[] redeemScriptBytes) {
Context.propagate(bitcoinjContext);
byte[] redeemScriptHash = Crypto.hash160(redeemScriptBytes);
return LegacyAddress.fromScriptHash(this.params, redeemScriptHash).toString();
}
public Long getWalletBalance(String entropy58) throws ForeignBlockchainException {
synchronized (this) {
PirateChainWalletController walletController = PirateChainWalletController.getInstance();

32
src/test/java/org/qortal/test/crosschain/PirateChainTests.java

@ -9,7 +9,9 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.qortal.controller.tradebot.TradeBot;
import org.qortal.crosschain.*;
import org.qortal.crypto.Crypto;
import org.qortal.repository.DataException;
import org.qortal.test.common.Common;
import org.qortal.transform.TransformationException;
@ -84,6 +86,36 @@ public class PirateChainTests extends Common {
assertNotNull(rawTransaction);
}
@Test
public void testDeriveP2SHAddressWithT3Prefix() {
byte[] creatorTradePrivateKey = TradeBot.generateTradePrivateKey();
byte[] creatorTradeForeignPublicKey = TradeBot.deriveTradeForeignPublicKey(creatorTradePrivateKey);
byte[] tradePrivateKey = TradeBot.generateTradePrivateKey();
byte[] tradeForeignPublicKey = TradeBot.deriveTradeForeignPublicKey(tradePrivateKey);
byte[] secretA = TradeBot.generateSecret();
byte[] hashOfSecretA = Crypto.hash160(secretA);
int lockTime = 1653233550;
byte[] redeemScriptBytes = PirateChainHTLC.buildScript(tradeForeignPublicKey, lockTime, creatorTradeForeignPublicKey, hashOfSecretA);
String p2shAddress = PirateChain.getInstance().deriveP2shAddress(redeemScriptBytes);
assertTrue(p2shAddress.startsWith("t3"));
}
@Test
public void testDeriveP2SHAddressWithBPrefix() {
byte[] creatorTradePrivateKey = TradeBot.generateTradePrivateKey();
byte[] creatorTradeForeignPublicKey = TradeBot.deriveTradeForeignPublicKey(creatorTradePrivateKey);
byte[] tradePrivateKey = TradeBot.generateTradePrivateKey();
byte[] tradeForeignPublicKey = TradeBot.deriveTradeForeignPublicKey(tradePrivateKey);
byte[] secretA = TradeBot.generateSecret();
byte[] hashOfSecretA = Crypto.hash160(secretA);
int lockTime = 1653233550;
byte[] redeemScriptBytes = PirateChainHTLC.buildScript(tradeForeignPublicKey, lockTime, creatorTradeForeignPublicKey, hashOfSecretA);
String p2shAddress = PirateChain.getInstance().deriveP2shAddressBPrefix(redeemScriptBytes);
assertTrue(p2shAddress.startsWith("b"));
}
@Test
public void testHTLCStatusFunded() throws ForeignBlockchainException {
String p2shAddress = "ba6Q5HWrWtmfU2WZqQbrFdRYsafA45cUAt";

Loading…
Cancel
Save