12

In the code for OP_CHECKLOCKTIMEVERIFY i noticed that if the txin sequence number is maxxed out then the script will fail to validate. I'm wondering what the point of this is? Why would anybody ever submit a transaction that will fail to verify to the network?

Here is the relevant section of code:

// 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 (txTo->vin[nIn].IsFinal())
    return false;

I'm also confused about the comments in the code for this. They say that every sequence number has to be maxxed out to get the script to fail to validate, but it looks to me like this is not true - it looks like only one sequence number needs to be maxxed out and then the whole transaction (all txins) will fail. And I assume this would mean that the transaction will therefore not be included in the blockchain? But that runs counter to the code comments. It would make sense if there were a ! on the if condition.

1

3 Answers 3

9

the purpose of OP_CHECKLOCKTIMEVERIFY is kind of the opposite to the purpose of tx.nLockTime. tx.nLockTime prevents transactions with future dates from entering the blockchain, whereas OP_CHECKLOCKTIMEVERIFY enables someone to freeze funds so that they can only be spent after a given timestamp or block height.

tx.nLockTime

tx.nLockTime is validated by function IsFinalTx() in src/main.cpp:

bool IsFinalTx(const CTransaction &tx, int nBlockHeight, int64_t nBlockTime)
{
    if (tx.nLockTime == 0)
        return true;
    if ((int64_t)tx.nLockTime < ((int64_t)tx.nLockTime < LOCKTIME_THRESHOLD ? (int64_t)nBlockHeight : nBlockTime))
        return true;
    BOOST_FOREACH(const CTxIn& txin, tx.vin)
        if (!txin.IsFinal())
            return false;
    return true;
}

where txin.IsFinal() is in src/primitives/transaction.h:

bool IsFinal() const
{
    return (nSequence == std::numeric_limits<uint32_t>::max());
}

If the tx locktime is below the threshold then it is treated as a block height and if it is above the threshold then it is treated as a timestamp. Either way, the transaction locktime value must be smaller than the relevant constraint. If it is larger then miners must wait before including the transaction in a block.

The only way to bypass this transaction locktime constraint is to disable the transaction locktime completely by setting all txin sequence numbers to maxint. When this is done then miners will include the transaction straight away, even if the locktime has not yet been reached.

The idea with the transaction locktime is that before the transaction is locked (ie before the block height or timestamp catch up with tx locktime), someone can make amendments to the transaction. Each time they make a change then they must increment the sequence number to let miners know which amendment comes after another.

One use case for this might be a digital will. If you wanted to pass your money on to someone else specifically in the event of your death then you could create a transaction with a locktime of one year from now and then give it to a few friends. In the event of your death they can broadcast this transaction on the network after one year and the funds will be sent accordingly. Broadcasting the transaction before this time period of one year would not enable them to receive funds, since miners will ignore the transaction until the time period becomes valid (and obviously the friends cannot alter the functionality of this transaction since it is signed by your private key which you never disclose).

If you don't die then you can spend the funds to a different address of your own choosing by broadcasting a different transaction on the network. Your friends will then not be able to use the original transaction you gave them since this would be a doublespend, which the miners do not allow. For the transaction which you broadcast to cancel the will you would alter the locktime to make it sooner, and increment the sequence number. Alternatively you could set the locktime to 0 or set the sequence number to maxint to spend straight away.

OP_CHECKLOCKTIMEVERIFY

OP_CHECKLOCKTIMEVERIFY has a very different use. It is validated in function EvalScript() in src/script/interpreter.cpp:

            case OP_CHECKLOCKTIMEVERIFY:
            {
                if (!(flags & SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY)) {
                    // not enabled; treat as a NOP2
                    if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) {
                        return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS);
                    }
                    break;
                }

                if (stack.size() < 1)
                    return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);

                // Note that elsewhere numeric opcodes are limited to
                // operands in the range -2**31+1 to 2**31-1, however it is
                // legal for opcodes to produce results exceeding that
                // range. This limitation is implemented by CScriptNum's
                // default 4-byte limit.
                //
                // If we kept to that limit we'd have a year 2038 problem,
                // even though the nLockTime field in transactions
                // themselves is uint32 which only becomes meaningless
                // after the year 2106.
                //
                // Thus as a special case we tell CScriptNum to accept up
                // to 5-byte bignums, which are good until 2**39-1, well
                // beyond the 2**32-1 limit of the nLockTime field itself.
                const CScriptNum nLockTime(stacktop(-1), fRequireMinimal, 5);

                // In the rare event that the argument may be < 0 due to
                // some arithmetic being done first, you can always use
                // 0 MAX CHECKLOCKTIMEVERIFY.
                if (nLockTime < 0)
                    return set_error(serror, SCRIPT_ERR_NEGATIVE_LOCKTIME);

                // Actually compare the specified lock time with the transaction.
                if (!checker.CheckLockTime(nLockTime))
                    return set_error(serror, SCRIPT_ERR_UNSATISFIED_LOCKTIME);

                break;
            }

which relies on function CheckLockTime() in the same file:

