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.
This commit is contained in:
CalDescent 2022-05-23 23:14:54 +01:00
parent 767ef62b64
commit 6b53eb5384
4 changed files with 65 additions and 24 deletions

View File

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

View File

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

View File

@ -243,13 +243,20 @@ public class PirateChain extends Bitcoiny {
return walletKey != null && Base58.decode(walletKey).length == 32; 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) { public String deriveP2shAddress(byte[] redeemScriptBytes) {
Context.propagate(bitcoinjContext); Context.propagate(bitcoinjContext);
byte[] redeemScriptHash = Crypto.hash160(redeemScriptBytes); byte[] redeemScriptHash = Crypto.hash160(redeemScriptBytes);
return LegacyZcashAddress.fromScriptHash(this.params, redeemScriptHash).toString(); 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 { public Long getWalletBalance(String entropy58) throws ForeignBlockchainException {
synchronized (this) { synchronized (this) {
PirateChainWalletController walletController = PirateChainWalletController.getInstance(); PirateChainWalletController walletController = PirateChainWalletController.getInstance();

View File

@ -9,7 +9,9 @@ import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.qortal.controller.tradebot.TradeBot;
import org.qortal.crosschain.*; import org.qortal.crosschain.*;
import org.qortal.crypto.Crypto;
import org.qortal.repository.DataException; import org.qortal.repository.DataException;
import org.qortal.test.common.Common; import org.qortal.test.common.Common;
import org.qortal.transform.TransformationException; import org.qortal.transform.TransformationException;
@ -84,6 +86,36 @@ public class PirateChainTests extends Common {
assertNotNull(rawTransaction); 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 @Test
public void testHTLCStatusFunded() throws ForeignBlockchainException { public void testHTLCStatusFunded() throws ForeignBlockchainException {
String p2shAddress = "ba6Q5HWrWtmfU2WZqQbrFdRYsafA45cUAt"; String p2shAddress = "ba6Q5HWrWtmfU2WZqQbrFdRYsafA45cUAt";