diff --git a/core/src/main/java/org/bitcoinj/protocols/channels/PaymentChannelV1ClientState.java b/core/src/main/java/org/bitcoinj/protocols/channels/PaymentChannelV1ClientState.java index b8b97d19..0cb665d9 100644 --- a/core/src/main/java/org/bitcoinj/protocols/channels/PaymentChannelV1ClientState.java +++ b/core/src/main/java/org/bitcoinj/protocols/channels/PaymentChannelV1ClientState.java @@ -151,7 +151,9 @@ public class PaymentChannelV1ClientState extends PaymentChannelClientState { // in future as it breaks the intended design of timelocking/tx replacement, but for now it simplifies this // specific protocol somewhat. refundTx = new Transaction(params); - refundTx.addInput(multisigOutput).setSequenceNumber(0); // Allow replacement when it's eventually reactivated. + // don't disable lock time. the sequence will be included in the server's signature and thus won't be changeable. + // by using this sequence value, we avoid extra full replace-by-fee and relative lock time processing. + refundTx.addInput(multisigOutput).setSequenceNumber(TransactionInput.NO_SEQUENCE - 1L); refundTx.setLockTime(expiryTime); if (totalValue.compareTo(Coin.CENT) < 0 && Context.get().isEnsureMinRequiredFee()) { // Must pay min fee. diff --git a/core/src/main/java/org/bitcoinj/protocols/channels/PaymentChannelV1ServerState.java b/core/src/main/java/org/bitcoinj/protocols/channels/PaymentChannelV1ServerState.java index 3a5492aa..80b683f6 100644 --- a/core/src/main/java/org/bitcoinj/protocols/channels/PaymentChannelV1ServerState.java +++ b/core/src/main/java/org/bitcoinj/protocols/channels/PaymentChannelV1ServerState.java @@ -125,9 +125,9 @@ public class PaymentChannelV1ServerState extends PaymentChannelServerState { // Verify that the refund transaction has a single input (that we can fill to sign the multisig output). if (refundTx.getInputs().size() != 1) throw new VerificationException("Refund transaction does not have exactly one input"); - // Verify that the refund transaction has a time lock on it and a sequence number of zero. - if (refundTx.getInput(0).getSequenceNumber() != 0) - throw new VerificationException("Refund transaction's input's sequence number is non-0"); + // Verify that the refund transaction has a time lock on it and a sequence number that does not disable lock time. + if (refundTx.getInput(0).getSequenceNumber() == TransactionInput.NO_SEQUENCE) + throw new VerificationException("Refund transaction's input's sequence number disables lock time"); if (refundTx.getLockTime() < minExpireTime) throw new VerificationException("Refund transaction has a lock time too soon"); // Verify the transaction has one output (we don't care about its contents, its up to the client) diff --git a/core/src/main/java/org/bitcoinj/protocols/channels/PaymentChannelV2ClientState.java b/core/src/main/java/org/bitcoinj/protocols/channels/PaymentChannelV2ClientState.java index 94300d68..3e3cbb1a 100644 --- a/core/src/main/java/org/bitcoinj/protocols/channels/PaymentChannelV2ClientState.java +++ b/core/src/main/java/org/bitcoinj/protocols/channels/PaymentChannelV2ClientState.java @@ -119,13 +119,12 @@ public class PaymentChannelV2ClientState extends PaymentChannelClientState { contract = req.tx; // Build a refund transaction that protects us in the case of a bad server that's just trying to cause havoc - // by locking up peoples money (perhaps as a precursor to a ransom attempt). We time lock it so the server - // has an assurance that we cannot take back our money by claiming a refund before the channel closes - this - // relies on the fact that since Bitcoin 0.8 time locked transactions are non-final. This will need to change - // in future as it breaks the intended design of timelocking/tx replacement, but for now it simplifies this - // specific protocol somewhat. + // by locking up peoples money (perhaps as a precursor to a ransom attempt). We time lock it because the + // CheckLockTimeVerify opcode requires a lock time to be specified and the input to have a non-final sequence + // number (so that the lock time is not disabled). refundTx = new Transaction(params); - refundTx.addInput(contract.getOutput(0)).setSequenceNumber(0); // Allow replacement when it's eventually reactivated. + // by using this sequence value, we avoid extra full replace-by-fee and relative lock time processing. + refundTx.addInput(contract.getOutput(0)).setSequenceNumber(TransactionInput.NO_SEQUENCE - 1L); refundTx.setLockTime(expiryTime); if (totalValue.compareTo(Coin.CENT) < 0 && Context.get().isEnsureMinRequiredFee()) { // Must pay min fee.