Skip to content

Commit

Permalink
Add OP_CHECKLOCKTIMEVERIFY script support
Browse files Browse the repository at this point in the history
  • Loading branch information
Ross Nicoll authored and schildbach committed Nov 23, 2015
1 parent 92bd6d0 commit 70f557a
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 3 deletions.
3 changes: 3 additions & 0 deletions core/src/main/java/org/bitcoinj/core/Transaction.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@

import static org.bitcoinj.core.Utils.*;
import static com.google.common.base.Preconditions.checkState;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import org.bitcoinj.script.ScriptChunk;
Expand Down Expand Up @@ -84,6 +85,8 @@ public int compare(final Transaction tx1, final Transaction tx2) {

/** Threshold for lockTime: below this value it is interpreted as block number, otherwise as timestamp. **/
public static final int LOCKTIME_THRESHOLD = 500000000; // Tue Nov 5 00:53:20 1985 UTC
/** Same but as a BigInteger for CHECKLOCKTIMEVERIFY */
public static final BigInteger LOCKTIME_THRESHOLD_BIG = BigInteger.valueOf(LOCKTIME_THRESHOLD);

/** How many bytes a transaction can be before it won't be relayed anymore. Currently 100kb. */
public static final int MAX_STANDARD_TX_SIZE = 100000;
Expand Down
76 changes: 75 additions & 1 deletion core/src/main/java/org/bitcoinj/script/Script.java
Original file line number Diff line number Diff line change
Expand Up @@ -753,12 +753,34 @@ private static boolean castToBool(byte[] data) {
return false;
}

/**
* Cast a script chunk to a BigInteger.
*
* @see #castToBigInteger(byte[], int) for values with different maximum
* sizes.
* @throws ScriptException if the chunk is longer than 4 bytes.
*/
private static BigInteger castToBigInteger(byte[] chunk) throws ScriptException {
if (chunk.length > 4)
throw new ScriptException("Script attempted to use an integer larger than 4 bytes");
return Utils.decodeMPI(Utils.reverseBytes(chunk), false);
}

/**
* Cast a script chunk to a BigInteger. Normally you would want
* {@link #castToBigInteger(byte[])} instead, this is only for cases where
* the normal maximum length does not apply (i.e. CHECKLOCKTIMEVERIFY).
*
* @param maxLength the maximum length in bytes.
* @throws ScriptException if the chunk is longer than the specified maximum.
*/
private static BigInteger castToBigInteger(final byte[] chunk, final int maxLength) throws ScriptException {
if (chunk.length > maxLength)
throw new ScriptException("Script attempted to use an integer larger than "
+ maxLength + " bytes");
return Utils.decodeMPI(Utils.reverseBytes(chunk), false);
}

public boolean isOpReturn() {
return chunks.size() > 0 && chunks.get(0).equalsOpCode(OP_RETURN);
}
Expand Down Expand Up @@ -1271,8 +1293,17 @@ public static void executeScript(@Nullable Transaction txContainingThis, long in
throw new IllegalStateException("Script attempted signature check but no tx was provided");
opCount = executeMultiSig(txContainingThis, (int) index, script, stack, opCount, lastCodeSepLocation, opcode, verifyFlags);
break;
case OP_CHECKLOCKTIMEVERIFY:
if (!verifyFlags.contains(VerifyFlag.CHECKLOCKTIMEVERIFY)) {
// not enabled; treat as a NOP2
if (verifyFlags.contains(VerifyFlag.DISCOURAGE_UPGRADABLE_NOPS)) {
throw new ScriptException("Script used a reserved opcode " + opcode);
}
break;
}
executeCheckLockTimeVerify(txContainingThis, (int) index, script, stack, lastCodeSepLocation, opcode, verifyFlags);
break;
case OP_NOP1:
case OP_NOP2:
case OP_NOP3:
case OP_NOP4:
case OP_NOP5:
Expand All @@ -1281,6 +1312,9 @@ public static void executeScript(@Nullable Transaction txContainingThis, long in
case OP_NOP8:
case OP_NOP9:
case OP_NOP10:
if (verifyFlags.contains(VerifyFlag.DISCOURAGE_UPGRADABLE_NOPS)) {
throw new ScriptException("Script used a reserved opcode " + opcode);
}
break;

default:
Expand All @@ -1296,6 +1330,46 @@ public static void executeScript(@Nullable Transaction txContainingThis, long in
throw new ScriptException("OP_IF/OP_NOTIF without OP_ENDIF");
}

// This is more or less a direct translation of the code in Bitcoin Core
private static void executeCheckLockTimeVerify(Transaction txContainingThis, int index, Script script, LinkedList<byte[]> stack,
int lastCodeSepLocation, int opcode,
Set<VerifyFlag> verifyFlags) throws ScriptException {
if (stack.size() < 1)
throw new ScriptException("Attempted OP_CHECKLOCKTIMEVERIFY on a stack with size < 1");

// Thus as a special case we tell CScriptNum to accept up
// to 5-byte bignums to avoid year 2038 issue.
final BigInteger nLockTime = castToBigInteger(stack.getLast(), 5);

if (nLockTime.compareTo(BigInteger.ZERO) < 0)
throw new ScriptException("Negative locktime");

// There are two kinds of nLockTime, need to ensure we're comparing apples-to-apples
if (!(
((txContainingThis.getLockTime() < Transaction.LOCKTIME_THRESHOLD) && (nLockTime.compareTo(Transaction.LOCKTIME_THRESHOLD_BIG)) < 0) ||
((txContainingThis.getLockTime() >= Transaction.LOCKTIME_THRESHOLD) && (nLockTime.compareTo(Transaction.LOCKTIME_THRESHOLD_BIG)) >= 0))
)
throw new ScriptException("Locktime requirement type mismatch");

// Now that we know we're comparing apples-to-apples, the
// comparison is a simple numeric one.
if (nLockTime.compareTo(BigInteger.valueOf(txContainingThis.getLockTime())) > 0)
throw new ScriptException("Locktime requirement not satisfied");

// Finally the nLockTime feature can be disabled and thus
// CHECKLOCKTIMEVERIFY bypassed if every txin has been
// finalized by setting nSequence to maxint. The
// transaction would be allowed into the blockchain, making
// the opcode ineffective.
//
// Testing if this vin is not final is sufficient to
// prevent this condition. Alternatively we could test all
// inputs, but testing just this input minimizes the data
// required to prove correct CHECKLOCKTIMEVERIFY execution.
if (!txContainingThis.getInput(index).hasSequence())
throw new ScriptException("Transaction contains a final transaction input for a CHECKLOCKTIMEVERIFY script.");
}

private static void executeCheckSig(Transaction txContainingThis, int index, Script script, LinkedList<byte[]> stack,
int lastCodeSepLocation, int opcode,
Set<VerifyFlag> verifyFlags) throws ScriptException {
Expand Down
11 changes: 9 additions & 2 deletions core/src/main/java/org/bitcoinj/script/ScriptOpCodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,15 @@ public class ScriptOpCodes {
public static final int OP_CHECKMULTISIG = 0xae;
public static final int OP_CHECKMULTISIGVERIFY = 0xaf;

// block state
/** Check lock time of the block. Introduced in BIP 65, replacing OP_NOP2 */
public static final int OP_CHECKLOCKTIMEVERIFY = 0xb1;

// expansion
public static final int OP_NOP1 = 0xb0;
public static final int OP_NOP2 = 0xb1;
/** Deprecated by BIP 65 */
@Deprecated
public static final int OP_NOP2 = OP_CHECKLOCKTIMEVERIFY;
public static final int OP_NOP3 = 0xb2;
public static final int OP_NOP4 = 0xb3;
public static final int OP_NOP5 = 0xb4;
Expand Down Expand Up @@ -258,7 +264,7 @@ public class ScriptOpCodes {
.put(OP_CHECKMULTISIG, "CHECKMULTISIG")
.put(OP_CHECKMULTISIGVERIFY, "CHECKMULTISIGVERIFY")
.put(OP_NOP1, "NOP1")
.put(OP_NOP2, "NOP2")
.put(OP_CHECKLOCKTIMEVERIFY, "CHECKLOCKTIMEVERIFY")
.put(OP_NOP3, "NOP3")
.put(OP_NOP4, "NOP4")
.put(OP_NOP5, "NOP5")
Expand Down Expand Up @@ -371,6 +377,7 @@ public class ScriptOpCodes {
.put("CHECKMULTISIG", OP_CHECKMULTISIG)
.put("CHECKMULTISIGVERIFY", OP_CHECKMULTISIGVERIFY)
.put("NOP1", OP_NOP1)
.put("CHECKLOCKTIMEVERIFY", OP_CHECKLOCKTIMEVERIFY)
.put("NOP2", OP_NOP2)
.put("NOP3", OP_NOP3)
.put("NOP4", OP_NOP4)
Expand Down

0 comments on commit 70f557a

Please sign in to comment.