WIP: cross-chain trading AT passes AtTests now

This commit is contained in:
catbref
2020-06-18 12:22:35 +01:00
parent 23062c59cd
commit 886c9156a5
7 changed files with 374 additions and 94 deletions

View File

@@ -13,6 +13,8 @@ import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.function.ToIntFunction;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.DELETE;
@@ -392,9 +394,9 @@ public class CrossChainResource {
}
@POST
@Path("/p2sh")
@Path("/p2sh/a")
@Operation(
summary = "Returns Bitcoin P2SH address based on trade info",
summary = "Returns Bitcoin P2SH-A address based on trade info",
requestBody = @RequestBody(
required = true,
content = @Content(
@@ -411,7 +413,35 @@ public class CrossChainResource {
}
)
@ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE})
public String deriveP2sh(CrossChainBitcoinTemplateRequest templateRequest) {
public String deriveP2shA(CrossChainBitcoinTemplateRequest templateRequest) {
return deriveP2sh(templateRequest, (crossChainTradeData) -> crossChainTradeData.lockTimeA, (crossChainTradeData) -> crossChainTradeData.hashOfSecretA);
}
@POST
@Path("/p2sh/b")
@Operation(
summary = "Returns Bitcoin P2SH-B address based on trade info",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(
implementation = CrossChainBitcoinTemplateRequest.class
)
)
),
responses = {
@ApiResponse(
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string"))
)
}
)
@ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE})
public String deriveP2shB(CrossChainBitcoinTemplateRequest templateRequest) {
return deriveP2sh(templateRequest, (crossChainTradeData) -> crossChainTradeData.lockTimeB, (crossChainTradeData) -> crossChainTradeData.hashOfSecretB);
}
private String deriveP2sh(CrossChainBitcoinTemplateRequest templateRequest, ToIntFunction<CrossChainTradeData> lockTimeFn, Function<CrossChainTradeData, byte[]> hashOfSecretFn) {
BTC btc = BTC.getInstance();
NetworkParameters params = btc.getNetworkParameters();
@@ -432,7 +462,7 @@ public class CrossChainResource {
if (crossChainTradeData.mode == Mode.OFFER)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
byte[] redeemScriptBytes = BTCP2SH.buildScript(templateRequest.refundPublicKeyHash, crossChainTradeData.lockTime, templateRequest.redeemPublicKeyHash, crossChainTradeData.hashOfSecretB);
byte[] redeemScriptBytes = BTCP2SH.buildScript(templateRequest.refundPublicKeyHash, lockTimeFn.applyAsInt(crossChainTradeData), templateRequest.redeemPublicKeyHash, hashOfSecretFn.apply(crossChainTradeData));
byte[] redeemScriptHash = Crypto.hash160(redeemScriptBytes);
Address p2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash);
@@ -443,9 +473,9 @@ public class CrossChainResource {
}
@POST
@Path("/p2sh/check")
@Path("/p2sh/a/check")
@Operation(
summary = "Checks Bitcoin P2SH address based on trade info",
summary = "Checks Bitcoin P2SH-A address based on trade info",
requestBody = @RequestBody(
required = true,
content = @Content(
@@ -462,7 +492,35 @@ public class CrossChainResource {
}
)
@ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.INVALID_ADDRESS, ApiError.ADDRESS_UNKNOWN, ApiError.REPOSITORY_ISSUE})
public CrossChainBitcoinP2SHStatus checkP2sh(CrossChainBitcoinTemplateRequest templateRequest) {
public CrossChainBitcoinP2SHStatus checkP2shA(CrossChainBitcoinTemplateRequest templateRequest) {
return checkP2sh(templateRequest, (crossChainTradeData) -> crossChainTradeData.lockTimeA, (crossChainTradeData) -> crossChainTradeData.hashOfSecretA);
}
@POST
@Path("/p2sh/b/check")
@Operation(
summary = "Checks Bitcoin P2SH-B address based on trade info",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(
implementation = CrossChainBitcoinTemplateRequest.class
)
)
),
responses = {
@ApiResponse(
content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = CrossChainBitcoinP2SHStatus.class))
)
}
)
@ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.INVALID_ADDRESS, ApiError.ADDRESS_UNKNOWN, ApiError.REPOSITORY_ISSUE})
public CrossChainBitcoinP2SHStatus checkP2shB(CrossChainBitcoinTemplateRequest templateRequest) {
return checkP2sh(templateRequest, (crossChainTradeData) -> crossChainTradeData.lockTimeB, (crossChainTradeData) -> crossChainTradeData.hashOfSecretB);
}
private CrossChainBitcoinP2SHStatus checkP2sh(CrossChainBitcoinTemplateRequest templateRequest, ToIntFunction<CrossChainTradeData> lockTimeFn, Function<CrossChainTradeData, byte[]> hashOfSecretFn) {
BTC btc = BTC.getInstance();
NetworkParameters params = btc.getNetworkParameters();
@@ -483,7 +541,10 @@ public class CrossChainResource {
if (crossChainTradeData.mode == Mode.OFFER)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
byte[] redeemScriptBytes = BTCP2SH.buildScript(templateRequest.refundPublicKeyHash, crossChainTradeData.lockTime, templateRequest.redeemPublicKeyHash, crossChainTradeData.hashOfSecretB);
int lockTime = lockTimeFn.applyAsInt(crossChainTradeData);
byte[] hashOfSecret = hashOfSecretFn.apply(crossChainTradeData);
byte[] redeemScriptBytes = BTCP2SH.buildScript(templateRequest.refundPublicKeyHash, lockTime, templateRequest.redeemPublicKeyHash, hashOfSecret);
byte[] redeemScriptHash = Crypto.hash160(redeemScriptBytes);
Address p2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash);
@@ -508,7 +569,7 @@ public class CrossChainResource {
if (p2shBalance >= crossChainTradeData.expectedBitcoin && !fundingOutputs.isEmpty()) {
p2shStatus.canRedeem = now >= medianBlockTime * 1000L;
p2shStatus.canRefund = now >= crossChainTradeData.lockTime * 1000L;
p2shStatus.canRefund = now >= lockTime * 1000L;
}
if (now >= medianBlockTime * 1000L) {
@@ -524,9 +585,9 @@ public class CrossChainResource {
}
@POST
@Path("/p2sh/refund")
@Path("/p2sh/a/refund")
@Operation(
summary = "Returns serialized Bitcoin transaction attempting refund from P2SH address",
summary = "Returns serialized Bitcoin transaction attempting refund from P2SH-A address",
requestBody = @RequestBody(
required = true,
content = @Content(
@@ -544,7 +605,36 @@ public class CrossChainResource {
)
@ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.INVALID_ADDRESS, ApiError.ADDRESS_UNKNOWN,
ApiError.BTC_TOO_SOON, ApiError.BTC_BALANCE_ISSUE, ApiError.BTC_NETWORK_ISSUE, ApiError.REPOSITORY_ISSUE})
public String refundP2sh(CrossChainBitcoinRefundRequest refundRequest) {
public String refundP2shA(CrossChainBitcoinRefundRequest refundRequest) {
return refundP2sh(refundRequest, (crossChainTradeData) -> crossChainTradeData.lockTimeA, (crossChainTradeData) -> crossChainTradeData.hashOfSecretA);
}
@POST
@Path("/p2sh/b/refund")
@Operation(
summary = "Returns serialized Bitcoin transaction attempting refund from P2SH-B address",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(
implementation = CrossChainBitcoinRefundRequest.class
)
)
),
responses = {
@ApiResponse(
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string"))
)
}
)
@ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.INVALID_ADDRESS, ApiError.ADDRESS_UNKNOWN,
ApiError.BTC_TOO_SOON, ApiError.BTC_BALANCE_ISSUE, ApiError.BTC_NETWORK_ISSUE, ApiError.REPOSITORY_ISSUE})
public String refundP2shB(CrossChainBitcoinRefundRequest refundRequest) {
return refundP2sh(refundRequest, (crossChainTradeData) -> crossChainTradeData.lockTimeB, (crossChainTradeData) -> crossChainTradeData.hashOfSecretB);
}
private String refundP2sh(CrossChainBitcoinRefundRequest refundRequest, ToIntFunction<CrossChainTradeData> lockTimeFn, Function<CrossChainTradeData, byte[]> hashOfSecretFn) {
BTC btc = BTC.getInstance();
NetworkParameters params = btc.getNetworkParameters();
@@ -580,7 +670,10 @@ public class CrossChainResource {
if (crossChainTradeData.mode == Mode.OFFER)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
byte[] redeemScriptBytes = BTCP2SH.buildScript(refundKey.getPubKeyHash(), crossChainTradeData.lockTime, refundRequest.redeemPublicKeyHash, crossChainTradeData.hashOfSecretB);
int lockTime = lockTimeFn.applyAsInt(crossChainTradeData);
byte[] hashOfSecret = hashOfSecretFn.apply(crossChainTradeData);
byte[] redeemScriptBytes = BTCP2SH.buildScript(refundKey.getPubKeyHash(), lockTime, refundRequest.redeemPublicKeyHash, hashOfSecret);
byte[] redeemScriptHash = Crypto.hash160(redeemScriptBytes);
Address p2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash);
@@ -597,7 +690,7 @@ public class CrossChainResource {
if (fundingOutputs.isEmpty())
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
boolean canRefund = now >= crossChainTradeData.lockTime * 1000L;
boolean canRefund = now >= lockTime * 1000L;
if (!canRefund)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BTC_TOO_SOON);
@@ -606,7 +699,7 @@ public class CrossChainResource {
Coin refundAmount = Coin.valueOf(p2shBalance - refundRequest.bitcoinMinerFee.unscaledValue().longValue());
org.bitcoinj.core.Transaction refundTransaction = BTCP2SH.buildRefundTransaction(refundAmount, refundKey, fundingOutputs, redeemScriptBytes, crossChainTradeData.lockTime);
org.bitcoinj.core.Transaction refundTransaction = BTCP2SH.buildRefundTransaction(refundAmount, refundKey, fundingOutputs, redeemScriptBytes, lockTime);
boolean wasBroadcast = BTC.getInstance().broadcastTransaction(refundTransaction);
if (!wasBroadcast)
@@ -619,9 +712,9 @@ public class CrossChainResource {
}
@POST
@Path("/p2sh/redeem")
@Path("/p2sh/a/redeem")
@Operation(
summary = "Returns serialized Bitcoin transaction attempting redeem from P2SH address",
summary = "Returns serialized Bitcoin transaction attempting redeem from P2SH-A address",
requestBody = @RequestBody(
required = true,
content = @Content(
@@ -639,7 +732,36 @@ public class CrossChainResource {
)
@ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.INVALID_ADDRESS, ApiError.ADDRESS_UNKNOWN,
ApiError.BTC_TOO_SOON, ApiError.BTC_BALANCE_ISSUE, ApiError.BTC_NETWORK_ISSUE, ApiError.REPOSITORY_ISSUE})
public String redeemP2sh(CrossChainBitcoinRedeemRequest redeemRequest) {
public String redeemP2shA(CrossChainBitcoinRedeemRequest redeemRequest) {
return redeemP2sh(redeemRequest, (crossChainTradeData) -> crossChainTradeData.lockTimeA, (crossChainTradeData) -> crossChainTradeData.hashOfSecretA);
}
@POST
@Path("/p2sh/b/redeem")
@Operation(
summary = "Returns serialized Bitcoin transaction attempting redeem from P2SH-B address",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(
implementation = CrossChainBitcoinRedeemRequest.class
)
)
),
responses = {
@ApiResponse(
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string"))
)
}
)
@ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.INVALID_ADDRESS, ApiError.ADDRESS_UNKNOWN,
ApiError.BTC_TOO_SOON, ApiError.BTC_BALANCE_ISSUE, ApiError.BTC_NETWORK_ISSUE, ApiError.REPOSITORY_ISSUE})
public String redeemP2shB(CrossChainBitcoinRedeemRequest redeemRequest) {
return redeemP2sh(redeemRequest, (crossChainTradeData) -> crossChainTradeData.lockTimeB, (crossChainTradeData) -> crossChainTradeData.hashOfSecretB);
}
private String redeemP2sh(CrossChainBitcoinRedeemRequest redeemRequest, ToIntFunction<CrossChainTradeData> lockTimeFn, Function<CrossChainTradeData, byte[]> hashOfSecretFn) {
BTC btc = BTC.getInstance();
NetworkParameters params = btc.getNetworkParameters();
@@ -678,7 +800,10 @@ public class CrossChainResource {
if (crossChainTradeData.mode == Mode.OFFER)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
byte[] redeemScriptBytes = BTCP2SH.buildScript(redeemRequest.refundPublicKeyHash, crossChainTradeData.lockTime, redeemKey.getPubKeyHash(), crossChainTradeData.hashOfSecretB);
int lockTime = lockTimeFn.applyAsInt(crossChainTradeData);
byte[] hashOfSecret = hashOfSecretFn.apply(crossChainTradeData);
byte[] redeemScriptBytes = BTCP2SH.buildScript(redeemRequest.refundPublicKeyHash, lockTime, redeemKey.getPubKeyHash(), hashOfSecret);
byte[] redeemScriptHash = Crypto.hash160(redeemScriptBytes);
Address p2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash);

View File

@@ -299,15 +299,12 @@ public class QortalATAPI extends API {
byte[] messageData = this.getMessageFromTransaction(transactionData);
// Check data length is appropriate, i.e. not larger than B
if (messageData.length > 4 * 8)
return;
// Pad messageData to fit B
byte[] paddedMessageData = Bytes.ensureCapacity(messageData, 4 * 8, 0);
if (messageData.length < 4 * 8)
messageData = Bytes.ensureCapacity(messageData, 4 * 8, 0);
// Endian must be correct here so that (for example) a SHA256 message can be compared to one generated locally
this.setB(state, paddedMessageData);
this.setB(state, messageData);
}
@Override

View File

@@ -34,7 +34,6 @@ import org.qortal.transaction.MessageTransaction;
import org.qortal.transaction.Transaction.ValidationResult;
import org.qortal.transform.TransformationException;
import org.qortal.transform.transaction.DeployAtTransactionTransformer;
import org.qortal.utils.Base58;
import org.qortal.utils.NTP;
public class TradeBot {
@@ -123,7 +122,7 @@ public class TradeBot {
repository.getCrossChainRepository().save(tradeBotData);
// P2SH_a to be funded
byte[] redeemScriptBytes = BTCP2SH.buildScript(tradeForeignPublicKeyHash, crossChainTradeData.lockTime, crossChainTradeData.creatorBitcoinPKH, secretHash);
byte[] redeemScriptBytes = BTCP2SH.buildScript(tradeForeignPublicKeyHash, crossChainTradeData.lockTimeA, crossChainTradeData.creatorBitcoinPKH, secretHash);
byte[] redeemScriptHash = Crypto.hash160(redeemScriptBytes);
Address p2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash);
@@ -189,7 +188,7 @@ public class TradeBot {
return;
}
long tradeStartTimestamp = atData.getCreation();
long atCreationTimestamp = atData.getCreation();
String address = Crypto.toAddress(tradeBotData.getTradeNativePublicKey());
List<MessageTransactionData> messageTransactionsData = repository.getTransactionRepository().getMessagesByRecipient(address, null, null, null);
@@ -223,10 +222,10 @@ public class TradeBot {
byte[] aliceForeignPublicKeyHash = new byte[20];
System.arraycopy(messageData, 20, aliceForeignPublicKeyHash, 0, 20);
// Determine P2SH address and confirm funded
// First P2SH refund timeout is last in chain, so add all of tradeTimeout
int lockTime = (int) (tradeStartTimestamp / 1000L + tradeBotData.getTradeTimeout() * 60);
byte[] redeemScript = BTCP2SH.buildScript(aliceForeignPublicKeyHash, lockTime, tradeBotData.getTradeForeignPublicKeyHash(), aliceSecretHash);
// Determine P2SH-A address and confirm funded
// First P2SH-A refund timeout is last in chain, so add all of tradeTimeout
int lockTimeA = BTCACCT.calcLockTimeA(atCreationTimestamp, tradeBotData.getTradeTimeout());
byte[] redeemScript = BTCP2SH.buildScript(aliceForeignPublicKeyHash, lockTimeA, tradeBotData.getTradeForeignPublicKeyHash(), aliceSecretHash);
String p2shAddress = BTC.getInstance().deriveP2shAddress(redeemScript);
Long balance = BTC.getInstance().getBalance(p2shAddress);
@@ -235,13 +234,10 @@ public class TradeBot {
// Good to go - send MESSAGE to AT
byte[] aliceNativeAddress = Base58.decode(Crypto.toAddress(messageTransactionData.getCreatorPublicKey()));
String aliceNativeAddress = Crypto.toAddress(messageTransactionData.getCreatorPublicKey());
// Build outgoing message, padding each part to 32 bytes to make it easier for AT to consume
byte[] outgoingMessageData = new byte[96];
System.arraycopy(aliceNativeAddress, 0, outgoingMessageData, 0, aliceNativeAddress.length);
System.arraycopy(aliceForeignPublicKeyHash, 0, outgoingMessageData, 32, 20);
System.arraycopy(aliceSecretHash, 0, outgoingMessageData, 64, 20);
byte[] outgoingMessageData = BTCACCT.buildOfferMessage(aliceNativeAddress, aliceForeignPublicKeyHash, aliceSecretHash);
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
MessageTransaction outgoingMessageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, tradeBotData.getAtAddress(), outgoingMessageData, false, false);

View File

@@ -107,6 +107,8 @@ public class BTCACCT {
* @return
*/
public static byte[] buildQortalAT(String creatorTradeAddress, byte[] bitcoinPublicKeyHash, byte[] hashOfSecretB, int tradeTimeout, long qortAmount, long bitcoinAmount) {
int refundTimeout = calcRefundTimeout(tradeTimeout);
// Labels for data segment addresses
int addrCounter = 0;
@@ -207,7 +209,7 @@ public class BTCACCT {
// Refund timeout in minutes (¾ of trade-timeout)
assert dataByteBuffer.position() == addrRefundTimeout * MachineState.VALUE_SIZE : "addrRefundTimeout incorrect";
dataByteBuffer.putLong(tradeTimeout * 3 / 4);
dataByteBuffer.putLong(refundTimeout);
// Redeem Qort amount
assert dataByteBuffer.position() == addrQortAmount * MachineState.VALUE_SIZE : "addrQortAmount incorrect";
@@ -263,7 +265,7 @@ public class BTCACCT {
// Offset into TRADE MESSAGE data payload for extracting secret-B
assert dataByteBuffer.position() == addrTradeMessageSecretBOffset * MachineState.VALUE_SIZE : "addrTradeMessageSecretBOffset incorrect";
dataByteBuffer.putLong(64L);
dataByteBuffer.putLong(32L);
// Source location and length for hashing any passed secret
assert dataByteBuffer.position() == addrMessageDataPointer * MachineState.VALUE_SIZE : "addrMessageDataPointer incorrect";
@@ -634,9 +636,10 @@ public class BTCACCT {
// Skip temporary message data
dataByteBuffer.position(dataByteBuffer.position() + 8 * 4);
// Potential hash of secret A
byte[] hashOfSecretA = new byte[32];
// Potential hash160 of secret A
byte[] hashOfSecretA = new byte[20];
dataByteBuffer.get(hashOfSecretA);
dataByteBuffer.position(dataByteBuffer.position() + 32 - hashOfSecretA.length); // skip to 32 bytes
// Potential recipient's Bitcoin PKH
byte[] recipientBitcoinPKH = new byte[20];
@@ -651,6 +654,8 @@ public class BTCACCT {
tradeData.qortalRecipient = qortalRecipient;
tradeData.hashOfSecretA = hashOfSecretA;
tradeData.recipientBitcoinPKH = recipientBitcoinPKH;
tradeData.lockTimeA = calcLockTimeA(tradeData.creationTimestamp, tradeData.tradeTimeout);
tradeData.lockTimeB = calcLockTimeB(tradeData.creationTimestamp, tradeData.tradeTimeout);
} else {
tradeData.mode = CrossChainTradeData.Mode.OFFER;
}
@@ -658,4 +663,51 @@ public class BTCACCT {
return tradeData;
}
/** Returns trade-info MESSAGE payload for AT creator to send to AT. */
public static byte[] buildOfferMessage(String recipientQortalAddress, byte[] recipientBitcoinPKH, byte[] hashOfSecretA) {
byte[] data = new byte[32 + 32 + 32];
byte[] recipientQortalAddressBytes = Base58.decode(recipientQortalAddress);
System.arraycopy(recipientQortalAddressBytes, 0, data, 0, recipientQortalAddressBytes.length);
System.arraycopy(recipientBitcoinPKH, 0, data, 32, recipientBitcoinPKH.length);
System.arraycopy(hashOfSecretA, 0, data, 64, hashOfSecretA.length);
return data;
}
/** Returns refund MESSAGE payload for AT creator to cancel trade AT. */
public static byte[] buildRefundMessage(String creatorQortalAddress) {
byte[] data = new byte[32];
byte[] creatorQortalAddressBytes = Base58.decode(creatorQortalAddress);
System.arraycopy(creatorQortalAddressBytes, 0, data, 0, creatorQortalAddressBytes.length);
return data;
}
/** Returns redeem MESSAGE payload for trade partner/recipient to send to AT. */
public static byte[] buildTradeMessage(byte[] secretA, byte[] secretB) {
byte[] data = new byte[32 + 32];
System.arraycopy(secretA, 0, data, 0, secretA.length);
System.arraycopy(secretB, 0, data, 32, secretB.length);
return data;
}
/** Returns AT refundTimeout (minutes) based on tradeTimeout. */
public static int calcRefundTimeout(int tradeTimeout) {
return tradeTimeout * 3 / 4;
}
/** Returns P2SH-A lockTime (epoch seconds). */
public static int calcLockTimeA(long atCreationTimestamp, int tradeTimeout) {
return (int) (atCreationTimestamp / 1000L + tradeTimeout * 60);
}
/** Returns P2SH-B lockTime (epoch seconds). */
public static int calcLockTimeB(long atCreationTimestamp, int tradeTimeout) {
return (int) (atCreationTimestamp / 1000L + tradeTimeout / 2 * 60);
}
}

View File

@@ -64,8 +64,11 @@ public class CrossChainTradeData {
public Mode mode;
@Schema(description = "Suggested Bitcoin P2SH nLockTime based on trade timeout")
public Integer lockTime;
@Schema(description = "Suggested Bitcoin P2SH-A nLockTime based on trade timeout")
public Integer lockTimeA;
@Schema(description = "Suggested Bitcoin P2SH-B nLockTime based on trade timeout")
public Integer lockTimeB;
@Schema(description = "Trade partner's Bitcoin public-key-hash (PKH)")
public byte[] recipientBitcoinPKH;