3
0
mirror of https://github.com/Qortal/qortal.git synced 2025-02-11 17:55:50 +00:00

BTC-ACCT progress

Bump bitcoinj to 0.15.5 for fixes.

lockTime is int (seconds since epoch), not long (ms since epoch).

Improve output of Initiate1.

Added (most of) Respond2.
This commit is contained in:
catbref 2019-11-25 09:54:53 +00:00
parent d58b7c1f53
commit 369a45f5c0
6 changed files with 256 additions and 13 deletions

Binary file not shown.

View File

@ -8,6 +8,6 @@
<version>1.0</version>
<version>1.2</version>
</versions>
<lastUpdated>20191120104937</lastUpdated>
<lastUpdated>20191121173210</lastUpdated>
</versioning>
</metadata>

View File

@ -6,7 +6,7 @@
<version>1.0.6</version>
<packaging>jar</packaging>
<properties>
<bitcoin.version>0.15.4</bitcoin.version>
<bitcoinj.version>0.15.5</bitcoinj.version>
<bouncycastle.version>1.64</bouncycastle.version>
<build.timestamp>${maven.build.timestamp}</build.timestamp>
<commons-net.version>3.6</commons-net.version>
@ -406,13 +406,13 @@
<dependency>
<groupId>org.ciyam</groupId>
<artifactId>at</artifactId>
<version>1.0</version>
<version>1.2</version>
</dependency>
<!-- Bitcoin support -->
<dependency>
<groupId>org.bitcoinj</groupId>
<artifactId>bitcoinj-core</artifactId>
<version>${bitcoin.version}</version>
<version>${bitcoinj.version}</version>
</dependency>
<!-- Utilities -->
<dependency>

View File

