diff --git a/src/main/java/org/qortal/api/model/CrossChainBuildRequest.java b/src/main/java/org/qortal/api/model/CrossChainBuildRequest.java index c4fa097a..834eda6f 100644 --- a/src/main/java/org/qortal/api/model/CrossChainBuildRequest.java +++ b/src/main/java/org/qortal/api/model/CrossChainBuildRequest.java @@ -24,6 +24,9 @@ public class CrossChainBuildRequest { @XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class) public long fundingQortAmount; + @Schema(description = "HASH160 of creator's Bitcoin public key", example = "2daMveGc5pdjRyFacbxBzMksCbyC") + public byte[] bitcoinPublicKeyHash; + @Schema(description = "HASH160 of secret", example = "43vnftqkjxrhb5kJdkU1ZFQLEnWV") public byte[] secretHash; diff --git a/src/main/java/org/qortal/api/resource/CrossChainResource.java b/src/main/java/org/qortal/api/resource/CrossChainResource.java index b7cbfb1d..f78f5000 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainResource.java @@ -180,7 +180,8 @@ public class CrossChainResource { try (final Repository repository = RepositoryManager.getRepository()) { PublicKeyAccount creatorAccount = new PublicKeyAccount(repository, creatorPublicKey); - byte[] creationBytes = BTCACCT.buildQortalAT(creatorAccount.getAddress(), tradeRequest.secretHash, tradeRequest.tradeTimeout, tradeRequest.initialQortAmount, tradeRequest.finalQortAmount, tradeRequest.bitcoinAmount); + byte[] creationBytes = BTCACCT.buildQortalAT(creatorAccount.getAddress(), tradeRequest.bitcoinPublicKeyHash, tradeRequest.secretHash, + tradeRequest.tradeTimeout, tradeRequest.initialQortAmount, tradeRequest.finalQortAmount, tradeRequest.bitcoinAmount); long txTimestamp = NTP.getTime(); byte[] lastReference = creatorAccount.getLastReference(); diff --git a/src/main/java/org/qortal/controller/TradeBot.java b/src/main/java/org/qortal/controller/TradeBot.java index 0a16bfbe..d8e9b9e8 100644 --- a/src/main/java/org/qortal/controller/TradeBot.java +++ b/src/main/java/org/qortal/controller/TradeBot.java @@ -11,15 +11,11 @@ import org.bitcoinj.core.ECKey; import org.bitcoinj.core.LegacyAddress; import org.bitcoinj.core.NetworkParameters; import org.qortal.account.PrivateKeyAccount; -import org.qortal.api.ApiError; -import org.qortal.api.ApiExceptionFactory; import org.qortal.crosschain.BTC; import org.qortal.crosschain.BTCACCT; import org.qortal.crypto.Crypto; -import org.qortal.data.at.ATData; import org.qortal.data.crosschain.CrossChainTradeData; import org.qortal.data.crosschain.TradeBotData; -import org.qortal.data.crosschain.CrossChainTradeData.Mode; import org.qortal.repository.DataException; import org.qortal.repository.Repository; import org.qortal.repository.RepositoryManager; diff --git a/src/main/java/org/qortal/crosschain/BTCACCT.java b/src/main/java/org/qortal/crosschain/BTCACCT.java index a0246d04..758c7b91 100644 --- a/src/main/java/org/qortal/crosschain/BTCACCT.java +++ b/src/main/java/org/qortal/crosschain/BTCACCT.java @@ -237,6 +237,7 @@ public class BTCACCT { * 32-byte secret to the AT, before the AT automatically refunds the AT's creator. * * @param qortalCreator Qortal address for AT creator, also used for refunds + * @param bitcoinPublicKeyHash 20-byte HASH160 of creator's bitcoin public key * @param secretHash 20-byte HASH160 of 32-byte secret * @param tradeTimeout how many minutes, from start of 'trade mode' until AT auto-refunds AT creator * @param initialPayout how much QORT to pay trade partner upon switch to 'trade mode' @@ -244,7 +245,7 @@ public class BTCACCT { * @param bitcoinAmount how much BTC the AT creator is expecting to trade * @return */ - public static byte[] buildQortalAT(String qortalCreator, byte[] secretHash, int tradeTimeout, long initialPayout, long redeemPayout, long bitcoinAmount) { + public static byte[] buildQortalAT(String qortalCreator, byte[] bitcoinPublicKeyHash, byte[] secretHash, int tradeTimeout, long initialPayout, long redeemPayout, long bitcoinAmount) { // Labels for data segment addresses int addrCounter = 0; @@ -255,6 +256,9 @@ public class BTCACCT { final int addrQortalCreator3 = addrCounter++; final int addrQortalCreator4 = addrCounter++; + final int addrBitcoinPublickeyHash = addrCounter; + addrCounter += 4; + final int addrSecretHash = addrCounter; addrCounter += 4; @@ -303,6 +307,10 @@ public class BTCACCT { byte[] qortalCreatorBytes = Base58.decode(qortalCreator); dataByteBuffer.put(Bytes.ensureCapacity(qortalCreatorBytes, 32, 0)); + // Bitcoin public key hash + assert dataByteBuffer.position() == addrBitcoinPublickeyHash * MachineState.VALUE_SIZE : "addrBitcoinPublicKeyHash incorrect"; + dataByteBuffer.put(Bytes.ensureCapacity(bitcoinPublicKeyHash, 32, 0)); + // Hash of secret assert dataByteBuffer.position() == addrSecretHash * MachineState.VALUE_SIZE : "addrSecretHash incorrect"; dataByteBuffer.put(Bytes.ensureCapacity(secretHash, 32, 0)); @@ -559,6 +567,11 @@ public class BTCACCT { // Skip AT creator address dataByteBuffer.position(dataByteBuffer.position() + 32); + // Bitcoin/foreign public key hash + tradeData.foreignPublicKeyHash = new byte[20]; + dataByteBuffer.get(tradeData.foreignPublicKeyHash); + dataByteBuffer.position(dataByteBuffer.position() + 32 - 20); // skip to 32 bytes + // Hash of secret tradeData.secretHash = new byte[20]; dataByteBuffer.get(tradeData.secretHash); diff --git a/src/main/java/org/qortal/data/crosschain/CrossChainTradeData.java b/src/main/java/org/qortal/data/crosschain/CrossChainTradeData.java index 8c9b6602..a19ef81d 100644 --- a/src/main/java/org/qortal/data/crosschain/CrossChainTradeData.java +++ b/src/main/java/org/qortal/data/crosschain/CrossChainTradeData.java @@ -59,6 +59,8 @@ public class CrossChainTradeData { @Schema(description = "Suggested Bitcoin P2SH nLockTime based on trade timeout") public Integer lockTime; + public byte[] foreignPublicKeyHash; + // Constructors // Necessary for JAXB diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java index 4c70b825..54f8f3c3 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java @@ -620,12 +620,12 @@ public class HSQLDBDatabaseUpdates { case 20: // Trade bot - stmt.execute("CREATE TABLE TradeBotStates (trade_private_key QortalPrivateKey NOT NULL, trade_state TINYINT NOT NULL, " + stmt.execute("CREATE TABLE TradeBotStates (trade_private_key QortalKeySeed NOT NULL, trade_state TINYINT NOT NULL, " + "trade_native_public_key QortalPublicKey NOT NULL, trade_native_public_key_hash VARBINARY(32) NOT NULL, " + "secret VARBINARY(32) NOT NULL, secret_hash VARBINARY(32) NOT NULL, " + "trade_foreign_public_key QortalPublicKey NOT NULL, trade_foreign_public_key_hash VARBINARY(32) NOT NULL, " + "at_address QortalAddress, " - + "last_transaction_signature Signature, PRIMARY KEY (trade_private_key)"); + + "last_transaction_signature Signature, PRIMARY KEY (trade_private_key))"); break; default: diff --git a/src/test/java/org/qortal/test/btcacct/AtTests.java b/src/test/java/org/qortal/test/btcacct/AtTests.java index 19fd7340..2026424f 100644 --- a/src/test/java/org/qortal/test/btcacct/AtTests.java +++ b/src/test/java/org/qortal/test/btcacct/AtTests.java @@ -44,6 +44,7 @@ import com.google.common.primitives.Bytes; public class AtTests extends Common { public static final byte[] secret = "This string is exactly 32 bytes!".getBytes(); + public static final byte[] bitcoinPublicKeyHash = new byte[20]; // not used in tests public static final byte[] secretHash = Crypto.hash160(secret); // daf59884b4d1aec8c1b17102530909ee43c0151a public static final int refundTimeout = 10; // blocks public static final long initialPayout = 100000L; @@ -60,7 +61,7 @@ public class AtTests extends Common { public void testCompile() { Account deployer = Common.getTestAccount(null, "chloe"); - byte[] creationBytes = BTCACCT.buildQortalAT(deployer.getAddress(), secretHash, refundTimeout, initialPayout, redeemAmount, bitcoinAmount); + byte[] creationBytes = BTCACCT.buildQortalAT(deployer.getAddress(), bitcoinPublicKeyHash, secretHash, refundTimeout, initialPayout, redeemAmount, bitcoinAmount); System.out.println("CIYAM AT creation bytes: " + HashCode.fromBytes(creationBytes).toString()); } @@ -434,7 +435,7 @@ public class AtTests extends Common { } private DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer) throws DataException { - byte[] creationBytes = BTCACCT.buildQortalAT(deployer.getAddress(), secretHash, refundTimeout, initialPayout, redeemAmount, bitcoinAmount); + byte[] creationBytes = BTCACCT.buildQortalAT(deployer.getAddress(), bitcoinPublicKeyHash, secretHash, refundTimeout, initialPayout, redeemAmount, bitcoinAmount); long txTimestamp = System.currentTimeMillis(); byte[] lastReference = deployer.getLastReference(); diff --git a/src/test/java/org/qortal/test/btcacct/DeployAT.java b/src/test/java/org/qortal/test/btcacct/DeployAT.java index 98672164..dec9f563 100644 --- a/src/test/java/org/qortal/test/btcacct/DeployAT.java +++ b/src/test/java/org/qortal/test/btcacct/DeployAT.java @@ -34,11 +34,12 @@ public class DeployAT { if (error != null) System.err.println(error); - System.err.println(String.format("usage: DeployAT ")); + System.err.println(String.format("usage: DeployAT ")); System.err.println(String.format("example: DeployAT " + "AdTd9SUEYSdTW8mgK3Gu72K97bCHGdUwi2VvLNjUohot \\\n" + "\t80.4020 \\\n" + "\t0.00864200 \\\n" + + "\t750b06757a2448b8a4abebaa6e4662833fd5ddbb \\\n" + "\tdaf59884b4d1aec8c1b17102530909ee43c0151a \\\n" + "\t0.0001 \\\n" + "\t123.456 \\\n" @@ -56,6 +57,7 @@ public class DeployAT { byte[] refundPrivateKey = null; long redeemAmount = 0; long expectedBitcoin = 0; + byte[] bitcoinPublicKeyHash = null; byte[] secretHash = null; long initialPayout = 0; long fundingAmount = 0; @@ -75,6 +77,10 @@ public class DeployAT { if (expectedBitcoin <= 0) usage("Expected BTC amount must be positive"); + bitcoinPublicKeyHash = HashCode.fromString(args[argIndex++]).asBytes(); + if (bitcoinPublicKeyHash.length != 20) + usage("Bitcoin PKH must be 20 bytes"); + secretHash = HashCode.fromString(args[argIndex++]).asBytes(); if (secretHash.length != 20) usage("Hash of secret must be 20 bytes"); @@ -114,7 +120,7 @@ public class DeployAT { System.out.println(String.format("HASH160 of secret: %s", HashCode.fromBytes(secretHash))); // Deploy AT - byte[] creationBytes = BTCACCT.buildQortalAT(refundAccount.getAddress(), secretHash, tradeTimeout, initialPayout, redeemAmount, expectedBitcoin); + byte[] creationBytes = BTCACCT.buildQortalAT(refundAccount.getAddress(), bitcoinPublicKeyHash, secretHash, tradeTimeout, initialPayout, redeemAmount, expectedBitcoin); System.out.println("CIYAM AT creation bytes: " + HashCode.fromBytes(creationBytes).toString()); long txTimestamp = System.currentTimeMillis(); diff --git a/src/test/java/org/qortal/test/common/AccountUtils.java b/src/test/java/org/qortal/test/common/AccountUtils.java index 59722ae1..0e7ef020 100644 --- a/src/test/java/org/qortal/test/common/AccountUtils.java +++ b/src/test/java/org/qortal/test/common/AccountUtils.java @@ -20,15 +20,19 @@ public class AccountUtils { public static final int txGroupId = Group.NO_GROUP; public static final long fee = 1L * Amounts.MULTIPLIER; - public static void pay(Repository repository, String sender, String recipient, long amount) throws DataException { - PrivateKeyAccount sendingAccount = Common.getTestAccount(repository, sender); - PrivateKeyAccount recipientAccount = Common.getTestAccount(repository, recipient); + public static void pay(Repository repository, String testSenderName, String testRecipientName, long amount) throws DataException { + PrivateKeyAccount sendingAccount = Common.getTestAccount(repository, testSenderName); + PrivateKeyAccount recipientAccount = Common.getTestAccount(repository, testRecipientName); + pay(repository, sendingAccount, recipientAccount.getAddress(), amount); + } + + public static void pay(Repository repository, PrivateKeyAccount sendingAccount, String recipientAddress, long amount) throws DataException { byte[] reference = sendingAccount.getLastReference(); long timestamp = repository.getTransactionRepository().fromSignature(reference).getTimestamp() + 1; BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, sendingAccount.getPublicKey(), fee, null); - TransactionData transactionData = new PaymentTransactionData(baseTransactionData, recipientAccount.getAddress(), amount); + TransactionData transactionData = new PaymentTransactionData(baseTransactionData, recipientAddress, amount); TransactionUtils.signAndMint(repository, transactionData, sendingAccount); }