From 098e2623d64dd3298ae9f11b1f560ad36766a7bf Mon Sep 17 00:00:00 2001 From: catbref Date: Tue, 28 Jul 2020 17:21:54 +0100 Subject: [PATCH] WIP: cross-chain AT now stores bitcoin receiving PKH --- .../model/CrossChainBitcoinRedeemRequest.java | 3 ++ .../api/model/CrossChainBuildRequest.java | 3 ++ .../api/model/TradeBotCreateRequest.java | 3 ++ .../api/resource/CrossChainResource.java | 35 +++++++++++++++++-- .../java/org/qortal/controller/TradeBot.java | 24 +++++++++++-- .../java/org/qortal/crosschain/BTCACCT.java | 16 +++++++-- .../java/org/qortal/crosschain/BTCP2SH.java | 14 +++++--- .../data/crosschain/CrossChainTradeData.java | 19 ++++++++-- .../java/org/qortal/test/btcacct/AtTests.java | 8 +++-- .../org/qortal/test/btcacct/DeployAT.java | 19 +++++++--- .../java/org/qortal/test/btcacct/Redeem.java | 2 +- 11 files changed, 124 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/qortal/api/model/CrossChainBitcoinRedeemRequest.java b/src/main/java/org/qortal/api/model/CrossChainBitcoinRedeemRequest.java index 5e95e36c..68129a3c 100644 --- a/src/main/java/org/qortal/api/model/CrossChainBitcoinRedeemRequest.java +++ b/src/main/java/org/qortal/api/model/CrossChainBitcoinRedeemRequest.java @@ -25,6 +25,9 @@ public class CrossChainBitcoinRedeemRequest { @Schema(description = "32-byte secret", example = "6gVbAXCVzJXAWwtAVGAfgAkkXpeXvPUwSciPmCfSfXJG") public byte[] secret; + @Schema(description = "Bitcoin HASH160(public key) for receiving funds, or omit to derive from private key", example = "u17kBVKkKSp12oUzaxFwNnq1JZf") + public byte[] receivePublicKeyHash; + public CrossChainBitcoinRedeemRequest() { } diff --git a/src/main/java/org/qortal/api/model/CrossChainBuildRequest.java b/src/main/java/org/qortal/api/model/CrossChainBuildRequest.java index e8d38703..dfc5dfd8 100644 --- a/src/main/java/org/qortal/api/model/CrossChainBuildRequest.java +++ b/src/main/java/org/qortal/api/model/CrossChainBuildRequest.java @@ -33,6 +33,9 @@ public class CrossChainBuildRequest { @Schema(description = "Trade time window (minutes) from trade agreement to automatic refund", example = "10080") public Integer tradeTimeout; + @Schema(description = "Bitcoin address for receiving", example = "1NCTG9oLk41bU6pcehLNo9DVJup77EHAVx") + public String receiveAddress; + public CrossChainBuildRequest() { } diff --git a/src/main/java/org/qortal/api/model/TradeBotCreateRequest.java b/src/main/java/org/qortal/api/model/TradeBotCreateRequest.java index 1898a989..386715bd 100644 --- a/src/main/java/org/qortal/api/model/TradeBotCreateRequest.java +++ b/src/main/java/org/qortal/api/model/TradeBotCreateRequest.java @@ -27,6 +27,9 @@ public class TradeBotCreateRequest { @Schema(description = "Suggested trade timeout (minutes)", example = "10080") public int tradeTimeout; + @Schema(description = "Bitcoin address for receiving", example = "1NCTG9oLk41bU6pcehLNo9DVJup77EHAVx") + public String receiveAddress; + public TradeBotCreateRequest() { } diff --git a/src/main/java/org/qortal/api/resource/CrossChainResource.java b/src/main/java/org/qortal/api/resource/CrossChainResource.java index a68eb0e5..51c33990 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainResource.java @@ -26,11 +26,13 @@ import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; 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.TransactionOutput; +import org.bitcoinj.script.Script.ScriptType; import org.qortal.account.Account; import org.qortal.account.PublicKeyAccount; import org.qortal.api.ApiError; @@ -179,11 +181,23 @@ public class CrossChainResource { if (tradeRequest.bitcoinAmount <= 0) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + Address bitcoinReceiveAddress; + try { + bitcoinReceiveAddress = Address.fromString(BTC.getInstance().getNetworkParameters(), tradeRequest.receiveAddress); + } catch (AddressFormatException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); + } + byte[] bitcoinReceivePublicKeyHash = bitcoinReceiveAddress.getHash(); + + // We only support P2PKH addresses at this time + if (bitcoinReceiveAddress.getOutputScriptType() != ScriptType.P2PKH) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); + try (final Repository repository = RepositoryManager.getRepository()) { PublicKeyAccount creatorAccount = new PublicKeyAccount(repository, creatorPublicKey); byte[] creationBytes = BTCACCT.buildQortalAT(creatorAccount.getAddress(), tradeRequest.bitcoinPublicKeyHash, tradeRequest.hashOfSecretB, - tradeRequest.qortAmount, tradeRequest.bitcoinAmount, tradeRequest.tradeTimeout); + tradeRequest.qortAmount, tradeRequest.bitcoinAmount, tradeRequest.tradeTimeout, bitcoinReceivePublicKeyHash); long txTimestamp = NTP.getTime(); byte[] lastReference = creatorAccount.getLastReference(); @@ -848,6 +862,12 @@ public class CrossChainResource { if (redeemRequest.secret == null || redeemRequest.secret.length != BTCACCT.SECRET_LENGTH) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + if (redeemRequest.receivePublicKeyHash == null) + redeemRequest.receivePublicKeyHash = redeemKey.getPubKeyHash(); + + if (redeemRequest.receivePublicKeyHash.length != 20) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY); + // Extract data from cross-chain trading AT try (final Repository repository = RepositoryManager.getRepository()) { ATData atData = fetchAtDataWithChecking(repository, redeemRequest.atAddress); @@ -888,7 +908,7 @@ public class CrossChainResource { Coin redeemAmount = Coin.valueOf(p2shBalance - redeemRequest.bitcoinMinerFee.unscaledValue().longValue()); - org.bitcoinj.core.Transaction redeemTransaction = BTCP2SH.buildRedeemTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, redeemRequest.secret); + org.bitcoinj.core.Transaction redeemTransaction = BTCP2SH.buildRedeemTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, redeemRequest.secret, redeemRequest.receivePublicKeyHash); boolean wasBroadcast = BTC.getInstance().broadcastTransaction(redeemTransaction); if (!wasBroadcast) @@ -950,6 +970,17 @@ public class CrossChainResource { public String tradeBotCreator(TradeBotCreateRequest tradeBotCreateRequest) { Security.checkApiCallAllowed(request); + Address receiveAddress; + try { + receiveAddress = Address.fromString(BTC.getInstance().getNetworkParameters(), tradeBotCreateRequest.receiveAddress); + } catch (AddressFormatException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); + } + + // We only support P2PKH addresses at this time + if (receiveAddress.getOutputScriptType() != ScriptType.P2PKH) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); + if (tradeBotCreateRequest.tradeTimeout < 60) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); diff --git a/src/main/java/org/qortal/controller/TradeBot.java b/src/main/java/org/qortal/controller/TradeBot.java index 91f23345..8d9577c3 100644 --- a/src/main/java/org/qortal/controller/TradeBot.java +++ b/src/main/java/org/qortal/controller/TradeBot.java @@ -8,10 +8,13 @@ import java.util.concurrent.atomic.AtomicBoolean; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.bitcoinj.core.Address; +import org.bitcoinj.core.AddressFormatException; import org.bitcoinj.core.Coin; import org.bitcoinj.core.ECKey; import org.bitcoinj.core.Transaction; import org.bitcoinj.core.TransactionOutput; +import org.bitcoinj.script.Script.ScriptType; import org.qortal.account.PrivateKeyAccount; import org.qortal.account.PublicKeyAccount; import org.qortal.api.model.TradeBotCreateRequest; @@ -103,6 +106,18 @@ public class TradeBot { byte[] tradeForeignPublicKey = deriveTradeForeignPublicKey(tradePrivateKey); byte[] tradeForeignPublicKeyHash = Crypto.hash160(tradeForeignPublicKey); + // Convert Bitcoin receive address into public key hash (we only support P2PKH at this time) + Address bitcoinReceiveAddress; + try { + bitcoinReceiveAddress = Address.fromString(BTC.getInstance().getNetworkParameters(), tradeBotCreateRequest.receiveAddress); + } catch (AddressFormatException e) { + throw new DataException("Unsupported Bitcoin receive address: " + tradeBotCreateRequest.receiveAddress); + } + if (bitcoinReceiveAddress.getOutputScriptType() != ScriptType.P2PKH) + throw new DataException("Unsupported Bitcoin receive address: " + tradeBotCreateRequest.receiveAddress); + + byte[] bitcoinReceivePublicKeyHash = bitcoinReceiveAddress.getHash(); + PublicKeyAccount creator = new PublicKeyAccount(repository, tradeBotCreateRequest.creatorPublicKey); // Deploy AT @@ -116,7 +131,8 @@ public class TradeBot { String description = "QORT/BTC cross-chain trade"; String aTType = "ACCT"; String tags = "ACCT QORT BTC"; - byte[] creationBytes = BTCACCT.buildQortalAT(tradeNativeAddress, tradeForeignPublicKeyHash, hashOfSecretB, tradeBotCreateRequest.qortAmount, tradeBotCreateRequest.bitcoinAmount, tradeBotCreateRequest.tradeTimeout); + byte[] creationBytes = BTCACCT.buildQortalAT(tradeNativeAddress, tradeForeignPublicKeyHash, hashOfSecretB, tradeBotCreateRequest.qortAmount, + tradeBotCreateRequest.bitcoinAmount, tradeBotCreateRequest.tradeTimeout, bitcoinReceivePublicKeyHash); long amount = tradeBotCreateRequest.fundingQortAmount; DeployAtTransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, aTType, tags, creationBytes, amount, Asset.QORT); @@ -699,8 +715,9 @@ public class TradeBot { Coin redeemAmount = Coin.ZERO; // The real funds are in P2SH-A ECKey redeemKey = ECKey.fromPrivate(tradeBotData.getTradePrivateKey()); List fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress); + byte[] receivePublicKeyHash = crossChainTradeData.creatorReceiveBitcoinPKH; - Transaction p2shRedeemTransaction = BTCP2SH.buildRedeemTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, tradeBotData.getSecret()); + Transaction p2shRedeemTransaction = BTCP2SH.buildRedeemTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, tradeBotData.getSecret(), receivePublicKeyHash); if (!BTC.getInstance().broadcastTransaction(p2shRedeemTransaction)) { // We couldn't redeem P2SH-B at this time @@ -846,8 +863,9 @@ public class TradeBot { Coin redeemAmount = Coin.valueOf(crossChainTradeData.expectedBitcoin); ECKey redeemKey = ECKey.fromPrivate(tradeBotData.getTradePrivateKey()); List fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress); + byte[] receivePublicKeyHash = crossChainTradeData.creatorReceiveBitcoinPKH; - Transaction p2shRedeemTransaction = BTCP2SH.buildRedeemTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, secretA); + Transaction p2shRedeemTransaction = BTCP2SH.buildRedeemTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, secretA, receivePublicKeyHash); if (!BTC.getInstance().broadcastTransaction(p2shRedeemTransaction)) { // We couldn't redeem P2SH-A at this time diff --git a/src/main/java/org/qortal/crosschain/BTCACCT.java b/src/main/java/org/qortal/crosschain/BTCACCT.java index cb87ca0f..da7ed7a1 100644 --- a/src/main/java/org/qortal/crosschain/BTCACCT.java +++ b/src/main/java/org/qortal/crosschain/BTCACCT.java @@ -92,7 +92,7 @@ public class BTCACCT { public static final int SECRET_LENGTH = 32; public static final int MIN_LOCKTIME = 1500000000; - public static final byte[] CODE_BYTES_HASH = HashCode.fromString("14ee2cb9899f582037901c384bab9ccdd41e48d8c98bf7df5cf79f4e8c236286").asBytes(); // SHA256 of AT code bytes + public static final byte[] CODE_BYTES_HASH = HashCode.fromString("58542b1d204d7034280fb85e8053c056353fcc9c3870c062a19b2fc17f764092").asBytes(); // SHA256 of AT code bytes public static class OfferMessageData { public byte[] recipientBitcoinPKH; @@ -117,7 +117,7 @@ public class BTCACCT { * @param tradeTimeout suggested timeout for entire trade * @return */ - public static byte[] buildQortalAT(String creatorTradeAddress, byte[] bitcoinPublicKeyHash, byte[] hashOfSecretB, long qortAmount, long bitcoinAmount, int tradeTimeout) { + public static byte[] buildQortalAT(String creatorTradeAddress, byte[] bitcoinPublicKeyHash, byte[] hashOfSecretB, long qortAmount, long bitcoinAmount, int tradeTimeout, byte[] bitcoinReceivePublicKeyHash) { // Labels for data segment addresses int addrCounter = 0; @@ -157,6 +157,9 @@ public class BTCACCT { final int addrMessageDataPointer = addrCounter++; final int addrMessageDataLength = addrCounter++; + final int addrBitcoinReceivePublicKeyHash = addrCounter; + addrCounter += 4; + final int addrEndOfConstants = addrCounter; // Variables @@ -280,6 +283,10 @@ public class BTCACCT { assert dataByteBuffer.position() == addrMessageDataLength * MachineState.VALUE_SIZE : "addrMessageDataLength incorrect"; dataByteBuffer.putLong(32L); + // Bitcoin receive public key hash + assert dataByteBuffer.position() == addrBitcoinReceivePublicKeyHash * MachineState.VALUE_SIZE : "addrBitcoinReceivePublicKeyHash incorrect"; + dataByteBuffer.put(Bytes.ensureCapacity(bitcoinReceivePublicKeyHash, 32, 0)); + assert dataByteBuffer.position() == addrEndOfConstants * MachineState.VALUE_SIZE : "dataByteBuffer position not at end of constants"; // Code labels @@ -619,6 +626,11 @@ public class BTCACCT { // Skip message data length dataByteBuffer.position(dataByteBuffer.position() + 8); + // Creator's Bitcoin/foreign receiving public key hash + tradeData.creatorReceiveBitcoinPKH = new byte[20]; + dataByteBuffer.get(tradeData.creatorReceiveBitcoinPKH); + dataByteBuffer.position(dataByteBuffer.position() + 32 - tradeData.creatorReceiveBitcoinPKH.length); // skip to 32 bytes + /* End of constants / begin variables */ // Skip AT creator's address diff --git a/src/main/java/org/qortal/crosschain/BTCP2SH.java b/src/main/java/org/qortal/crosschain/BTCP2SH.java index 0e8a2b0b..319fc5ac 100644 --- a/src/main/java/org/qortal/crosschain/BTCP2SH.java +++ b/src/main/java/org/qortal/crosschain/BTCP2SH.java @@ -76,16 +76,18 @@ public class BTCP2SH { * @param redeemScriptBytes the redeemScript itself, in byte[] form * @param lockTime (optional) transaction nLockTime, used in refund scenario * @param scriptSigBuilder function for building scriptSig using transaction input signature + * @param outputPublicKeyHash PKH used to create P2PKH output * @return Signed Bitcoin transaction for spending P2SH */ - public static Transaction buildP2shTransaction(Coin amount, ECKey spendKey, List fundingOutputs, byte[] redeemScriptBytes, Long lockTime, Function scriptSigBuilder) { + public static Transaction buildP2shTransaction(Coin amount, ECKey spendKey, List fundingOutputs, byte[] redeemScriptBytes, + Long lockTime, Function scriptSigBuilder, byte[] outputPublicKeyHash) { NetworkParameters params = BTC.getInstance().getNetworkParameters(); Transaction transaction = new Transaction(params); transaction.setVersion(2); // Output is back to P2SH funder - transaction.addOutput(amount, ScriptBuilder.createP2PKHOutputScript(spendKey.getPubKeyHash())); + transaction.addOutput(amount, ScriptBuilder.createP2PKHOutputScript(outputPublicKeyHash)); for (int inputIndex = 0; inputIndex < fundingOutputs.size(); ++inputIndex) { TransactionOutput fundingOutput = fundingOutputs.get(inputIndex); @@ -149,7 +151,8 @@ public class BTCP2SH { return scriptBuilder.build(); }; - return buildP2shTransaction(refundAmount, refundKey, fundingOutputs, redeemScriptBytes, lockTime, refundSigScriptBuilder); + // Send funds back to funding address + return buildP2shTransaction(refundAmount, refundKey, fundingOutputs, redeemScriptBytes, lockTime, refundSigScriptBuilder, refundKey.getPubKeyHash()); } /** @@ -160,9 +163,10 @@ public class BTCP2SH { * @param fundingOutput output from transaction that funded P2SH address * @param redeemScriptBytes the redeemScript itself, in byte[] form * @param secret actual 32-byte secret used when building redeemScript + * @param receivePublicKeyHash PKH used for output * @return Signed Bitcoin transaction for redeeming P2SH */ - public static Transaction buildRedeemTransaction(Coin redeemAmount, ECKey redeemKey, List fundingOutputs, byte[] redeemScriptBytes, byte[] secret) { + public static Transaction buildRedeemTransaction(Coin redeemAmount, ECKey redeemKey, List fundingOutputs, byte[] redeemScriptBytes, byte[] secret, byte[] receivePublicKeyHash) { Function redeemSigScriptBuilder = (txSigBytes) -> { // Build scriptSig with... ScriptBuilder scriptBuilder = new ScriptBuilder(); @@ -183,7 +187,7 @@ public class BTCP2SH { return scriptBuilder.build(); }; - return buildP2shTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, null, redeemSigScriptBuilder); + return buildP2shTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, null, redeemSigScriptBuilder, receivePublicKeyHash); } /** Returns 'secret', if any, given list of raw bitcoin transactions. */ diff --git a/src/main/java/org/qortal/data/crosschain/CrossChainTradeData.java b/src/main/java/org/qortal/data/crosschain/CrossChainTradeData.java index 99a7f5e5..1ad50218 100644 --- a/src/main/java/org/qortal/data/crosschain/CrossChainTradeData.java +++ b/src/main/java/org/qortal/data/crosschain/CrossChainTradeData.java @@ -26,9 +26,12 @@ public class CrossChainTradeData { @Schema(description = "AT creator's Qortal trade address") public String qortalCreatorTradeAddress; - @Schema(description = "AT creator's Bitcoin public-key-hash (PKH)") + @Schema(description = "AT creator's Bitcoin trade public-key-hash (PKH)") public byte[] creatorBitcoinPKH; + @Schema(description = "AT creator's Bitcoin receiving public-key-hash (PKH)") + public byte[] creatorReceiveBitcoinPKH; + @Schema(description = "Timestamp when AT was created (milliseconds since epoch)") public long creationTimestamp; @@ -84,7 +87,7 @@ public class CrossChainTradeData { // We can represent BitcoinPKH as an address @XmlElement(name = "creatorBitcoinAddress") - @Schema(description = "AT creator's Bitcoin PKH in address form") + @Schema(description = "AT creator's trading Bitcoin PKH in address form") public String getCreatorBitcoinAddress() { if (this.creatorBitcoinPKH == null) return null; @@ -94,7 +97,7 @@ public class CrossChainTradeData { // We can represent BitcoinPKH as an address @XmlElement(name = "recipientBitcoinAddress") - @Schema(description = "Trade partner's Bitcoin PKH in address form") + @Schema(description = "Trade partner's trading Bitcoin PKH in address form") public String getRecipientBitcoinAddress() { if (this.recipientBitcoinPKH == null) return null; @@ -102,4 +105,14 @@ public class CrossChainTradeData { return BTC.getInstance().pkhToAddress(this.recipientBitcoinPKH); } + // We can represent BitcoinPKH as an address + @XmlElement(name = "creatorBitcoinReceivingAddress") + @Schema(description = "AT creator's Bitcoin receiving address") + public String getCreatorBitcoinReceivingAddress() { + if (this.creatorReceiveBitcoinPKH == null) + return null; + + return BTC.getInstance().pkhToAddress(this.creatorReceiveBitcoinPKH); + } + } diff --git a/src/test/java/org/qortal/test/btcacct/AtTests.java b/src/test/java/org/qortal/test/btcacct/AtTests.java index 3f0fe919..c5150daa 100644 --- a/src/test/java/org/qortal/test/btcacct/AtTests.java +++ b/src/test/java/org/qortal/test/btcacct/AtTests.java @@ -19,6 +19,7 @@ import org.qortal.account.Account; import org.qortal.account.PrivateKeyAccount; import org.qortal.asset.Asset; import org.qortal.block.Block; +import org.qortal.crosschain.BTC; import org.qortal.crosschain.BTCACCT; import org.qortal.crypto.Crypto; import org.qortal.data.at.ATData; @@ -53,6 +54,7 @@ public class AtTests extends Common { public static final long redeemAmount = 80_40200000L; public static final long fundingAmount = 123_45600000L; public static final long bitcoinAmount = 864200L; + public static final byte[] bitcoinReceivePublicKeyHash = HashCode.fromString("00112233445566778899aabbccddeeff").asBytes(); @Before public void beforeTest() throws DataException { @@ -63,7 +65,7 @@ public class AtTests extends Common { public void testCompile() { Account deployer = Common.getTestAccount(null, "chloe"); - byte[] creationBytes = BTCACCT.buildQortalAT(deployer.getAddress(), bitcoinPublicKeyHash, hashOfSecretB, redeemAmount, bitcoinAmount, tradeTimeout); + byte[] creationBytes = BTCACCT.buildQortalAT(deployer.getAddress(), bitcoinPublicKeyHash, hashOfSecretB, redeemAmount, bitcoinAmount, tradeTimeout, bitcoinReceivePublicKeyHash); System.out.println("CIYAM AT creation bytes: " + HashCode.fromBytes(creationBytes).toString()); } @@ -526,7 +528,7 @@ public class AtTests extends Common { } private DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer) throws DataException { - byte[] creationBytes = BTCACCT.buildQortalAT(deployer.getAddress(), bitcoinPublicKeyHash, hashOfSecretB, redeemAmount, bitcoinAmount, tradeTimeout); + byte[] creationBytes = BTCACCT.buildQortalAT(deployer.getAddress(), bitcoinPublicKeyHash, hashOfSecretB, redeemAmount, bitcoinAmount, tradeTimeout, bitcoinReceivePublicKeyHash); long txTimestamp = System.currentTimeMillis(); byte[] lastReference = deployer.getLastReference(); @@ -616,6 +618,7 @@ public class AtTests extends Common { + "\tHASH160 of secret-B: %s,\n" + "\tredeem payout: %s QORT,\n" + "\texpected bitcoin: %s BTC,\n" + + "\treceiving bitcoin address: %s,\n" + "\tcurrent block height: %d,\n", tradeData.qortalAtAddress, tradeData.qortalCreator, @@ -625,6 +628,7 @@ public class AtTests extends Common { HashCode.fromBytes(tradeData.hashOfSecretB).toString().substring(0, 40), Amounts.prettyAmount(tradeData.qortAmount), Amounts.prettyAmount(tradeData.expectedBitcoin), + BTC.getInstance().pkhToAddress(tradeData.creatorReceiveBitcoinPKH), currentBlockHeight)); // Are we in 'offer' or 'trade' stage? diff --git a/src/test/java/org/qortal/test/btcacct/DeployAT.java b/src/test/java/org/qortal/test/btcacct/DeployAT.java index 56e75150..4f33353d 100644 --- a/src/test/java/org/qortal/test/btcacct/DeployAT.java +++ b/src/test/java/org/qortal/test/btcacct/DeployAT.java @@ -2,10 +2,13 @@ package org.qortal.test.btcacct; import java.security.Security; +import org.bitcoinj.core.Address; +import org.bitcoinj.script.Script.ScriptType; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.qortal.account.PrivateKeyAccount; import org.qortal.asset.Asset; import org.qortal.controller.Controller; +import org.qortal.crosschain.BTC; import org.qortal.crosschain.BTCACCT; import org.qortal.data.transaction.BaseTransactionData; import org.qortal.data.transaction.DeployAtTransactionData; @@ -34,7 +37,7 @@ public class DeployAT { if (error != null) System.err.println(error); - System.err.println(String.format("usage: DeployAT 50000) usage("Trade timeout (minutes) must be between 60 and 50000"); + + Address receiveAddress = Address.fromString(BTC.getInstance().getNetworkParameters(), args[argIndex++]); + if (receiveAddress.getOutputScriptType() != ScriptType.P2PKH) + usage("Bitcoin receive address must be P2PKH form"); + + bitcoinReceivePublicKeyHash = receiveAddress.getHash(); } catch (IllegalArgumentException e) { usage(String.format("Invalid argument %d: %s", argIndex, e.getMessage())); } @@ -114,7 +125,7 @@ public class DeployAT { System.out.println(String.format("HASH160 of secret: %s", HashCode.fromBytes(secretHash))); // Deploy AT - byte[] creationBytes = BTCACCT.buildQortalAT(refundAccount.getAddress(), bitcoinPublicKeyHash, secretHash, redeemAmount, expectedBitcoin, tradeTimeout); + byte[] creationBytes = BTCACCT.buildQortalAT(refundAccount.getAddress(), bitcoinPublicKeyHash, secretHash, redeemAmount, expectedBitcoin, tradeTimeout, bitcoinReceivePublicKeyHash); System.out.println("CIYAM AT creation bytes: " + HashCode.fromBytes(creationBytes).toString()); long txTimestamp = System.currentTimeMillis(); diff --git a/src/test/java/org/qortal/test/btcacct/Redeem.java b/src/test/java/org/qortal/test/btcacct/Redeem.java index 40968450..761d4796 100644 --- a/src/test/java/org/qortal/test/btcacct/Redeem.java +++ b/src/test/java/org/qortal/test/btcacct/Redeem.java @@ -182,7 +182,7 @@ public class Redeem { Coin redeemAmount = Coin.valueOf(p2shBalance).subtract(bitcoinFee); System.out.println(String.format("Spending %s of output, with %s as mining fee", BTC.format(redeemAmount), BTC.format(bitcoinFee))); - Transaction redeemTransaction = BTCP2SH.buildRedeemTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, secret); + Transaction redeemTransaction = BTCP2SH.buildRedeemTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, secret, redeemAddress.getHash()); byte[] redeemBytes = redeemTransaction.bitcoinSerialize();