bool TransactionSignatureChecker::CheckLockTime(const CScriptNum& nLockTime) const
{
    // There are two kinds of nLockTime: lock-by-blockheight
    // and lock-by-blocktime, distinguished by whether
    // nLockTime < LOCKTIME_THRESHOLD.
    //
    // We want to compare apples to apples, so fail the script
    // unless the type of nLockTime being tested is the same as
    // the nLockTime in the transaction.
    if (!(
        (txTo->nLockTime <  LOCKTIME_THRESHOLD && nLockTime <  LOCKTIME_THRESHOLD) ||
        (txTo->nLockTime >= LOCKTIME_THRESHOLD && nLockTime >= LOCKTIME_THRESHOLD)
    ))
        return false;

    // Now that we know we're comparing apples-to-apples, the
    // comparison is a simple numeric one.
    if (nLockTime > (int64_t)txTo->nLockTime)
        return false;

    // 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 (txTo->vin[nIn].IsFinal())
        return false;

    return true;
}

Here the transaction locktime is compared to a value on the stack. To validate successfully, both must be the same side of the theshold (ie both must be interpreted as a block height, or both as a timestamp), and the script will only validate if the stack value is lower than the tx locktime. Or to put it another way, the script will only validate if the transaction locktime has passed the stack value.

Whereas IsFinalTx() prevents transactions with locktimes in the future from being included into the blockchain in the present, OP_CHECKLOCKTIMEVERIFY freezes funds in the blockchain so that they can only be spent after a specified time in the future.

Note that the stack value used for comparison is most useful when placed in the scriptPubKey. The locktime used for comparison against the stack value is that of the signing transaction. This forces the spender to wait for the block or timestamp in order to spend the funds.

As previously discussed, IsFinalTx() does allow transactions with locktimes above the current block height or timestamp to be mined - providing the sequence number is maxxed out, thus disabling the tx locktime. Submitting such a transaction with a maxxed out sequence number would be a sneaky way for the recipient to spend the funds earlier than the time specified by the sender in the txout script. So, to prevent the OP_CHECKLOCKTIMEVERIFY criteria from being bypassed, the script validation must fail when the tx locktime is disabled by the sequence number.

1
  • I've been searching for an example using OP_CLTV, and I think I found one here: api.blockcypher.com/v1/btc/main/txs/… However, the nLockTime for that transaction is 0, and seq is max. I was expecting the source code to compare the value pushed to stack before OP_CHECKLOCKTIMEVERIFY to compare against the current block time and not tx.nLockTime Mar 12, 2016 at 16:21
3

mulllhausen's answer is great, but here's a shorter example that illustrates why the vin seq number can't be final or maxed.

Suppose I want to spend a CLTV output. I wait until the CLTV time has passed, and then create a transaction with nLockTime set to the current block time, and the sequence number set to something less than 2^32-1, say 0.

Now that transaction will verify because

CLTV time <= tx.nLockTime <= current block time

We're basically using nLockTime as a proxy for the current block time to say current block time is past the CLTV time.

However, if I set all my seq numbers to max, I would be able to submit a transaction with an nLockTime in the future, ie, greater than current block time, thereby bypassing the CLTV time. nLockTime must be <= to current block time if not all seq numbers are maxed. So by checking to make sure at least one Vin Seq number is not final, we make sure they all aren't maxed, allowing this bypass scenario.

2

From Bitcoin.org:

Locktime allows signers to create time-locked transactions which will only become valid in the future, giving the signers a chance to change their minds.

...

Previous versions of Bitcoin Core provided a feature which prevented transaction signers from using the method described above to cancel a time-locked transaction, but a necessary part of this feature was disabled to prevent denial of service attacks. A legacy of this system are four-byte sequence numbers in every input. Sequence numbers were meant to allow multiple signers to agree to update a transaction; when they finished updating the transaction, they could agree to set every input’s sequence number to the four-byte unsigned maximum (0xffffffff), allowing the transaction to be added to a block even if its time lock had not expired.

Even today, setting all sequence numbers to 0xffffffff (the default in Bitcoin Core) can still disable the time lock, so if you want to use locktime, at least one input must have a sequence number below the maximum Since sequence numbers are not used by the network for any other purpose, setting any sequence number to zero is sufficient to enable locktime.

So it's essentially that nLockTime is disabled if the sequence value is 0xffffffff, but for 0 <= sequence_value < 0xffffffff, nTimeLock is enabled.

IMO the confusion is arising because there's conflation of the functionality of OP_CHECKLOCKTIMEVERIFY (BIP65) and nTimeLock. I was similarly confused, so check out these Bitcoin.org definitions and nTimeLock may disappoint you

4
  • some more good discussion on the topic - bitcointalk.org/index.php?topic=766893.0 Sep 30, 2015 at 14:40
  • I'm still confused - mainly by the comment where it says "and thus CHECKLOCKTIMEVERIFY bypassed". If the sequence number is maxxed out but there is a script with CHECKLOCKTIMEVERIFY in it, then this will NOT be bypassed - the script will fail! Oct 1, 2015 at 13:35
  • @mulllhausen I'm not versed in C++, so can you clarify what if (txTo->vin[nIn].isFinal()); means? txTo is the spending transaction, and vin[nIn] is just an array of UTXOs, right? What does -> mean? Re if the sequence number is maxxed out but there is a script with CHECKLOCKTIMEVERIFY in it, then this will NOT be bypassed - the script will fail!... Hmm, yes, I see your point. I wonder what bypassing means? It may just mean the scripting drops OP_CLTV if the sequence is less than 0xffffffff? Oct 1, 2015 at 13:42
  • isFinal() is a method in transaction.h. its basically a check for sequence num == 0xffffffff for this txin. when this condition is met then a script containing OP_CHECKLOCKTIMEVERIFY fails with set_error(serror, SCRIPT_ERR_UNSATISFIED_LOCKTIME); Oct 1, 2015 at 14:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.