diff --git a/lib/org/ciyam/at/1.2/at-1.2.jar b/lib/org/ciyam/at/1.2/at-1.2.jar
new file mode 100644
index 00000000..862c37c6
Binary files /dev/null and b/lib/org/ciyam/at/1.2/at-1.2.jar differ
diff --git a/lib/org/ciyam/at/1.2/at-1.2.pom b/lib/org/ciyam/at/1.2/at-1.2.pom
new file mode 100644
index 00000000..dd16b150
--- /dev/null
+++ b/lib/org/ciyam/at/1.2/at-1.2.pom
@@ -0,0 +1,9 @@
+
+
+ *
+ * OP_DUP OP_SHA256 push(0x20) <SHA256 of secret> OP_EQUAL + * OP_IF + * OP_DROP OP_DUP OP_HASH160 push(0x14) <HASH160 of recipient pubkey> + * OP_EQUALVERIFY OP_CHECKSIG + * OP_ELSE + * push(0x04) <refund locktime> OP_CHECKLOCKTIMEVERIFY + * OP_DROP OP_DUP OP_HASH160 push(0x14) <HASH160 of sender pubkey> + * OP_EQUALVERIFY OP_CHECKSIG + * OP_ENDIF + *+ * + * @param secretHash + * @param senderPubKey + * @param recipientPubKey + * @param lockTime + * @return + */ + public static byte[] buildRedeemScript(byte[] secretHash, byte[] senderPubKey, byte[] recipientPubKey, long lockTime) { + byte[] senderPubKeyHash160 = BTC.hash160(senderPubKey); + byte[] recipientPubKeyHash160 = BTC.hash160(recipientPubKey); + + return Bytes.concat(redeemScript1, secretHash, redeemScript2, recipientPubKeyHash160, redeemScript3, BitTwiddling.toLEByteArray((int) (lockTime & 0xffffffffL)), + redeemScript4, senderPubKeyHash160, redeemScript5); + } + + public static byte[] buildCiyamAT(byte[] secretHash, byte[] destinationQortalPubKey, long refundMinutes) { + // Labels for data segment addresses + int addrCounter = 0; + final int addrHashPart1 = addrCounter++; + final int addrHashPart2 = addrCounter++; + final int addrHashPart3 = addrCounter++; + final int addrHashPart4 = addrCounter++; + final int addrAddressPart1 = addrCounter++; + final int addrAddressPart2 = addrCounter++; + final int addrAddressPart3 = addrCounter++; + final int addrAddressPart4 = addrCounter++; + final int addrRefundMinutes = addrCounter++; + final int addrRefundTimestamp = addrCounter++; + final int addrLastTimestamp = addrCounter++; + final int addrBlockTimestamp = addrCounter++; + final int addrTxType = addrCounter++; + final int addrComparator = addrCounter++; + final int addrAddressTemp1 = addrCounter++; + final int addrAddressTemp2 = addrCounter++; + final int addrAddressTemp3 = addrCounter++; + final int addrAddressTemp4 = addrCounter++; + + // Data segment + ByteBuffer dataByteBuffer = ByteBuffer.allocate(addrCounter * 8).order(ByteOrder.LITTLE_ENDIAN); + + // Hash of secret into HashPart1-4 + dataByteBuffer.put(secretHash); + + // Destination Qortal account's public key + dataByteBuffer.put(destinationQortalPubKey); + + // Expiry in minutes + dataByteBuffer.putLong(refundMinutes); + + // Code labels + final int addrTxLoop = 0x36; + final int addrCheckTx = 0x4b; + final int addrCheckSender = 0x64; + final int addrCheckMessage = 0xab; + final int addrPayout = 0xdf; + final int addrRefund = 0x102; + final int addrEndOfCode = 0x109; + + int tempPC; + ByteBuffer codeByteBuffer = ByteBuffer.allocate(addrEndOfCode * 1).order(ByteOrder.LITTLE_ENDIAN); + + // init: + codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_CREATION_TIMESTAMP.value).putInt(addrRefundTimestamp); + codeByteBuffer.put(OpCode.SET_DAT.value).putInt(addrLastTimestamp).putInt(addrRefundTimestamp); + codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.value).putShort(FunctionCode.ADD_MINUTES_TO_TIMESTAMP.value).putInt(addrRefundTimestamp) + .putInt(addrRefundTimestamp).putInt(addrRefundMinutes); + codeByteBuffer.put(OpCode.SET_PCS.value); + + // loop: + codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_BLOCK_TIMESTAMP.value).putInt(addrBlockTimestamp); + tempPC = codeByteBuffer.position(); + codeByteBuffer.put(OpCode.BLT_DAT.value).putInt(addrBlockTimestamp).putInt(addrRefundTimestamp).put((byte) (addrTxLoop - tempPC)); + codeByteBuffer.put(OpCode.JMP_ADR.value).putInt(addrRefund); + + // txloop: + assert codeByteBuffer.position() == addrTxLoop : "addrTxLoop incorrect"; + codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.PUT_TX_AFTER_TIMESTAMP_IN_A.value).putInt(addrLastTimestamp); + codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_A_IS_ZERO.value).putInt(addrComparator); + tempPC = codeByteBuffer.position(); + codeByteBuffer.put(OpCode.BZR_DAT.value).putInt(addrComparator).put((byte) (addrCheckTx - tempPC)); + codeByteBuffer.put(OpCode.STP_IMD.value); + + // checkTx: + codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_TIMESTAMP_FROM_TX_IN_A.value).putInt(addrLastTimestamp); + codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_TYPE_FROM_TX_IN_A.value).putInt(addrTxType); + tempPC = codeByteBuffer.position(); + codeByteBuffer.put(OpCode.BNZ_DAT.value).putInt(addrTxType).put((byte) (addrCheckSender - tempPC)); + codeByteBuffer.put(OpCode.JMP_ADR.value).putInt(addrTxLoop); + + // checkSender + assert codeByteBuffer.position() == addrCheckSender : "addrCheckSender incorrect"; + codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PUT_ADDRESS_FROM_TX_IN_A_INTO_B.value); + codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_B1.value).putInt(addrAddressTemp1); + codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_B2.value).putInt(addrAddressTemp2); + codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_B3.value).putInt(addrAddressTemp3); + codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_B4.value).putInt(addrAddressTemp4); + tempPC = codeByteBuffer.position(); + codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(addrAddressTemp1).putInt(addrAddressPart1).put((byte) (addrTxLoop - tempPC)); + tempPC = codeByteBuffer.position(); + codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(addrAddressTemp2).putInt(addrAddressPart2).put((byte) (addrTxLoop - tempPC)); + tempPC = codeByteBuffer.position(); + codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(addrAddressTemp3).putInt(addrAddressPart3).put((byte) (addrTxLoop - tempPC)); + tempPC = codeByteBuffer.position(); + codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(addrAddressTemp4).putInt(addrAddressPart4).put((byte) (addrTxLoop - tempPC)); + + // checkMessage: + assert codeByteBuffer.position() == addrCheckMessage : "addrCheckMessage incorrect"; + codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PUT_MESSAGE_FROM_TX_IN_A_INTO_B.value); + codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.SWAP_A_AND_B.value); + codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B1.value).putInt(addrHashPart1); + codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B2.value).putInt(addrHashPart2); + codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B3.value).putInt(addrHashPart3); + codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B4.value).putInt(addrHashPart4); + codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_SHA256_A_WITH_B.value).putInt(addrComparator); + tempPC = codeByteBuffer.position(); + codeByteBuffer.put(OpCode.BNZ_DAT.value).putInt(addrComparator).put((byte) (addrPayout - tempPC)); + codeByteBuffer.put(OpCode.JMP_ADR.value).putInt(addrTxLoop); + + // payout: + assert codeByteBuffer.position() == addrPayout : "addrPayout incorrect"; + codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B1.value).putInt(addrAddressPart1); + codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B2.value).putInt(addrAddressPart2); + codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B3.value).putInt(addrAddressPart3); + codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B4.value).putInt(addrAddressPart4); + codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.MESSAGE_A_TO_ADDRESS_IN_B.value); + codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PAY_ALL_TO_ADDRESS_IN_B.value); + codeByteBuffer.put(OpCode.FIN_IMD.value); + + // refund: + assert codeByteBuffer.position() == addrRefund : "addrRefund incorrect"; + codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PUT_CREATOR_INTO_B.value); + codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PAY_ALL_TO_ADDRESS_IN_B.value); + codeByteBuffer.put(OpCode.FIN_IMD.value); + + // end-of-code + assert codeByteBuffer.position() == addrEndOfCode : "addrEndOfCode incorrect"; + + final short ciyamAtVersion = 2; + final short numCallStackPages = 0; + final short numUserStackPages = 0; + final long minActivationAmount = 0L; + + return MachineState.toCreationBytes(ciyamAtVersion, codeByteBuffer.array(), dataByteBuffer.array(), numCallStackPages, numUserStackPages, minActivationAmount); + } + +} diff --git a/src/main/java/org/qortal/crosschain/BTC.java b/src/main/java/org/qortal/crosschain/BTC.java index 17b5cc66..83a8bb07 100644 --- a/src/main/java/org/qortal/crosschain/BTC.java +++ b/src/main/java/org/qortal/crosschain/BTC.java @@ -14,6 +14,7 @@ import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.security.DigestOutputStream; import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.Date; import java.util.List; import java.util.concurrent.ExecutionException; @@ -47,8 +48,28 @@ import org.qortal.settings.Settings; public class BTC { - private static class RollbackBlockChain extends BlockChain { + private static final MessageDigest RIPE_MD160_DIGESTER; + private static final MessageDigest SHA256_DIGESTER; + static { + try { + RIPE_MD160_DIGESTER = MessageDigest.getInstance("RIPEMD160"); + SHA256_DIGESTER = MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + private static BTC instance; + + private static File directory; + private static String chainFileName; + private static String checkpointsFileName; + + private static NetworkParameters params; + private static PeerGroup peerGroup; + private static BlockStore blockStore; + + private static class RollbackBlockChain extends BlockChain { public RollbackBlockChain(NetworkParameters params, BlockStore blockStore) throws BlockStoreException { super(params, blockStore); } @@ -57,11 +78,10 @@ public class BTC { public void setChainHead(StoredBlock chainHead) throws BlockStoreException { super.setChainHead(chainHead); } - } + private static RollbackBlockChain chain; private static class UpdateableCheckpointManager extends CheckpointManager implements NewBestBlockListener { - private static final int checkpointInterval = 500; private static final String minimalTestNet3TextFile = "TXT CHECKPOINTS 1\n0\n1\nAAAAAAAAB+EH4QfhAAAH4AEAAAApmwX6UCEnJcYIKTa7HO3pFkqqNhAzJVBMdEuGAAAAAPSAvVCBUypCbBW/OqU0oIF7ISF84h2spOqHrFCWN9Zw6r6/T///AB0E5oOO\n"; @@ -130,23 +150,24 @@ public class BTC { } } } - } - - private static BTC instance; - private static final Object instanceLock = new Object(); - - private static File directory; - private static String chainFileName; - private static String checkpointsFileName; - - private static NetworkParameters params; - private static PeerGroup peerGroup; - private static BlockStore blockStore; - private static RollbackBlockChain chain; private static UpdateableCheckpointManager manager; private BTC() { + } + + public static synchronized BTC getInstance() { + if (instance == null) + instance = new BTC(); + + return instance; + } + + public static byte[] hash160(byte[] message) { + return RIPE_MD160_DIGESTER.digest(SHA256_DIGESTER.digest(message)); + } + + public void start() { // Start wallet if (Settings.getInstance().useBitcoinTestNet()) { params = TestNet3Params.get(); @@ -196,20 +217,11 @@ public class BTC { peerGroup.start(); } - public static synchronized BTC getInstance() { + public synchronized void shutdown() { if (instance == null) - instance = new BTC(); + return; - return instance; - } - - public void shutdown() { - synchronized (instanceLock) { - if (instance == null) - return; - - instance = null; - } + instance = null; peerGroup.stop(); diff --git a/src/main/java/org/qortal/utils/BitTwiddling.java b/src/main/java/org/qortal/utils/BitTwiddling.java index e17e6034..ada2c2f5 100644 --- a/src/main/java/org/qortal/utils/BitTwiddling.java +++ b/src/main/java/org/qortal/utils/BitTwiddling.java @@ -21,4 +21,9 @@ public class BitTwiddling { return maxValue; } + /** Convert int to little-endian byte array */ + public static byte[] toLEByteArray(int value) { + return new byte[] { (byte) (value), (byte) (value >> 8), (byte) (value >> 16), (byte) (value >> 24) }; + } + } diff --git a/src/test/java/org/qora/test/btcacct/Initiate1.java b/src/test/java/org/qora/test/btcacct/Initiate1.java new file mode 100644 index 00000000..859a7d2e --- /dev/null +++ b/src/test/java/org/qora/test/btcacct/Initiate1.java @@ -0,0 +1,107 @@ +package org.qora.test.btcacct; + +import java.security.SecureRandom; +import java.security.Security; + +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.PublicKeyAccount; +import org.qora.crosschain.BTC; +import org.qora.crosschain.BTCACCT; +import org.qora.crypto.Crypto; +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 Initiate1 { + + private static final long REFUND_TIMEOUT = 600L; // seconds + + private static void usage() { + System.err.println(String.format("usage: Initiate1