@ -3,6 +3,18 @@ package org.qora.crosschain;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionOutPoint;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptBuilder;
import org.bitcoinj.wallet.Wallet;
import org.bitcoinj.script.Script.ScriptType;
import org.ciyam.at.FunctionCode;
import org.ciyam.at.MachineState;
import org.ciyam.at.OpCode;
@ -40,7 +52,7 @@ public class BTCACCT {
* @param lockTime
* @return
*/
public static byte[] buildRedeemScript(byte[] secretHash, byte[] senderPubKey, byte[] recipientPubKey, long lockTime) {
public static byte[] buildRedeemScript(byte[] secretHash, byte[] senderPubKey, byte[] recipientPubKey, int lockTime) {
byte[] senderPubKeyHash160 = BTC.hash160(senderPubKey);
byte[] recipientPubKeyHash160 = BTC.hash160(recipientPubKey);

View File

@ -1,19 +1,39 @@
package org.qora.test.btcacct;
import java.io.File;
import java.math.BigDecimal;
import java.security.SecureRandom;
import java.security.Security;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.LegacyAddress;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionBroadcast;
import org.bitcoinj.kits.WalletAppKit;
import org.bitcoinj.params.TestNet3Params;
import org.bitcoinj.script.Script.ScriptType;
import org.bitcoinj.wallet.SendRequest;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.qora.account.PrivateKeyAccount;
import org.qora.account.PublicKeyAccount;
import org.qora.controller.Controller;
import org.qora.crosschain.BTC;
import org.qora.crosschain.BTCACCT;
import org.qora.crypto.Crypto;
import org.qora.repository.DataException;
import org.qora.repository.Repository;
import org.qora.repository.RepositoryFactory;
import org.qora.repository.RepositoryManager;
import org.qora.repository.hsqldb.HSQLDBRepositoryFactory;
import org.qora.utils.Base58;
import com.google.common.hash.HashCode;
@ -41,8 +61,8 @@ public class Initiate1 {
private static final long REFUND_TIMEOUT = 600L; // seconds
private static void usage() {
System.err.println(String.format("usage: Initiate1 <your-QORT-pubkey> <your-BTC-pubkey> <QORT-amount> <BTC-amount> <their-QORT-pubkey> <their-BTC-pubkey>"));
System.err.println(String.format("example: Initiate1 6rNn9b3pYRrG9UKqzMWYZ9qa8F3Zgv2mVWrULGHUusb \\\n"
System.err.println(String.format("usage: Initiate1 <your-QORT-PRIVkey> <your-BTC-pubkey> <QORT-amount> <BTC-amount> <their-QORT-pubkey> <their-BTC-pubkey>"));
System.err.println(String.format("example: Initiate1 pYQ6DpQBJ2n72TCLJLScEvwhf3boxWy2kQEPynakwpj \\\n"
+ "\t03aa20871c2195361f2826c7a649eab6b42639630c4d8c33c55311d5c1e476b5d6 \\\n"
+ "\t123 0.00008642 \\\n"
+ "\tJBNBQQDzZsm5do1BrwWAp53Ps4KYJVt749EGpCf7ofte \\\n"
@ -57,16 +77,29 @@ public class Initiate1 {
Security.insertProviderAt(new BouncyCastleProvider(), 0);
NetworkParameters params = TestNet3Params.get();
String yourQortPubKey58 = args[0];
String yourBitcoinPubKeyHex = args[1];
int argIndex = 0;
String yourQortPrivKey58 = args[argIndex++];
String yourBitcoinPubKeyHex = args[argIndex++];
String theirBitcoinPubKeyHex = args[5];
String rawQortAmount = args[argIndex++];
String rawBitcoinAmount = args[argIndex++];
String theirQortPubKey58 = args[argIndex++];
String theirBitcoinPubKeyHex = args[argIndex++];
try {
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.getRepositoryUrl());
RepositoryManager.setRepositoryFactory(repositoryFactory);
} catch (DataException e) {
throw new RuntimeException("Repository startup issue: " + e.getMessage());
}
try (final Repository repository = RepositoryManager.getRepository()) {
System.out.println("Confirm the following is correct based on the info you've given:");
byte[] yourQortPubKey = Base58.decode(yourQortPubKey58);
PublicKeyAccount yourQortalAccount = new PublicKeyAccount(null, yourQortPubKey);
byte[] yourQortPrivKey = Base58.decode(yourQortPrivKey58);
PrivateKeyAccount yourQortalAccount = new PrivateKeyAccount(repository, yourQortPrivKey);
byte[] yourQortPubKey = yourQortalAccount.getPublicKey();
System.out.println(String.format("Your Qortal address: %s", yourQortalAccount.getAddress()));
byte[] yourBitcoinPubKey = HashCode.fromString(yourBitcoinPubKeyHex).asBytes();
@ -74,6 +107,10 @@ public class Initiate1 {
Address yourBitcoinAddress = Address.fromKey(params, yourBitcoinKey, ScriptType.P2PKH);
System.out.println(String.format("Your Bitcoin address: %s", yourBitcoinAddress.toString()));
byte[] theirQortPubKey = Base58.decode(theirQortPubKey58);
PublicKeyAccount theirQortalAccount = new PublicKeyAccount(repository, theirQortPubKey);
System.out.println(String.format("Their Qortal address: %s", theirQortalAccount.getAddress()));
byte[] theirBitcoinPubKey = HashCode.fromString(theirBitcoinPubKeyHex).asBytes();
ECKey theirBitcoinKey = ECKey.fromPublicOnly(theirBitcoinPubKey);
Address theirBitcoinAddress = Address.fromKey(params, theirBitcoinKey, ScriptType.P2PKH);
@ -91,7 +128,9 @@ public class Initiate1 {
byte[] secretHash = Crypto.digest(secret);
System.out.println("Hash of secret: " + HashCode.fromBytes(secretHash).toString());
long lockTime = System.currentTimeMillis() + REFUND_TIMEOUT;
int lockTime = (int) ((System.currentTimeMillis() / 1000L) + REFUND_TIMEOUT);
System.out.println(String.format("Redeem script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneId.systemDefault()).toString(), lockTime));
byte[] redeemScriptBytes = BTCACCT.buildRedeemScript(secretHash, yourBitcoinPubKey, theirBitcoinPubKey, lockTime);
System.out.println("Redeem script: " + HashCode.fromBytes(redeemScriptBytes).toString());
@ -99,8 +138,17 @@ public class Initiate1 {
Address p2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash);
System.out.println("P2SH address: " + p2shAddress.toString());
Coin bitcoinAmount = Coin.parseCoin(rawBitcoinAmount);
// Fund P2SH
System.out.println(String.format("\nYou need to fund %s with %s BTC", p2shAddress.toString(), bitcoinAmount.toPlainString()));
System.out.println("Once this is done, responder should run Respond2 to check P2SH funding and create AT");
} catch (NumberFormatException e) {
usage();
} catch (DataException e) {
throw new RuntimeException("Repository issue: " + e.getMessage());
}
}

View File

@ -0,0 +1,183 @@
package org.qora.test.btcacct;
import java.math.BigDecimal;
import java.security.SecureRandom;
import java.security.Security;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.LegacyAddress;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.params.TestNet3Params;
import org.bitcoinj.script.Script.ScriptType;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.qora.account.PrivateKeyAccount;
import org.qora.account.PublicKeyAccount;
import org.qora.asset.Asset;
import org.qora.controller.Controller;
import org.qora.crosschain.BTC;
import org.qora.crosschain.BTCACCT;
import org.qora.crypto.Crypto;
import org.qora.data.transaction.BaseTransactionData;
import org.qora.data.transaction.DeployAtTransactionData;
import org.qora.data.transaction.TransactionData;
import org.qora.group.Group;
import org.qora.repository.DataException;
import org.qora.repository.Repository;
import org.qora.repository.RepositoryFactory;
import org.qora.repository.RepositoryManager;
import org.qora.repository.hsqldb.HSQLDBRepositoryFactory;
import org.qora.transaction.DeployAtTransaction;
import org.qora.transaction.Transaction;
import org.qora.utils.Base58;
import com.google.common.hash.HashCode;
/**
* Initiator must be Qora-chain so that initiator can send initial message to BTC P2SH then Qora can scan for P2SH add send corresponding message to Qora AT.
*
* Initiator (wants Qora, has BTC)
* Funds BTC P2SH address
*
* Responder (has Qora, wants BTC)
* Builds Qora ACCT AT and funds it with Qora
*
* Initiator sends recipient+secret+script as input to BTC P2SH address, releasing BTC amount - fees to responder
*
* Qora nodes scan for P2SH output, checks amount and recipient and if ok sends secret to Qora ACCT AT
* (Or it's possible to feed BTC transaction details into Qora AT so it can check them itself?)
*
* Qora ACCT AT sends its Qora to initiator
*
*/
public class Respond2 {
private static final long REFUND_TIMEOUT = 600L; // seconds
private static void usage() {
System.err.println(String.format("usage: Respond2 <your-QORT-PRIVkey> <your-BTC-pubkey> <QORT-amount> <BTC-amount> <their-QORT-pubkey> <their-BTC-pubkey> <hash-of-secret> <locktime> <P2SH-address>"));
System.err.println(String.format("example: Respond2 pYQ6DpQBJ2n72TCLJLScEvwhf3boxWy2kQEPynakwpj \\\n"
+ "\t03aa20871c2195361f2826c7a649eab6b42639630c4d8c33c55311d5c1e476b5d6 \\\n"
+ "\t123 0.00008642 \\\n"
+ "\tJBNBQQDzZsm5do1BrwWAp53Ps4KYJVt749EGpCf7ofte \\\n"
+ "\t032783606be32a3e639a33afe2b15f058708ab124f3b290d595ee954390a0c8559 \\\n"
+ "\te43f5ab106b70df2e85656de30e1862891752f81e82f5dfd03abb8465a7346f9 1574441679 2N4R2pSEzLcJgtgAbFuLvviwwEkBrmq6sx4"));
System.exit(1);
}
public static void main(String[] args) {
if (args.length != 9)
usage();
Security.insertProviderAt(new BouncyCastleProvider(), 0);
NetworkParameters params = TestNet3Params.get();
int argIndex = 0;
String yourQortPrivKey58 = args[argIndex++];
String yourBitcoinPubKeyHex = args[argIndex++];
String rawQortAmount = args[argIndex++];
String rawBitcoinAmount = args[argIndex++];
String theirQortPubKey58 = args[argIndex++];
String theirBitcoinPubKeyHex = args[argIndex++];
String secretHashHex = args[argIndex++];
String rawLockTime = args[argIndex++];
String rawP2shAddress = args[argIndex++];
try {
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.getRepositoryUrl());
RepositoryManager.setRepositoryFactory(repositoryFactory);
} catch (DataException e) {
throw new RuntimeException("Repository startup issue: " + e.getMessage());
}
try (final Repository repository = RepositoryManager.getRepository()) {
System.out.println("Confirm the following is correct based on the info you've given:");
byte[] yourQortPrivKey = Base58.decode(yourQortPrivKey58);
PrivateKeyAccount yourQortalAccount = new PrivateKeyAccount(repository, yourQortPrivKey);
byte[] yourQortPubKey = yourQortalAccount.getPublicKey();
System.out.println(String.format("Your Qortal address: %s", yourQortalAccount.getAddress()));
byte[] yourBitcoinPubKey = HashCode.fromString(yourBitcoinPubKeyHex).asBytes();
ECKey yourBitcoinKey = ECKey.fromPublicOnly(yourBitcoinPubKey);
Address yourBitcoinAddress = Address.fromKey(params, yourBitcoinKey, ScriptType.P2PKH);
System.out.println(String.format("Your Bitcoin address: %s", yourBitcoinAddress.toString()));
byte[] theirQortPubKey = Base58.decode(theirQortPubKey58);
PublicKeyAccount theirQortalAccount = new PublicKeyAccount(repository, theirQortPubKey);
System.out.println(String.format("Their Qortal address: %s", theirQortalAccount.getAddress()));
byte[] theirBitcoinPubKey = HashCode.fromString(theirBitcoinPubKeyHex).asBytes();
ECKey theirBitcoinKey = ECKey.fromPublicOnly(theirBitcoinPubKey);
Address theirBitcoinAddress = Address.fromKey(params, theirBitcoinKey, ScriptType.P2PKH);
System.out.println(String.format("Their Bitcoin address: %s", theirBitcoinAddress.toString()));
System.out.println("Hash of secret: " + secretHashHex);
// New/derived info
System.out.println("\nCHECKING info from other party:");
int lockTime = Integer.valueOf(rawLockTime);
System.out.println(String.format("Redeem script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneId.systemDefault()).toString(), lockTime));
byte[] secretHash = HashCode.fromString(secretHashHex).asBytes();
System.out.println("Hash of secret: " + HashCode.fromBytes(secretHash).toString());
byte[] redeemScriptBytes = BTCACCT.buildRedeemScript(secretHash, theirBitcoinPubKey, yourBitcoinPubKey, lockTime);
System.out.println("Redeem script: " + HashCode.fromBytes(redeemScriptBytes).toString());
byte[] redeemScriptHash = BTC.hash160(redeemScriptBytes);
Address p2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash);
System.out.println("P2SH address: " + p2shAddress.toString());
if (!p2shAddress.toString().equals(rawP2shAddress)) {
System.err.println(String.format("Derived P2SH address %s does not match given address %s", p2shAddress.toString(), rawP2shAddress));
System.exit(2);
}
// TODO: Check for funded P2SH
System.out.println("\nYour response:");
// If good, deploy AT
byte[] creationBytes = BTCACCT.buildCiyamAT(secretHash, theirQortPubKey, REFUND_TIMEOUT / 60);
System.out.println("CIYAM AT creation bytes: " + HashCode.fromBytes(creationBytes).toString());
BigDecimal qortAmount = new BigDecimal(rawQortAmount).setScale(8);
long txTimestamp = System.currentTimeMillis();
byte[] lastReference = yourQortalAccount.getLastReference();
if (lastReference == null) {
System.err.println(String.format("Qortal account %s has no last reference", yourQortalAccount.getAddress()));
System.exit(2);
}
BigDecimal fee = BigDecimal.ZERO;
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, yourQortPubKey, fee, null);
TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, "QORT-BTC", "QORT-BTC ACCT", "", "", creationBytes, qortAmount, Asset.QORT);
Transaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData);
fee = deployAtTransaction.calcRecommendedFee();
deployAtTransactionData.setFee(fee);
deployAtTransaction.sign(yourQortalAccount);
} catch (NumberFormatException e) {
usage();
} catch (DataException e) {
throw new RuntimeException("Repository issue: " + e.getMessage());
}
}
}